Skip to content

Commit f4401ef

Browse files
committed
named treeSort; document node columns
1 parent 69f4ff2 commit f4401ef

File tree

2 files changed

+47
-6
lines changed

2 files changed

+47
-6
lines changed

README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,19 +2061,43 @@ The following options control how the node-link diagram is laid out:
20612061
20622062
* **treeLayout** - a tree layout algorithm; defaults to [d3.tree](https://github.com/d3/d3-hierarchy/blob/main/README.md#tree)
20632063
* **treeAnchor** - a tree layout orientation, either *left* or *right*; defaults to *left*
2064-
* **treeSort** - a node comparator function, or null to preserve input order
2064+
* **treeSort** - a node comparator, or null to preserve input order
20652065
* **treeSeparation** - a node separation function, or null for uniform separation
20662066
2067-
The default **treeLayout** implements the Reingold–Tilford “tidy” algorithm based on Buchheim _et al._’s linear time approach. Use [d3.cluster](https://github.com/d3/d3-hierarchy/blob/main/README.md#cluster) instead to align leaf nodes; see also [Plot.cluster](#plotclusterdata-options). If the **treeAnchor** is *left*, the root of the tree will be aligned with the left side of the frame; if **treeAnchor** is *right*, the root of the tree will be aligned with the right side of the frame; use the **insetLeft** and **insetRight** [scale options](#scale-options) if horizontal padding is desired, say to make room for labels. If the **treeSort** option is not null, it is a function that is passed two nodes in the hierarchy and compares them, similar to [_array_.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort); see [d3-hierarchy’s _node_.sort](https://github.com/d3/d3-hierarchy/blob/main/README.md#node_sort) for more. If the **treeSeparation** is not null, it is a function that is passed two nodes in the hierarchy and returns the desired (relative) amount of separation; see [d3-hierarchy’s _tree_.separation](https://github.com/d3/d3-hierarchy/blob/main/README.md#tree_separation) for more. By default, non-siblings are at least twice as far apart as siblings.
2067+
The default **treeLayout** implements the Reingold–Tilford “tidy” algorithm based on Buchheim _et al._’s linear time approach. Use [d3.cluster](https://github.com/d3/d3-hierarchy/blob/main/README.md#cluster) instead to align leaf nodes; see also [Plot.cluster](#plotclusterdata-options). If the **treeAnchor** is *left*, the root of the tree will be aligned with the left side of the frame; if **treeAnchor** is *right*, the root of the tree will be aligned with the right side of the frame; use the **insetLeft** and **insetRight** [scale options](#scale-options) if horizontal padding is desired, say to make room for labels. If the **treeSort** option is not null, it is typically a function that is passed two nodes in the hierarchy and compares them, similar to [_array_.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort); see [d3-hierarchy’s _node_.sort](https://github.com/d3/d3-hierarchy/blob/main/README.md#node_sort) for more. The **treeSort** option can also be specified as a string, in which case it refers either to a named column in data, or if it starts with “node:”, a node value (see below). If the **treeSeparation** is not null, it is a function that is passed two nodes in the hierarchy and returns the desired (relative) amount of separation; see [d3-hierarchy’s _tree_.separation](https://github.com/d3/d3-hierarchy/blob/main/README.md#tree_separation) for more. By default, non-siblings are at least twice as far apart as siblings.
20682068
20692069
### Plot.treeNode(*options*)
20702070
20712071
Based on the tree options described above, populates the **x** and **y** channels with the positions for each node. The following defaults are also applied: the default **frameAnchor** inherits the **treeAnchor**. This transform is intended to be used with [dot](#dot), [text](#text), and other point-based marks. This transform is rarely used directly; see the [Plot.tree compound mark](#plottreedata-options).
20722072
2073+
The treeNode transform will derive output columns for any *options* that have one of the following named node values:
2074+
2075+
* *node:name* - the node’s name (the last part of its path)
2076+
* *node:path* - the node’s full path
2077+
* *node:internal* - true if the node is internal, or false for leaves
2078+
* *node:depth* - the distance from the node to the root
2079+
* *node:height* - the distance from the node to its deepest descendant
2080+
2081+
In addition, if any option value is specified as an object with a **node** method, a derived output column will be generated by invoking the **node** method for each node in the tree.
2082+
20732083
### Plot.treeLink(*options*)
20742084
20752085
Based on the tree options described above, populates the **x1**, **y1**, **x2**, and **y2** channels. The following defaults are also applied: the default **curve** is *bump-x*, the default **stroke** is #555, the default **strokeWidth** is 1.5, and the default **strokeOpacity** is 0.5. This transform is intended to be used with [link](#link), [arrow](#arrow), and other two-point-based marks. This transform is rarely used directly; see the [Plot.tree compound mark](#plottreedata-options).
20762086
2087+
The treeLink transform will derive output columns for any *options* that have one of the following named link values:
2088+
2089+
* *node:name* - the child node’s name (the last part of its path)
2090+
* *node:path* - the child node’s full path
2091+
* *node:internal* - true if the child node is internal, or false for leaves
2092+
* *node:depth* - the distance from the child node to the root
2093+
* *node:height* - the distance from the child node to its deepest descendant
2094+
* *parent:name* - the parent node’s name (the last part of its path)
2095+
* *parent:path* - the parent node’s full path
2096+
* *parent:depth* - the distance from the parent node to the root
2097+
* *parent:height* - the distance from the parent node to its deepest descendant
2098+
2099+
In addition, if any option value is specified as an object with a **node** method, a derived output column will be generated by invoking the **node** method for each child node in the tree; likewise if any option value is specified as an object with a **link** method, a derived output column will be generated by invoking the **link** method for each link in the tree, being passed two node arguments, the child and the parent.
2100+
20772101
### Plot.tree(*data*, *options*)
20782102
20792103
A convenience compound mark for rendering a tree diagram, including a [link](#link) to render links from parent to child, an optional [dot](#dot) for nodes, and a [text](#text) for node labels. The link mark uses the [treeLink transform](#plottreelinkoptions), while the dot and text marks use the [treeNode transform](#plottreenodeoptions). The following options are supported:

src/transforms/tree.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {stratify, tree} from "d3";
2+
import {ascendingDefined} from "../defined.js";
23
import {channel, isObject, one, valueof} from "../options.js";
34
import {basic} from "./basic.js";
45

@@ -13,6 +14,7 @@ export function treeNode({
1314
...options
1415
} = {}) {
1516
treeAnchor = maybeTreeAnchor(treeAnchor);
17+
treeSort = maybeTreeSort(treeSort);
1618
if (frameAnchor === undefined) frameAnchor = treeAnchor.frameAnchor;
1719
const normalize = normalizer(delimiter);
1820
const outputs = treeOutputs(options, maybeNodeValue);
@@ -36,13 +38,13 @@ export function treeNode({
3638
for (const o of outputs) o[output_values] = o[output_setValues]([]);
3739
for (const facet of facets) {
3840
const treeFacet = [];
39-
const root = rootof(facet);
41+
const root = rootof(facet).each(node => node.data = data[node.data]);
4042
if (treeSort != null) root.sort(treeSort);
4143
layout(root);
4244
for (const node of root.descendants()) {
4345
treeFacet.push(++treeIndex);
46+
treeData[treeIndex] = node.data;
4447
treeAnchor.position(node, treeIndex, X, Y);
45-
if (node.data !== undefined) treeData[treeIndex] = data[node.data];
4648
for (const o of outputs) o[output_values][treeIndex] = o[output_evaluate](node);
4749
}
4850
treeFacets.push(treeFacet);
@@ -67,6 +69,7 @@ export function treeLink({
6769
...options
6870
} = {}) {
6971
treeAnchor = maybeTreeAnchor(treeAnchor);
72+
treeSort = maybeTreeSort(treeSort);
7073
options = {curve, stroke, strokeWidth, strokeOpacity, ...options};
7174
const normalize = normalizer(delimiter);
7275
const outputs = treeOutputs(options, maybeLinkValue);
@@ -95,14 +98,14 @@ export function treeLink({
9598
for (const o of outputs) o[output_values] = o[output_setValues]([]);
9699
for (const facet of facets) {
97100
const treeFacet = [];
98-
const root = rootof(facet);
101+
const root = rootof(facet).each(node => node.data = data[node.data]);
99102
if (treeSort != null) root.sort(treeSort);
100103
layout(root);
101104
for (const {source, target} of root.links()) {
102105
treeFacet.push(++treeIndex);
106+
treeData[treeIndex] = target.data;
103107
treeAnchor.position(source, treeIndex, X1, Y1);
104108
treeAnchor.position(target, treeIndex, X2, Y2);
105-
if (target.data !== undefined) treeData[treeIndex] = data[target.data];
106109
for (const o of outputs) o[output_values][treeIndex] = o[output_evaluate](target, source);
107110
}
108111
treeFacets.push(treeFacet);
@@ -139,6 +142,20 @@ const treeAnchorRight = {
139142
}
140143
};
141144

145+
function maybeTreeSort(sort) {
146+
return sort == null || typeof sort === "function" ? sort
147+
: `${sort}`.trim().toLowerCase().startsWith("node:") ? nodeSort(maybeNodeValue(sort))
148+
: nodeSort(nodeData(sort));
149+
}
150+
151+
function nodeSort(value) {
152+
return (a, b) => ascendingDefined(value(a), value(b));
153+
}
154+
155+
function nodeData(field) {
156+
return node => node.data?.[field];
157+
}
158+
142159
function normalizer(delimiter = "/") {
143160
return `${delimiter}` === "/"
144161
? P => P // paths are already slash-separated

0 commit comments

Comments
 (0)