chart = (function createChart() {
const width = 950;
const height = 520;
const marginTop = 40;
const marginRight = 40;
const marginBottom = 30;
const marginLeft = 30;
const x = d3.scaleBand()
.domain(barley.map(d => d.site))
.range([marginLeft, width - marginRight])
.padding(0.1);
const xAxis = d3.axisBottom(x).tickSizeOuter(0);
const y = d3.scaleLinear()
.domain([0, d3.max(barley, d => d.yield)]).nice()
.range([height - marginBottom, marginTop]);
const varieties = [...new Set(barley.map(d => d.variety))];
const varietyColor = d3.scaleOrdinal(d3.schemeCategory10)
.domain(varieties);
const years = [...new Set(barley.map(d => d.year))];
const sizeScale = d3.scaleLinear()
.domain([d3.min(years), d3.max(years)])
.range([4, 8]);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto;");
//Create zoom behavior
const zoom = d3.zoom()
.scaleExtent([1, 5])
.on("zoom", function(event) {
const transform = event.transform;
svg.selectAll("circle:not(.year-legend-circle)")
.attr("transform", transform);
svg.select(".x-axis").call(xAxis.scale(transform.rescaleX(x)));
svg.select(".y-axis").call(d3.axisRight(transform.rescaleY(y)));
});
//Attach zoom behavior to SVG
svg.call(zoom)
.on("dblclick.zoom", null); //Disable double-click zooming
//Sort barley dataset by yield
barley.sort((a, b) => d3.ascending(a.yield, b.yield));
//Append circles with animation
const circles = svg.selectAll("circle")
.data(barley)
.enter()
.append("circle")
.attr("cx", d => x(d.site) + x.bandwidth() / 2) //Center
.attr("cy", height) //Start from bottom for animation
.attr("fill", d => varietyColor(d.variety)) //Color based on Variety
.attr("r", d => sizeScale(d.year)) //Size based on Year
circles.on("mouseover", function(event, d) {
d3.select(this)
.attr("r", sizeScale(d.year) + 2) //Increase size for hover
.raise(); //Bring to front
//Tooltip
const tooltipGroup = svg.append("g")
.attr("class", "tooltip")
.attr("pointer-events", "none"); //Ensure it doesn't interfere with other mouse events
const tooltipRect = tooltipGroup.append("rect");
const tooltipText = tooltipGroup.append("text")
.attr("x", x(d.site) + x.bandwidth() / 2)
.attr("y", y(d.yield) - 10)
.attr("font-size", "12px")
.attr("text-anchor", "middle")
.attr("fill", "white")
.text(`Variety: ${d.variety}, Year: ${d.year}, Yield: ${d.yield.toFixed(1)}`);
const textWidth = tooltipText.node().getBBox().width;
tooltipRect
.attr("x", x(d.site) + x.bandwidth() / 2 - textWidth / 2 - 5)
.attr("y", y(d.yield) - 25)
.attr("width", textWidth + 10)
.attr("height", 20)
.attr("fill", "black")
.attr("rx", 5)
.attr("ry", 5);
})
.on("mouseout", function(event, d) {
d3.select(this).attr("r", sizeScale(d.year)); //Reset size
svg.selectAll(".tooltip").remove(); //Remove tooltip
});
circles.transition() //Add transition
.duration(1000)
.attr("cy", d => y(d.yield)); //End position for animation
//Append axes
svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis);
const yAxisGroup = svg.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${width - marginRight},0)`)
.call(d3.axisRight(y))
.call(g => g.select(".domain").remove());
//Adjusted Y-axis title positioning
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("y", width - 10) //Adjust to position closer to edge
.attr("x", -height / 2)
.attr("text-anchor", "middle")
.attr("font-size", "14px")
.text("Yield (units)");
//Varieties Legend
const varietyLegend = svg.append("g")
.attr("transform", `translate(${marginLeft}, ${marginTop - 20})`);
const legendHeight = 15; //Adjust for smaller spacing
varieties.forEach((variety, i) => {
const yOffset = i * legendHeight;
varietyLegend.append("rect")
.attr("width", 8)
.attr("height", 8)
.attr("fill", varietyColor(variety))
.attr("y", yOffset);
varietyLegend.append("text")
.attr("x", 12)
.attr("y", yOffset + 7)
.attr("font-size", "10px")
.text(variety);
});
//Year legend below Variety Legend
const yearLegendYOffset = marginTop + varieties.length * legendHeight + 10;
//Legend for years by size
const yearLegend = svg.append("g")
.attr("transform", `translate(${marginLeft}, ${yearLegendYOffset})`);
years.sort().forEach((year, i) => {
const yOffset = i * legendHeight;
yearLegend.append("circle")
.attr("class", "year-legend-circle")
.attr("r", sizeScale(year))
.attr("fill", "gray")
.attr("cy", yOffset + sizeScale(year));
yearLegend.append("text")
.attr("x", 20)
.attr("y", yOffset + sizeScale(year) + 4)
.attr("font-size", "10px")
.text(year);
});
//Title
svg.append("text")
.attr("x", width / 2)
.attr("y", marginTop - 10)
.attr("text-anchor", "middle")
.attr("font-size", "24px")
.attr("font-weight", "bold")
.text("Barley Yield by Site Over Years");
return svg.node();
})();