Skip to content

Commit 1a15ead

Browse files
committed
treeFilter option (#1611)
1 parent cb6d92e commit 1a15ead

File tree

6 files changed

+802
-804
lines changed

6 files changed

+802
-804
lines changed

docs/transforms/tree.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ The treeNode transform will derive output columns for any *options* that have on
8888
* *node:name* - the node’s name (the last part of its path)
8989
* *node:path* - the node’s full, normalized, slash-separated path
9090
* *node:internal* - true if the node is internal, or false for leaves
91+
* *node:external* - true if the node is a leaf, or false for internal nodes
9192
* *node:depth* - the distance from the node to the root
9293
* *node:height* - the distance from the node to its deepest descendant
9394

@@ -102,6 +103,7 @@ The treeLink transform will likewise derive output columns for any *options* tha
102103
* *node:name* - the child node’s name (the last part of its path)
103104
* *node:path* - the child node’s full, normalized, slash-separated path
104105
* *node:internal* - true if the child node is internal, or false for leaves
106+
* *node:external* - true if the child node is a leaf, or false for internal nodes
105107
* *node:depth* - the distance from the child node to the root
106108
* *node:height* - the distance from the child node to its deepest descendant
107109
* *parent:name* - the parent node’s name (the last part of its path)

src/marks/tree.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {DotOptions} from "./dot.js";
44
import type {LinkOptions} from "./link.js";
55
import type {TextOptions} from "./text.js";
66

7-
// TODO tree channels, e.g., "node:name" | "node:path" | "node:internal"?
7+
// TODO tree channels, e.g., "node:name" | "node:path" | "node:internal" | "node:external"?
88

99
/** Options for the compound tree mark. */
1010
export interface TreeOptions extends DotOptions, LinkOptions, TextOptions, TreeTransformOptions {

src/marks/tree.js

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import {cluster as Cluster} from "d3";
2-
import {isNoneish} from "../options.js";
1+
import {tree as Tree, cluster as Cluster} from "d3";
32
import {marks} from "../mark.js";
3+
import {isNoneish} from "../options.js";
44
import {maybeTreeAnchor, treeLink, treeNode} from "../transforms/tree.js";
5-
import {filter} from "../transforms/basic.js";
65
import {dot} from "./dot.js";
76
import {link} from "./link.js";
87
import {text} from "./text.js";
@@ -32,8 +31,26 @@ export function tree(
3231
...options
3332
} = {}
3433
) {
34+
const {treeLayout = Tree} = options;
3535
if (dx === undefined) dx = maybeTreeAnchor(options.treeAnchor).dx;
3636
if (textAnchor !== undefined) throw new Error("textAnchor is not a configurable tree option");
37+
38+
function treeText(textOptions) {
39+
return text(
40+
data,
41+
treeNode({
42+
text: textText,
43+
fill: fill === undefined ? "currentColor" : fill,
44+
stroke: textStroke,
45+
dx,
46+
dy,
47+
title,
48+
...textOptions,
49+
...options
50+
})
51+
);
52+
}
53+
3754
return marks(
3855
link(
3956
data,
@@ -53,40 +70,12 @@ export function tree(
5370
),
5471
dotDot ? dot(data, treeNode({fill: fill === undefined ? "node:internal" : fill, title, ...options})) : null,
5572
textText != null
56-
? [
57-
text(
58-
data,
59-
filter(
60-
(d) => !d.internal,
61-
treeNode({
62-
text: textText,
63-
fill: fill === undefined ? "currentColor" : fill,
64-
stroke: textStroke,
65-
dx,
66-
dy,
67-
title,
68-
textAnchor: "start",
69-
...options
70-
})
71-
)
72-
),
73-
text(
74-
data,
75-
filter(
76-
"internal",
77-
treeNode({
78-
text: textText,
79-
fill: fill === undefined ? "currentColor" : fill,
80-
stroke: textStroke,
81-
dx: -dx,
82-
dy,
83-
title,
84-
textAnchor: "end",
85-
...options
86-
})
87-
)
88-
)
89-
]
73+
? treeLayout === Tree || treeLayout === Cluster
74+
? [
75+
treeText({textAnchor: "start", treeFilter: "node:external"}),
76+
treeText({textAnchor: "end", treeFilter: "node:internal", dx: -dx})
77+
]
78+
: treeText()
9079
: null
9180
);
9281
}

src/transforms/tree.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export interface TreeTransformOptions {
7575
* * *node:name* - the node’s name (the last part of its path)
7676
* * *node:path* - the node’s full, normalized, slash-separated path
7777
* * *node:internal* - true if the node is internal, or false for leaves
78+
* * *node:external* - true if the node is a leaf, or false for internal nodes
7879
* * *node:depth* - the distance from the node to the root
7980
* * *node:height* - the distance from the node to its deepest descendant
8081
*
@@ -97,6 +98,7 @@ export function treeNode<T>(options?: T & TreeTransformOptions): Transformed<T>;
9798
* * *node:name* - the child node’s name (the last part of its path)
9899
* * *node:path* - the child node’s full, normalized, slash-separated path
99100
* * *node:internal* - true if the child node is internal, or false for leaves
101+
* * *node:external* - true if the child node is a leaf, or false for external nodes
100102
* * *node:depth* - the distance from the child node to the root
101103
* * *node:height* - the distance from the child node to its deepest descendant
102104
* * *parent:name* - the parent node’s name (the last part of its path)

src/transforms/tree.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ export function treeNode({
1111
treeSort,
1212
treeSeparation,
1313
treeAnchor,
14+
treeFilter,
1415
...options
1516
} = {}) {
1617
treeAnchor = maybeTreeAnchor(treeAnchor);
1718
treeSort = maybeTreeSort(treeSort);
19+
if (treeFilter != null) treeFilter = maybeNodeValue(treeFilter);
1820
if (frameAnchor === undefined) frameAnchor = treeAnchor.frameAnchor;
1921
const normalize = normalizer(delimiter);
2022
const outputs = treeOutputs(options, maybeNodeValue);
@@ -42,15 +44,9 @@ export function treeNode({
4244
if (treeSort != null) root.sort(treeSort);
4345
layout(root);
4446
for (const node of root.descendants()) {
47+
if (treeFilter != null && !treeFilter(node)) continue;
4548
treeFacet.push(++treeIndex);
46-
treeData[treeIndex] = {
47-
data: node.data,
48-
name: nodeName(node),
49-
path: nodePath(node),
50-
internal: nodeInternal(node),
51-
depth: node.depth, // nodeDepth(node)
52-
height: node.height // nodeHeight(node)
53-
};
49+
treeData[treeIndex] = node.data;
5450
treeAnchor.position(node, treeIndex, X, Y);
5551
for (const o of outputs) o[output_values][treeIndex] = o[output_evaluate](node);
5652
}
@@ -73,10 +69,12 @@ export function treeLink({
7369
treeSort,
7470
treeSeparation,
7571
treeAnchor,
72+
treeFilter,
7673
...options
7774
} = {}) {
7875
treeAnchor = maybeTreeAnchor(treeAnchor);
7976
treeSort = maybeTreeSort(treeSort);
77+
if (treeFilter != null) treeFilter = maybeLinkValue(treeFilter);
8078
options = {curve, stroke, strokeWidth, strokeOpacity, ...options};
8179
const normalize = normalizer(delimiter);
8280
const outputs = treeOutputs(options, maybeLinkValue);
@@ -109,6 +107,7 @@ export function treeLink({
109107
if (treeSort != null) root.sort(treeSort);
110108
layout(root);
111109
for (const {source, target} of root.links()) {
110+
if (treeFilter != null && !treeFilter(target, source)) continue;
112111
treeFacet.push(++treeIndex);
113112
treeData[treeIndex] = target.data;
114113
treeAnchor.position(source, treeIndex, X1, Y1);
@@ -201,6 +200,8 @@ function maybeNodeValue(value) {
201200
return nodePath;
202201
case "node:internal":
203202
return nodeInternal;
203+
case "node:external":
204+
return nodeExternal;
204205
case "node:depth":
205206
return nodeDepth;
206207
case "node:height":
@@ -229,6 +230,8 @@ function maybeLinkValue(value) {
229230
return nodePath;
230231
case "node:internal":
231232
return nodeInternal;
233+
case "node:external":
234+
return nodeExternal;
232235
case "node:depth":
233236
return nodeDepth;
234237
case "node:height":
@@ -257,6 +260,10 @@ function nodeInternal(node) {
257260
return !!node.children;
258261
}
259262

263+
function nodeExternal(node) {
264+
return !node.children;
265+
}
266+
260267
function parentValue(evaluate) {
261268
return (child, parent) => (parent == null ? undefined : evaluate(parent));
262269
}

0 commit comments

Comments
 (0)