Agricultural Production Statistics in New Zealand
Racing bar for annual yield of cereals and legumes.
By Manish Datt
TidyTuesday dataset of 2026-02-17
Racing Bars by Year
Plotting code
<head>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://unpkg.com/tabulator-tables@6.3.0/dist/css/tabulator.min.css" rel="stylesheet" />
<script src="https://unpkg.com/tabulator-tables@6.3.0/dist/js/tabulator.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
<style>
.tabulator {
border: 1px solid #e5e7eb;
border-radius: 0.75rem;
overflow: hidden;
font-size: 0.9rem;
}
.tabulator .tabulator-header {
border-bottom: 1px solid #e5e7eb;
background: #f8fafc;
}
.tabulator .tabulator-header .tabulator-col {
background: #f8fafc;
}
</style>
</head>
<body class="bg-slate-50 text-slate-900">
<main class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<section class="rounded-xl border border-slate-200 bg-white p-4 shadow-sm sm:p-6">
<div id="table"></div>
</section>
<section class="mt-6 rounded-xl border border-slate-200 bg-white p-4 shadow-sm sm:p-6">
<div class="mb-1.5 flex items-center justify-between gap-3">
<h2 class="text-lg font-semibold">Racing Bars by Year</h2>
</div>
<div id="yield-race" class="h-[560px] w-full"></div>
</section>
</main>
<script>
function renderTable(rows) {
const firstRow = rows[0] || {};
const columns = Object.keys(firstRow).map((key) => ({
title: key,
field: key,
headerFilter: "input",
hozAlign: "left",
}));
new Tabulator("#table", {
data: rows,
layout: "fitDataStretch",
pagination: true,
paginationSize: 10,
movableColumns: true,
resizableRows: false,
columns,
});
}
function renderYieldRace(rows) {
const yieldRows = rows
.filter((row) => row.measure && row.measure.toLowerCase().includes("yield"))
.map((row) => ({
year: Number(row.year_ended_june),
measure: row.measure,
value: Number(row.value),
}))
.filter((row) => Number.isFinite(row.year) && Number.isFinite(row.value));
if (!yieldRows.length) {
document.getElementById("yield-race").innerHTML = '<div class="rounded-lg border border-yellow-200 bg-yellow-50 p-4 text-sm text-yellow-800">No yield rows found.</div>';
return;
}
const rawMaxYieldValue = Math.max(...yieldRows.map((row) => row.value), 0);
const maxYieldValue = Math.ceil(rawMaxYieldValue / 50000) * 50000;
const years = [...new Set(yieldRows.map((row) => row.year))].sort((a, b) => a - b);
const measures = [...new Set(yieldRows.map((row) => row.measure))];
const valueByYearMeasure = new Map();
for (const row of yieldRows) {
valueByYearMeasure.set(`${row.year}||${row.measure}`, row.value);
}
const chart = echarts.init(document.getElementById("yield-race"));
let yearIndex = 0;
const frameMs = 1200;
const barColors = ["#0f766e", "#0369a1", "#b45309", "#be123c", "#4f46e5"];
function buildSeriesData(year) {
return measures
.map((measure) => ({
name: measure,
value: valueByYearMeasure.get(`${year}||${measure}`) ?? 0,
}))
.sort((a, b) => a.value - b.value);
}
function updateChart(targetYearIndex = yearIndex, shouldAdvance = true) {
const year = years[targetYearIndex];
const seriesData = buildSeriesData(year);
chart.setOption({
title: {
text: "Timeline of annual yield (Tonnes) for cereals and legumes in New Zealand",
left: "center",
top: 4,
textStyle: { fontSize: 18, fontWeight: 600 },
},
graphic: [
{
type: "text",
right: 125,
bottom: 100,
z: 100,
style: {
text: String(year),
font: "700 44px sans-serif",
fill: "rgba(15, 23, 42, 0.22)",
},
},
],
xAxis: {
max: maxYieldValue,
axisLabel: {
formatter: (val) => {
const num = Number(val);
if (Math.abs(num) >= 1000) return `${(num / 1000).toLocaleString(undefined, { maximumFractionDigits: 1 })}K`;
return num.toLocaleString();
},
},
},
yAxis: {
type: "category",
inverse: true,
max: 4,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
formatter: (label) => String(label).replace(/\s*\(yield\)\s*$/i, ""),
},
animationDuration: 250,
animationDurationUpdate: 250,
data: seriesData.map((item) => item.name),
},
series: [
{
realtimeSort: true,
type: "bar",
data: seriesData,
label: {
show: true,
position: "right",
valueAnimation: true,
formatter: (param) => Number(param.value).toLocaleString(),
},
itemStyle: {
borderRadius: [0, 4, 4, 0],
color: (param) => barColors[param.dataIndex % barColors.length],
},
},
],
});
if (shouldAdvance) {
yearIndex = (targetYearIndex + 1) % years.length;
}
}
chart.setOption({
grid: {
top: 30,
bottom: 50,
left: 100,
right: 100,
},
tooltip: {
trigger: "item",
valueFormatter: (val) => Number(val).toLocaleString(),
},
xAxis: {
type: "value",
},
yAxis: {
type: "category",
inverse: true,
axisLine: { show: false },
axisTick: { show: false },
axisLabel: {
formatter: (label) => String(label).replace(/\s*\(yield\)\s*$/i, ""),
},
},
series: [
{
type: "bar",
realtimeSort: true,
},
],
animationDuration: 0,
animationDurationUpdate: frameMs,
animationEasing: "linear",
animationEasingUpdate: "linear",
});
updateChart();
setInterval(updateChart, frameMs);
window.addEventListener("resize", () => chart.resize());
}
async function loadData() {
const response = await fetch("dataset.csv");
if (!response.ok) throw new Error("Failed to load dataset.csv");
const csvText = await response.text();
const parsed = Papa.parse(csvText, {
header: true,
dynamicTyping: true,
skipEmptyLines: true,
});
const rows = parsed.data;
renderTable(rows);
renderYieldRace(rows);
}
loadData().catch((error) => {
const tableEl = document.getElementById("table");
const chartEl = document.getElementById("yield-race");
const msg = '<div class="rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-700">Could not load dataset.csv. Make sure you are serving this folder via a local server.</div>';
tableEl.innerHTML = msg;
chartEl.innerHTML = msg;
console.error(error);
});
</script>
</body>