Public
Edited
Sep 17, 2023
Importers
Insert cell
Insert cell
data = FileAttachment("sp500-components-2023-09-10.json").json()
Insert cell
import {treemapOptions} from "@chiahsun-ws/treemap"
Insert cell
import { interpolateSequentialOptions as schemeOptions } from "@chiahsun-ws/d3-functions"
Insert cell
viewof treemapSelected = Inputs.select(treemapOptions, {value: treemapOptions[5], label: "Treemap types"})
Insert cell
viewof schemeSelected = Inputs.select(schemeOptions, {value: schemeOptions[18], label: "Scheme colors"})
Insert cell
treemapType = treemapSelected
Insert cell
schemeColor = schemeSelected
Insert cell
nameColumn = 'name'
Insert cell
valueColumn = 'value'
Insert cell
width = 1500
Insert cell
height = 1060
Insert cell
Insert cell
chart = {
const margin = {top: 10, right: 10, bottom: 10, left: 10};

const svg_width = width + margin.left + margin.right;;
const svg_height = height + margin.top + margin.bottom;

const svg = d3.create("svg")
.attr("width", svg_width)
.attr("height", svg_height)
.attr("viewBox", [0, 0, svg_width, svg_height])
.attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");

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

const x = d3.scaleLinear().rangeRound([0, width]);
const y = d3.scaleLinear().rangeRound([0, height]);

const color = d3.scaleSequential([8, 0], d3[schemeColor]);

const format = d3.format(",d");

function tile(node, x0, y0, x1, y1) {
d3[treemapType](node, 0, 0, width, height);
for (const child of node.children) {
child.x0 = x0 + child.x0 / width * (x1 - x0);
child.x1 = x0 + child.x1 / width * (x1 - x0);
child.y0 = y0 + child.y0 / height * (y1 - y0);
child.y1 = y0 + child.y1 / height * (y1 - y0);
}
}
const treemap = data => d3.treemap()
.tile(tile)
.size([width, height])
.paddingOuter(3)
.paddingTop(21)
(d3.hierarchy(data)
.sum(d => d[valueColumn])
.sort((a, b) => b[valueColumn] - a[valueColumn]));

const treeroot = treemap(data);

x.domain([treeroot.x0, treeroot.x1]);
y.domain([treeroot.y0, treeroot.y1]);

let chartGroup = chart.append("g").call(render, treeroot); // Need to use `let` here!!!

function render(group, root) {
const node = group
.selectAll("g")
.data(d3.group(root, d => d.height))
.join("g")
.selectAll("g")
.data(d => d[1])
.join("g");

node.append("rect")
.attr("fill", d => color(d.height));

node.append("text")
.selectAll("tspan")
.data(d => d.data[nameColumn].split(/(?=[A-Z][^A-Z])/g).concat(format(d[valueColumn])))
.join("tspan")
.text(d => d);

node.filter(d => d.children).selectAll("tspan")
.attr("dx", 3)
.attr("y", 13)
.style("font-size", (d, i, nodes) => `${(i === nodes.length-1) ? 1 : 1.3}em`)
.style("opacity", (d, i, nodes) => `${(i === nodes.length-1) ? 0.7 : 1}`);

node.filter(d => !d.children).selectAll("tspan")
.attr("x", 3)
.attr("y", (d, i, nodes) => `${(i === nodes.length-1) * 0.5 + 0.9 * i + 1.1}em`)
.style("font-size", (d, i, nodes) => `${(i === nodes.length-1) ? 1 : 1.1}em`)
.style("opacity", (d, i, nodes) => `${(i === nodes.length-1) ? 0.7 : 1}`);

node.filter(d => d === root ? d.parent : d.children)
.attr("cursor", "pointer")
.on("click", (event, d) => d === root ? zoomOut(d) : zoomIn(d));

group.call(position, root);
}

function position(group, root) {
group.selectAll("g")
.selectAll("g")
.attr("transform", d => `translate(${x(d.x0)}, ${y(d.y0)})`)
.select("rect")
.attr("width", d => x(d.x1) - x(d.x0))
.attr("height", d => y(d.y1) - y(d.y0));
}

function zoomIn(d) {
const group0 = chartGroup.attr("pointer-events", "none");
const group1 = chartGroup = chart.append("g").call(render, d);
x.domain([d.x0, d.x1]);
y.domain([d.y0, d.y1]);

chart.transition()
.duration(750)
.call(t => group0.transition(t)
.attrTween("opacity", () => d3.interpolate(1, 0))
.call(position, d.parent)
.remove()
)
.call(t => group1.transition(t)
.attrTween("opacity", () => d3.interpolate(0, 1))
.call(position, d)
);
}

function zoomOut(d) {
const group0 = chartGroup.attr("pointer-events", "none");
const group1 = chartGroup = chart.append("g").call(render, d.parent);
x.domain([d.parent.x0, d.parent.x1]);
y.domain([d.parent.y0, d.parent.y1]);

chart.transition()
.duration(750)
.call(t => group0.transition(t)
.attrTween("opacity", () => d3.interpolate(1, 0))
.call(position, d)
.remove()
)
.call(t => group1.transition(t)
.attrTween("opacity", () => d3.interpolate(0, 1))
.call(position, d.parent)
);
}
return svg.node();
}
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