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

Insert cell
Insert cell
chart = {
const containerWidth = width; // total width including margins
const containerHeight = 500; // total height including margins
const margin = { top: 50, right: 50, bottom: 50, left: 50 };
const chartWidth = containerWidth - margin.left - margin.right; // chart width after margin-left and margin-right
const chartHeight = containerHeight - margin.top - margin.bottom; // chart height after margin-top and margin-bottom

// create and configure svg element
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, containerWidth, containerHeight])
.attr("width", containerWidth)
.attr("height", containerHeight)
.attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");

// create a group within the svg element to apply margins
const chartGroup = svg
.append("g")
.attr(
"transform",
`translate(${chartWidth / 3 - margin.left},${margin.top})`
);

// creating a deep clone of the current chartData from barley dataset for use.
let chartData = [...barley];

// choosing barley yield by site over the years
if (year === "year_1931") {
chartData = d3.filter(barley, (d) => d.year === "1931");
} else if (year === "year_1932") {
chartData = d3.filter(barley, (d) => d.year === "1932");
}

// configure data according to circular packing
const groupedData = d3.group(chartData, (d) => d.variety);
const hierarchyData = {
name: "varieties",
children: Array.from(groupedData, ([key, values]) => ({
name: key,
children: d3.map(values, (d) => ({
name: d.site,
size: d.yield
}))
}))
};

// create circular packing chart
const diameter = chartWidth / 3;
const pack = d3.pack().size([diameter, diameter]).padding(20);

// create root
const root = d3
.hierarchy(hierarchyData)
.sum((d) => {
return d.size;
})
.sort((a, b) => {
return b.value - a.value;
});

// create node
const node = chartGroup
.selectAll(".node")
.data(pack(root).descendants())
.join("g")
.attr("id", (d) => {
return d.data.name;
})
.attr("class", (d) => {
return d.children ? "node" : "leaf node";
})
.attr("transform", (d) => {
return "translate(" + d.x + "," + d.y + ")";
});

// craete color array
const colorGrid = [
"#4e79a7",
"#f28e2c",
"#e15759",
"#76b7b2",
"#59a14f",
"#edc949",
"#af7aa1",
"#ff9da7",
"#9c755f",
"#bab0ab"
];
// craete color scale for (variety)
const colorScale = d3
.scaleOrdinal()
.domain(d3.map(chartData, (d) => d.site))
.range(colorGrid);

// add circle
const circle = node
.append("circle")
.attr("r", (d) => d.r)
.attr("opacity", (d, i) => (i === 0 ? 0.2 : !d?.children ? 1 : 0.5))
.attr("fill", (d) =>
!d?.children ? colorScale(d?.parent.data.name) : colorScale(d.data.name)
)
.attr("site", (d) => !d.children && d.data.name)
.attr("yield", (d) => !d.children && d.data.size)
.attr("stroke", "black")
.attr("stroke-width", 1.2);

// add click to circle to highlight same site accross all variety
circle.on("click", (e, d, i) => {
// do nothing if there is children insite circle
if (d?.children) {
return;
}
});

// create tooltip container
const tooltipContainer = d3
.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.style("padding", "10px")
.style("font", "14px sans-serif")
.style("background", "lightsteelblue")
.style("border-radius", "4px")
.style("color", "#444");

// add tooltip on mousemove
circle.on("mouseover", function (e, d, i) {
// do nothing if there is children insite circle
if (d?.children) {
return;
}
// get value from DOM
const site = d3.select(this).attr("site");
const yieldSize = d3.select(this).attr("yield");
tooltipContainer
.html(`${site}<br/>Yield: <strong>${yieldSize}</strong>`)
.style("visibility", "visible");
});
circle.on("mousemove", function (event) {
tooltipContainer
.style("left", event.pageX + 10 + "px")
.style("top", event.pageY + 10 + "px");
});
// hide tooltip in mouseout
circle.on("mouseout", function () {
tooltipContainer.html(``).style("visibility", "hidden");
});

// get unique variety
const uniqueVariety = [...new Set(d3.map(chartData, (d) => d.variety))];

// create legend (variety)
const legendRectWidth = 60;
const legendRectHeight = 15;

// create legend scale
const legendScale = d3
.scaleBand()
.domain(uniqueVariety)
.range([0, legendRectWidth * uniqueVariety.length]);

// create legend axis
svg
.append("g")
.attr(
"transform",
(d, i) =>
`translate(${
chartWidth / 2 - legendScale.range()[1] / 2 + legendRectWidth * i
},${chartHeight + legendRectHeight + margin.bottom / 2})`
)
.call(d3.axisBottom(legendScale).tickSizeOuter(0));

svg
.append("g")
.attr("id", "legend")
.selectAll("rect")
.data(colorScale.range())
.join("rect")
.attr("class", "legend-item")
.attr("width", legendRectWidth)
.attr("height", legendRectHeight)
.style("fill", (d) => d)
.attr(
"transform",
(d, i) =>
`translate(${
chartWidth / 2 - legendScale.range()[1] / 2 + legendRectWidth * i
},${chartHeight + margin.bottom / 2})`
);

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