Public
Edited
Mar 28, 2023
Insert cell
Insert cell
Treemap(data_2, {
value: (d) => d.size, // size of each node (file); null for internal nodes (folders)
group: (d, n) => n.ancestors().slice(-2)[0].data.name, // e.g., "animate" in flare/animate/Easing; color
label: (d, n) =>
[...d.name.split(/(?=[A-Z][a-z])/g), n.value.toLocaleString("en")].join(
"\n"
),
title: (d, n) =>
`${n
.ancestors()
.reverse()
.map((d) => d.data.name)
.join(".")}\n${n.value.toLocaleString("en")}`,
width: 1000,
height: 400,
padding: 0
})
Insert cell
flareJson
Insert cell
data_2 = {
return {
name: "data",
children: [
{
name: "yes minority racial or ethnic group",
children: [
{
name: "yes experienced discrimination",
children: [
{ name: "yes racial harassment", size: 10 },
{ name: "no racial harassment", size: 9 }
]
},
{ name: "no experienced discrimination", size: 32 }
]
},
{
name: "no minority racial or ethnic group",
children: [
{
name: "yes experienced discrimination",
children: [
{ name: "yes racial harassment", size: 4 },
{ name: "no racial harassment", size: 25 }
]
},
{ name: "no experienced discrimination", size: 141 }
]
},
{ name: "prefer not to say minority racial or ethnic group", size: 5 }
]
};
}
Insert cell
data = {
return {
name: "data",
children: [
{
name: "yes minority racial or ethnic group",
children: [
{
name: "yes experienced discrimination",
children: [
{ name: "yes racial harassment", size: 10 },
{ name: "no racial harassment", size: 9 }
]
},
{ name: "no experienced discrimination", size: 32 },
{ name: "prefer not to say experienced discrimination", size: 1 }
]
},
{
name: "no minority racial or ethnic group",
children: [
{
name: "yes experienced discrimination",
children: [
{ name: "yes racial harassment", size: 4 },
{ name: "no racial harassment", size: 25 }
]
},
{ name: "no experienced discrimination", size: 141 },
{ name: "prefer not to say experienced discrimination", size: 2 }
]
},
{ name: "prefer not to say minority racial or ethnic group", size: 2 }
]
};
}
Insert cell
{
let flare = FileAttachment("flare.json").json();

let sort = (a, b) => d3.descending(a.value, b.value);

let root = d3.hierarchy(flareJson).count().sort(sort);

let value = (d) => d.size;

root.sum((d) => Math.max(0, value(d)));

// Prior to sorting, if a group channel is specified, construct an ordinal color scale.
let leaves = root.leaves();
let G = leaves.map((d) => group(d.data, d));

let zDomain = new d3.InternSet(G);
let color = d3.scaleOrdinal(zDomain, d3.schemeTableau10);

// Compute labels and titles.
let title = (d, n) =>
`${n
.ancestors()
.reverse()
.map((d) => d.data.name)
.join(".")}\n${n.data.size.toLocaleString("en")}`;
let L = leaves.map((d) => label(d.data, d));
let T = leaves.map((d) => title(d.data, d));

return root;
}
Insert cell
flareJson = FileAttachment("flare.json").json()
Insert cell
flareJson
Insert cell
sort = (a, b) => d3.descending(a.value, b.value)
Insert cell
root = d3.hierarchy(flareJson).count().sort(sort)
Insert cell
value = (d) => d.size
Insert cell
root.sum((d) => Math.max(0, value(d)))
Insert cell
leaves = root.leaves()
Insert cell
group = (d, n) => n.ancestors().slice(-2)[0].data.name
Insert cell
G = leaves.map((d) => group(d.data, d))
Insert cell
zDomain = new d3.InternSet(G)
Insert cell
color = d3.scaleOrdinal(zDomain, d3.schemeTableau10)
Insert cell
// Compute labels and titles.
label = (d, n) =>
[...d.name.split(/(?=[A-Z][a-z])/g), n.data.size.toLocaleString("en")].join(
"\n"
)
Insert cell
L = leaves.map((d) => label(d.data, d))
Insert cell
title = (d, n) =>
`${n
.ancestors()
.reverse()
.map((d) => d.data.name)
.join(".")}\n${n.data.size.toLocaleString("en")}`
Insert cell
T = leaves.map((d) => title(d.data, d))
Insert cell
width = 640
Insert cell
height = 400
Insert cell
marginTop = 0
Insert cell
marginRight = 0
Insert cell
marginBottom = 0
Insert cell
marginLeft = 0
Insert cell
padding = 1
Insert cell
paddingInner = 1
Insert cell
paddingOuter = 1
Insert cell
paddingTop = 1
Insert cell
paddingRight = 1
Insert cell
paddingBottom = 1
Insert cell
paddingLeft = 1
Insert cell
tile = d3.treemapBinary
Insert cell
round = true
Insert cell
fillOpacity = 0.6
Insert cell
d3
.treemap()
.tile(tile)
.size([width - marginLeft - marginRight, height - marginTop - marginBottom])
.paddingInner(paddingInner)
.paddingTop(paddingTop)
.paddingRight(paddingRight)
.paddingBottom(paddingBottom)
.paddingLeft(paddingLeft)
.round(round)(root)
Insert cell
{
const svg = d3
.create("svg")
.attr("viewBox", [-marginLeft, -marginTop, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "sans-serif")
.attr("font-size", 10);

const node = svg
.selectAll("rect")
.data(leaves)
.join("rect")
.attr("fill", (d, i) => color(G[i]))
.attr("fill-opacity", fillOpacity)
.attr("width", (d) => d.x1 - d.x0)
.attr("height", (d) => d.y1 - d.y0);

return svg.node();
}
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/treemap
function Treemap(
data,
{
// data is either tabular (array of objects) or hierarchy (nested objects)
path, // as an alternative to id and parentId, returns an array identifier, imputing internal nodes
id = Array.isArray(data) ? (d) => d.id : null, // if tabular data, given a d in data, returns a unique identifier (string)
parentId = Array.isArray(data) ? (d) => d.parentId : null, // if tabular data, given a node d, returns its parent’s identifier
children, // if hierarchical data, given a d in data, returns its children
value, // given a node d, returns a quantitative value (for area encoding; null for count)
sort = (a, b) => d3.descending(a.value, b.value), // how to sort nodes prior to layout
label, // given a leaf node d, returns the name to display on the rectangle
group, // given a leaf node d, returns a categorical value (for color encoding)
title, // given a leaf node d, returns its hover text
link, // given a leaf node d, its link (if any)
linkTarget = "_blank", // the target attribute for links (if any)
tile = d3.treemapBinary, // treemap strategy
width = 640, // outer width, in pixels
height = 400, // outer height, in pixels
margin = 0, // shorthand for margins
marginTop = margin, // top margin, in pixels
marginRight = margin, // right margin, in pixels
marginBottom = margin, // bottom margin, in pixels
marginLeft = margin, // left margin, in pixels
padding = 1, // shorthand for inner and outer padding
paddingInner = padding, // to separate a node from its adjacent siblings
paddingOuter = padding, // shorthand for top, right, bottom, and left padding
paddingTop = paddingOuter, // to separate a node’s top edge from its children
paddingRight = paddingOuter, // to separate a node’s right edge from its children
paddingBottom = paddingOuter, // to separate a node’s bottom edge from its children
paddingLeft = paddingOuter, // to separate a node’s left edge from its children
round = true, // whether to round to exact pixels
colors = d3.schemeTableau10, // array of colors
zDomain, // array of values for the color scale
fill = "#ccc", // fill for node rects (if no group color encoding)
fillOpacity = group == null ? null : 0.6, // fill opacity for node rects
stroke, // stroke for node rects
strokeWidth, // stroke width for node rects
strokeOpacity, // stroke opacity for node rects
strokeLinejoin // stroke line join for node rects
} = {}
) {
// If id and parentId options are specified, or the path option, use d3.stratify
// to convert tabular data to a hierarchy; otherwise we assume that the data is
// specified as an object {children} with nested objects (a.k.a. the “flare.json”
// format), and use d3.hierarchy.
const root =
path != null
? d3.stratify().path(path)(data)
: id != null || parentId != null
? d3.stratify().id(id).parentId(parentId)(data)
: d3.hierarchy(data, children);


// Compute the values of internal nodes by aggregating from the leaves.
value == null ? root.count() : root.sum((d) => Math.max(0, value(d)));

// Prior to sorting, if a group channel is specified, construct an ordinal color scale.
const leaves = root.leaves();
const G = group == null ? null : leaves.map((d) => group(d.data, d));
if (zDomain === undefined) zDomain = G;
zDomain = new d3.InternSet(zDomain);
const color = group == null ? null : d3.scaleOrdinal(zDomain, colors);

// Compute labels and titles.
const L = label == null ? null : leaves.map((d) => label(d.data, d));
const T =
title === undefined
? L
: title == null
? null
: leaves.map((d) => title(d.data, d));

// Sort the leaves (typically by descending value for a pleasing layout).
if (sort != null) root.sort(sort);

// Compute the treemap layout.
d3
.treemap()
.tile(tile)
.size([width - marginLeft - marginRight, height - marginTop - marginBottom])
.paddingInner(paddingInner)
.paddingTop(paddingTop)
.paddingRight(paddingRight)
.paddingBottom(paddingBottom)
.paddingLeft(paddingLeft)
.round(round)(root);

const svg = d3
.create("svg")
.attr("viewBox", [-marginLeft, -marginTop, width, height])
.attr("width", width)
.attr("height", height)
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
.attr("font-family", "sans-serif")
.attr("font-size", 10);

const node = svg
.selectAll("a")
.data(leaves)
.join("a")
.attr("xlink:href", link == null ? null : (d, i) => link(d.data, d))
.attr("target", link == null ? null : linkTarget)
.attr("transform", (d) => `translate(${d.x0},${d.y0})`);

node
.append("rect")
.attr("fill", color ? (d, i) => color(G[i]) : fill)
.attr("fill-opacity", fillOpacity)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("stroke-opacity", strokeOpacity)
.attr("stroke-linejoin", strokeLinejoin)
.attr("width", (d) => d.x1 - d.x0)
.attr("height", (d) => d.y1 - d.y0);

if (T) {
node.append("title").text((d, i) => T[i]);
}

if (L) {
// A unique identifier for clip paths (to avoid conflicts).
const uid = `O-${Math.random().toString(16).slice(2)}`;

node
.append("clipPath")
.attr("id", (d, i) => `${uid}-clip-${i}`)
.append("rect")
.attr("width", (d) => d.x1 - d.x0)
.attr("height", (d) => d.y1 - d.y0);

node
.append("text")
.attr(
"clip-path",
(d, i) => `url(${new URL(`#${uid}-clip-${i}`, location)})`
)
.selectAll("tspan")
.data((d, i) => `${L[i]}`.split(/\n/g))
.join("tspan")
.attr("x", 3)
.attr("y", (d, i, D) => `${(i === D.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
.attr("fill-opacity", (d, i, D) => (i === D.length - 1 ? 0.7 : null))
.text((d) => d);
}

return Object.assign(svg.node(), { scales: { color } });
}
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