Insert cell
Insert cell
barley.json
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
barley
Insert cell
Insert cell
Insert cell
barleyForVarietySiteVis = d3.flatGroup(
barley,
(d) => d.variety,
(d) => d.site
)
Insert cell
barleyForVarietyLabel = d3.rollup(
barley,
(D) => d3.reduce(D, (r, d) => r + d.yield, 0),
(d) => d.variety,
(d) => d.year
)
Insert cell
barleyForSiteLabel = d3.rollup(
barley,
(D) => d3.reduce(D, (r, d) => r + d.yield, 0),
(d) => d.site,
(d) => d.year
)
Insert cell
{
const plotWidth = 520;
const keyWidth = 240;
const width = plotWidth + keyWidth;
const height = 400;
const margin = { top: 30, bottom: 20, left: 80, right: 20 };

const svg = d3
.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);

const instruction = "Mouse over a circle or label to read the data!";

const text = svg
.append("text")
.style("font", "12px sans-serif")
.attr("y", 15)
.text(instruction);

const g = svg
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

g.append("rect")
.attr("width", plotWidth)
.attr("height", height)
.style("fill", "#eee");

const x = d3
.scaleBand()
.domain(Array.from(new Set(barleyForVarietySiteVis.map((d) => d[0]))))
.range([0, plotWidth]);

const y = d3
.scaleBand()
.domain(Array.from(new Set(barleyForVarietySiteVis.map((d) => d[1]))))
.range([height, 0]);

const rMax = d3.min([x.bandwidth() / 2, y.bandwidth() / 2]);

const rFactor =
rMax /
d3.max(
barleyForVarietySiteVis.map((d) => d3.max(d[2].map((d) => d["yield"])))
);

const red = "#b00";
const green = "#0b0";

{
const xKey = margin.left + plotWidth + 36;
let y = 65;

const xGap = 10;
const yGap = 10;

const r1 = rMax * 0.75;
const r2 = rMax * 0.5;
const r3 = rMax;

const heading = svg
.append("text")
.style("font-size", "28px")
.attr("x", xKey + r3)
.attr("y", y)
.attr("text-anchor", "middle")
.text("Key");

y += yGap;

svg
.append("circle")
.attr("r", r1)
.attr("cx", xKey + r3)
.attr("cy", y + r3)
.style("fill", red)
.style("fill-opacity", "0.5");

svg
.append("text")
.attr("x", xKey + 2 * r3 + xGap)
.attr("y", y + r3)
.attr("dominant-baseline", "middle")
.text("- 1931 yield");

y += 2 * r3 + yGap;

svg
.append("circle")
.attr("r", r1)
.attr("cx", xKey + r3)
.attr("cy", y + r3)
.style("fill", green)
.style("fill-opacity", "0.5");

svg
.append("text")
.attr("x", xKey + 2 * r3 + xGap)
.attr("y", y + r3)
.attr("dominant-baseline", "middle")
.text("- 1932 yield");

y += 2 * r3 + yGap;

svg
.append("circle")
.attr("r", r1)
.attr("cx", xKey + r3)
.attr("cy", y + r3)
.style("fill", red)
.style("fill-opacity", "0.5");

svg
.append("circle")
.attr("r", r1)
.attr("cx", xKey + r3)
.attr("cy", y + r3)
.style("fill", green)
.style("fill-opacity", "0.5");

svg
.append("text")
.attr("x", xKey + 2 * r3 + xGap)
.attr("y", y + r3)
.attr("dominant-baseline", "middle")
.text("- common yield");

y += 2 * r3 + yGap;

svg
.append("circle")
.attr("r", r2)
.attr("cx", xKey + r3)
.attr("cy", y + r3)
.style("fill", red)
.style("fill-opacity", "0.5");

svg
.append("circle")
.attr("r", r3)
.attr("cx", xKey + r3)
.attr("cy", y + r3)
.style("fill", green)
.style("fill-opacity", "0.5");

svg
.append("text")
.attr("x", xKey + 2 * r3 + xGap)
.attr("y", y + r3)
.attr("dominant-baseline", "middle")
.text("- increased yield");

y += 2 * r3 + yGap;

svg
.append("circle")
.attr("r", r3)
.attr("cx", xKey + r3)
.attr("cy", y + r3)
.style("fill", red)
.style("fill-opacity", "0.5");

svg
.append("circle")
.attr("r", r2)
.attr("cx", xKey + r3)
.attr("cy", y + r3)
.style("fill", green)
.style("fill-opacity", "0.5");

svg
.append("text")
.attr("x", xKey + 2 * r3 + xGap)
.attr("y", y + r3)
.attr("dominant-baseline", "middle")
.text("- decreased yield");
}

const num = d3.format(".1f");
const diff = d3.format("+.1%");

const gs = g
.append("g")
.selectAll("g")
.data(barleyForVarietySiteVis)
.join("g")
.on("mouseenter", (event, d) => {
if (d[2][0]["year"] !== "1931" || d[2][1]["year"] !== "1932") {
throw d;
}
const y1 = d[2][0]["yield"];
const y2 = d[2][1]["yield"];
let s = "";
text.text(
diff((y2 - y1) / y1) +
" ︱ " +
num(y1) +
" -> " +
num(y2) +
" ︱ " +
d[0] +
" @ " +
d[1]
);
d3.select(event.currentTarget).select("rect").attr("opacity", "0.05");
})
.on("mouseleave", (event, d) => {
text.text(instruction);
d3.select(event.currentTarget).select("rect").attr("opacity", "0");
});

gs.append("rect")
.attr("opacity", "0")
.attr("x", (d) => x(d[0]))
.attr("y", (d) => y(d[1]))
.attr("width", x.bandwidth())
.attr("height", y.bandwidth());

gs.selectAll("circle")
.data((d) => d[2])
.join("circle")
.attr("r", (d, i) => d["yield"] * rFactor)
.attr("cx", (d) => x(d["variety"]) + x.bandwidth() / 2)
.attr("cy", (d) => y(d["site"]) + y.bandwidth() / 2)
.style("fill", (d) =>
d["year"] === "1931" ? red : "1932" ? green : "#fff"
)
.style("fill-opacity", 0.5);

g.append("g")
.attr("transform", `translate(0, ${height})`)
.attr("class", "x-axis")
.call(d3.axisBottom(x));

g.append("g").attr("class", "y-axis").call(d3.axisLeft(y));

svg
.selectAll(".x-axis .tick")
.on("mouseover", (event, d) => {
const yields = barleyForVarietyLabel.get(d);
const y1 = yields.get("1931");
const y2 = yields.get("1932");
text.text(
diff((y2 - y1) / y1) + " ︱ " + num(y1) + " -> " + num(y2) + " ︱ " + d
);
})
.on("mouseleave", (event, d) => {
text.text(instruction);
});

svg
.selectAll(".y-axis .tick")
.on("mouseover", (event, d) => {
const yields = barleyForSiteLabel.get(d);
const y1 = yields.get("1931");
const y2 = yields.get("1932");
text.text(
diff((y2 - y1) / y1) + " ︱ " + num(y1) + " -> " + num(y2) + " ︱ " + d
);
})
.on("mouseleave", (event, d) => {
text.text(instruction);
});

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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