Skip to content
Draft

treemap #1224

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export * from "./marks/text.js";
export * from "./marks/tick.js";
export * from "./marks/tip.js";
export * from "./marks/tree.js";
export * from "./marks/treemap.js";
export * from "./marks/vector.js";
export * from "./options.js";
export * from "./plot.js";
Expand All @@ -53,4 +54,5 @@ export * from "./transforms/normalize.js";
export * from "./transforms/select.js";
export * from "./transforms/stack.js";
export * from "./transforms/tree.js";
export * from "./transforms/treemap.js";
export * from "./transforms/window.js";
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export {Text, text, textX, textY} from "./marks/text.js";
export {TickX, TickY, tickX, tickY} from "./marks/tick.js";
export {Tip, tip} from "./marks/tip.js";
export {tree, cluster} from "./marks/tree.js";
export {treemap} from "./marks/treemap.js";
export {Vector, vector, vectorX, vectorY, spike} from "./marks/vector.js";
export {valueof, column, identity, indexOf} from "./options.js";
export {filter, reverse, sort, shuffle, basic as transform, initializer} from "./transforms/basic.js";
Expand All @@ -41,6 +42,7 @@ export {window, windowX, windowY} from "./transforms/window.js";
export {select, selectFirst, selectLast, selectMaxX, selectMaxY, selectMinX, selectMinY} from "./transforms/select.js";
export {stackX, stackX1, stackX2, stackY, stackY1, stackY2} from "./transforms/stack.js";
export {treeNode, treeLink} from "./transforms/tree.js";
export {treemapNode} from "./transforms/treemap.js";
export {pointer, pointerX, pointerY} from "./interactions/pointer.js";
export {formatIsoDate, formatWeekday, formatMonth} from "./format.js";
export {scale} from "./scales.js";
Expand Down
11 changes: 11 additions & 0 deletions src/marks/treemap.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {ChannelValue} from "../channel.js";
import {Data} from "../mark.js";
import {TreeTransformOptions} from "../transforms/tree.js";
import {Rect, RectOptions} from "./rect.js";

// TODO tree channels, e.g., "node:name" | "node:path" | "node:internal"?
export interface TreemapOptions extends RectOptions, TreeTransformOptions {
value?: ChannelValue;
}

export function treemap(data?: Data, options?: TreemapOptions): Rect;
10 changes: 10 additions & 0 deletions src/marks/treemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {treemapNode} from "../transforms/treemap.js";
import {rect} from "./rect.js";

/** @jsdoc treemap */
export function treemap(
data,
{inset = 0.5, insetTop = inset, insetRight = inset, insetBottom = inset, insetLeft = inset, ...options} = {}
) {
return rect(data, treemapNode({insetTop, insetRight, insetBottom, insetLeft, ...options}));
}
14 changes: 7 additions & 7 deletions src/transforms/tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ const treeAnchorRight = {
}
};

function maybeTreeSort(sort) {
export function maybeTreeSort(sort) {
return sort == null || typeof sort === "function"
? sort
: `${sort}`.trim().toLowerCase().startsWith("node:")
Expand All @@ -166,7 +166,7 @@ function nodeData(field) {
return (node) => node.data?.[field];
}

function normalizer(delimiter = "/") {
export function normalizer(delimiter = "/") {
return `${delimiter}` === "/"
? (P) => P // paths are already slash-separated
: (P) => P.map(replaceAll(delimiter, "/")); // TODO string.replaceAll when supported
Expand All @@ -189,7 +189,7 @@ function isLinkValue(option) {
return isObject(option) && typeof option.link === "function";
}

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

function treeOutputs(options, maybeTreeValue) {
export function treeOutputs(options, maybeTreeValue) {
const outputs = [];
for (const name in options) {
const value = options[name];
Expand Down
4 changes: 4 additions & 0 deletions src/transforms/treemap.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {Transformed} from "./basic.js";
import {TreeTransformOptions} from "./tree.js";

export function treemapNode<T>(options?: T & TreeTransformOptions): Transformed<T>;
70 changes: 70 additions & 0 deletions src/transforms/treemap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {stratify, treemap, treemapBinary} from "d3";
import {column, identity, valueof} from "../options.js";
import {basic} from "./basic.js";
import {maybeNodeValue, maybeTreeSort, normalizer} from "./tree.js";
import {output_evaluate, output_setValues, output_values, treeOutputs} from "./tree.js";

/** @jsdoc treeNode */
export function treemapNode(options = {}) {
let {
path = identity, // the delimited path
delimiter, // how the path is separated
frameAnchor,
value,
treeTile = treemapBinary,
treeSort,
...remainingOptions
} = options;
treeSort = maybeTreeSort(treeSort);
value = value == null ? null : maybeNodeValue(value) ?? asDataValue(value);
const normalize = normalizer(delimiter);
const outputs = treeOutputs(remainingOptions, maybeNodeValue);
const [X1, setX1] = column();
const [Y1, setY1] = column();
const [X2, setX2] = column();
const [Y2, setY2] = column();
return {
x1: X1,
y1: Y1,
x2: X2,
y2: Y2,
frameAnchor,
...basic(remainingOptions, (data, facets) => {
const P = normalize(valueof(data, path));
const X1 = setX1([]);
const Y1 = setY1([]);
const X2 = setX2([]);
const Y2 = setY2([]);
let treeIndex = -1;
const treeData = [];
const treeFacets = [];
const rootof = stratify().path((i) => P[i]);
const layout = treemap().tile(treeTile);
for (const o of outputs) o[output_values] = o[output_setValues]([]);
for (const facet of facets) {
const treeFacet = [];
const root = rootof(facet.filter((i) => P[i] != null)).each((node) => (node.data = data[node.data]));
if (value) root.sum(value);
else root.count();
if (treeSort != null) root.sort(treeSort);
layout(root);
for (const node of root.leaves()) {
treeFacet.push(++treeIndex);
treeData[treeIndex] = node.data;
X1[treeIndex] = node.x0;
Y1[treeIndex] = node.y0;
X2[treeIndex] = node.x1;
Y2[treeIndex] = node.y1;
for (const o of outputs) o[output_values][treeIndex] = o[output_evaluate](node);
}
treeFacets.push(treeFacet);
}
return {data: treeData, facets: treeFacets};
}),
...Object.fromEntries(outputs)
};
}

function asDataValue(value) {
return typeof value === "function" ? value : (node) => node[value];
}
Loading