Public
Edited
Oct 23, 2023
Insert cell
Insert cell
barley.json
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
chart = (function createChart() {
//Specify dimensions
const width = 950;
const height = 520;
const marginTop = 40; //Space for title
const marginRight = 40; //Y-axis space
const marginBottom = 30;
const marginLeft = 30; //Legend space

//Create horizontal scale
const x = d3.scaleBand()
.domain(barley.map(d => d.site))
.range([marginLeft, width - marginRight])
.padding(0.1);

const xAxis = d3.axisBottom(x).tickSizeOuter(0);

//Create vertical scale
const y = d3.scaleLinear()
.domain([0, d3.max(barley, d => d.yield)]).nice()
.range([height - marginBottom, marginTop]);

//Color scale by variety
const varieties = [...new Set(barley.map(d => d.variety))];
const varietyColor = d3.scaleOrdinal(d3.schemeCategory10)
.domain(varieties);

//Size scale by year
const years = [...new Set(barley.map(d => d.year))];
const sizeScale = d3.scaleLinear()
.domain([d3.min(years), d3.max(years)])
.range([4, 8]);

//Create SVG container
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();
})();
Insert cell
When redesigning this chart, I wanted to make it feel fresh and more engaging. Bar charts are everywhere, and while they're effective, they can sometimes feel a bit stale. So, I opted for circles, which have this organic feel and can easily show the barley yields just by their position (variety and yield shown when hovering over specific circles). The color coding by year adds another layer of understanding; it's like telling a colorful story of barley yields over time. And who doesn't like a dash of color to liven things up?

I laid out the different sites on our horizontal axis, making sure each had its own spot. Yields, on the other hand, determined how high our circles floated. And to make things even more interesting, I added a zoom feature. The zoom is like a magnifying glass, letting viewers get up close to parts of the data they're curious about. Ultimately, all these changes were about making the data not just readable, but relatable and fun to explore.

Major changes to the reference code (Zoomable Bar Chart and Line Chart with Tooltip, predominately):
- Mark Type: Changed from Bars to Circles
- Flipped the axes
- Size Legend by Year
- Tooltip showing Variety, Year, and Yield inserted
- Y-axis moved to right side
- Legend and Title inserted
- Y axis-Title added
- Animation and Zoom feature created
- Ability to compare varieties added (Color Legend)
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more