Skip to content

Commit 4fe4e91

Browse files
committed
treemap
1 parent 640e3f9 commit 4fe4e91

File tree

11 files changed

+705
-7
lines changed

11 files changed

+705
-7
lines changed

src/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export * from "./marks/rule.js";
3232
export * from "./marks/text.js";
3333
export * from "./marks/tick.js";
3434
export * from "./marks/tree.js";
35+
export * from "./marks/treemap.js";
3536
export * from "./marks/vector.js";
3637
export * from "./options.js";
3738
export * from "./plot.js";
@@ -50,4 +51,5 @@ export * from "./transforms/normalize.js";
5051
export * from "./transforms/select.js";
5152
export * from "./transforms/stack.js";
5253
export * from "./transforms/tree.js";
54+
export * from "./transforms/treemap.js";
5355
export * from "./transforms/window.js";

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export {RuleX, RuleY, ruleX, ruleY} from "./marks/rule.js";
2525
export {Text, text, textX, textY} from "./marks/text.js";
2626
export {TickX, TickY, tickX, tickY} from "./marks/tick.js";
2727
export {tree, cluster} from "./marks/tree.js";
28+
export {treemap} from "./marks/treemap.js";
2829
export {Vector, vector, vectorX, vectorY, spike} from "./marks/vector.js";
2930
export {valueof, column, identity} from "./options.js";
3031
export {filter, reverse, sort, shuffle, basic as transform, initializer} from "./transforms/basic.js";
@@ -39,6 +40,7 @@ export {window, windowX, windowY} from "./transforms/window.js";
3940
export {select, selectFirst, selectLast, selectMaxX, selectMaxY, selectMinX, selectMinY} from "./transforms/select.js";
4041
export {stackX, stackX1, stackX2, stackY, stackY1, stackY2} from "./transforms/stack.js";
4142
export {treeNode, treeLink} from "./transforms/tree.js";
43+
export {treemapNode} from "./transforms/treemap.js";
4244
export {formatIsoDate, formatWeekday, formatMonth} from "./format.js";
4345
export {scale} from "./scales.js";
4446
export {legend} from "./legends.js";

src/marks/treemap.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {ChannelValue} from "../channel.js";
2+
import {Data} from "../mark.js";
3+
import {TreeTransformOptions} from "../transforms/tree.js";
4+
import {Rect, RectOptions} from "./rect.js";
5+
6+
// TODO tree channels, e.g., "node:name" | "node:path" | "node:internal"?
7+
export interface TreemapOptions extends RectOptions, TreeTransformOptions {
8+
value?: ChannelValue;
9+
}
10+
11+
export function treemap(data?: Data, options?: TreemapOptions): Rect;

src/marks/treemap.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {treemapNode} from "../transforms/treemap.js";
2+
import {rect} from "./rect.js";
3+
4+
/** @jsdoc treemap */
5+
export function treemap(
6+
data,
7+
{inset = 0.5, insetTop = inset, insetRight = inset, insetBottom = inset, insetLeft = inset, ...options} = {}
8+
) {
9+
return rect(data, treemapNode({insetTop, insetRight, insetBottom, insetLeft, ...options}));
10+
}

src/transforms/tree.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ const treeAnchorRight = {
146146
}
147147
};
148148

149-
function maybeTreeSort(sort) {
149+
export function maybeTreeSort(sort) {
150150
return sort == null || typeof sort === "function"
151151
? sort
152152
: `${sort}`.trim().toLowerCase().startsWith("node:")
@@ -162,7 +162,7 @@ function nodeData(field) {
162162
return (node) => node.data?.[field];
163163
}
164164

165-
function normalizer(delimiter = "/") {
165+
export function normalizer(delimiter = "/") {
166166
return `${delimiter}` === "/"
167167
? (P) => P // paths are already slash-separated
168168
: (P) => P.map(replaceAll(delimiter, "/")); // TODO string.replaceAll when supported
@@ -185,7 +185,7 @@ function isLinkValue(option) {
185185
return isObject(option) && typeof option.link === "function";
186186
}
187187

188-
function maybeNodeValue(value) {
188+
export function maybeNodeValue(value) {
189189
if (isNodeValue(value)) return value.node;
190190
value = `${value}`.trim().toLowerCase();
191191
if (!value.startsWith("node:")) return;
@@ -278,11 +278,11 @@ function slash(path, i) {
278278
// These indexes match the array returned by nodeOutputs. The first two elements
279279
// are always the name of the output and its column value definition so that
280280
// the outputs can be passed directly to Object.fromEntries.
281-
const output_setValues = 2;
282-
const output_evaluate = 3;
283-
const output_values = 4;
281+
export const output_setValues = 2;
282+
export const output_evaluate = 3;
283+
export const output_values = 4;
284284

285-
function treeOutputs(options, maybeTreeValue) {
285+
export function treeOutputs(options, maybeTreeValue) {
286286
const outputs = [];
287287
for (const name in options) {
288288
const value = options[name];

src/transforms/treemap.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import {Transformed} from "./basic.js";
2+
import {TreeTransformOptions} from "./tree.js";
3+
4+
export function treemapNode<T>(options?: T & TreeTransformOptions): Transformed<T>;

src/transforms/treemap.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import {stratify, treemap, treemapBinary} from "d3";
2+
import {column, identity, valueof} from "../options.js";
3+
import {basic} from "./basic.js";
4+
import {maybeNodeValue, maybeTreeSort, normalizer} from "./tree.js";
5+
import {output_evaluate, output_setValues, output_values, treeOutputs} from "./tree.js";
6+
7+
/** @jsdoc treeNode */
8+
export function treemapNode(options = {}) {
9+
let {
10+
path = identity, // the delimited path
11+
delimiter, // how the path is separated
12+
frameAnchor,
13+
value,
14+
treeTile = treemapBinary,
15+
treeSort,
16+
...remainingOptions
17+
} = options;
18+
treeSort = maybeTreeSort(treeSort);
19+
value = value == null ? null : maybeNodeValue(value) ?? asDataValue(value);
20+
const normalize = normalizer(delimiter);
21+
const outputs = treeOutputs(remainingOptions, maybeNodeValue);
22+
const [X1, setX1] = column();
23+
const [Y1, setY1] = column();
24+
const [X2, setX2] = column();
25+
const [Y2, setY2] = column();
26+
return {
27+
x1: X1,
28+
y1: Y1,
29+
x2: X2,
30+
y2: Y2,
31+
frameAnchor,
32+
...basic(remainingOptions, (data, facets) => {
33+
const P = normalize(valueof(data, path));
34+
const X1 = setX1([]);
35+
const Y1 = setY1([]);
36+
const X2 = setX2([]);
37+
const Y2 = setY2([]);
38+
let treeIndex = -1;
39+
const treeData = [];
40+
const treeFacets = [];
41+
const rootof = stratify().path((i) => P[i]);
42+
const layout = treemap().tile(treeTile);
43+
for (const o of outputs) o[output_values] = o[output_setValues]([]);
44+
for (const facet of facets) {
45+
const treeFacet = [];
46+
const root = rootof(facet.filter((i) => P[i] != null)).each((node) => (node.data = data[node.data]));
47+
if (value) root.sum(value);
48+
else root.count();
49+
if (treeSort != null) root.sort(treeSort);
50+
layout(root);
51+
for (const node of root.leaves()) {
52+
treeFacet.push(++treeIndex);
53+
treeData[treeIndex] = node.data;
54+
X1[treeIndex] = node.x0;
55+
Y1[treeIndex] = node.y0;
56+
X2[treeIndex] = node.x1;
57+
Y2[treeIndex] = node.y1;
58+
for (const o of outputs) o[output_values][treeIndex] = o[output_evaluate](node);
59+
}
60+
treeFacets.push(treeFacet);
61+
}
62+
return {data: treeData, facets: treeFacets};
63+
}),
64+
...Object.fromEntries(outputs)
65+
};
66+
}
67+
68+
function asDataValue(value) {
69+
return typeof value === "function" ? value : (node) => node[value];
70+
}

0 commit comments

Comments
 (0)