From 7d7a23a556a3ffa4b9cf4697e8a1aeeee85b910a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Fri, 17 Mar 2023 12:14:24 +0100 Subject: [PATCH 1/3] documentation for the tree mark and the treeLink, treeNode transforms --- src/marks/tree.d.ts | 22 ++++++++ src/transforms/tree.d.ts | 92 +++++++++++++++++++++++++++++++ test/output/greekGodsExplicit.svg | 45 +++++++++++++++ test/plots/greek-gods.ts | 21 +++++++ 4 files changed, 180 insertions(+) create mode 100644 test/output/greekGodsExplicit.svg diff --git a/src/marks/tree.d.ts b/src/marks/tree.d.ts index b6066ef773..40f86aa247 100644 --- a/src/marks/tree.d.ts +++ b/src/marks/tree.d.ts @@ -6,10 +6,32 @@ import type {TextOptions} from "./text.js"; // TODO tree channels, e.g., "node:name" | "node:path" | "node:internal"? export interface TreeOptions extends DotOptions, LinkOptions, TextOptions, TreeTransformOptions { + /** + * Should the node be represented by a dot. Defaults to true unless a **marker** is specified. + */ dot?: boolean; + /** + * A **stroke** color to pass to the text mark, to limit label occlusion. Defaults to white. + */ textStroke?: MarkOptions["stroke"]; } +/** + * Transforms a tabular dataset into a hierarchy according to the given **path** input channel, which is typically a slash-separated string; then executes a tree layout algorithm to compute **x** and **y** output channels; these channels can then be fed to other marks to construct a node-link diagram. + * + * The following options control how the tabular data is organized into a hierarchy: + * + * * **path** - a channel specifying each node’s hierarchy location; defaults to identity + * * **delimiter** - the path separator; defaults to forward slash (/) + * + * The following options control how the node-link diagram is laid out: + * + * * **treeLayout** - a tree layout algorithm; defaults to [d3.tree](https://github.com/d3/d3-hierarchy/blob/main/README.md#tree) + * * **treeAnchor** - a tree layout orientation, either *left* or *right*; defaults to *left* + * * **treeSort** - a node comparator, or null to preserve input order + * * **treeSeparation** - a node separation function, or null for uniform separation + * + */ export function tree(data?: Data, options?: TreeOptions): CompoundMark; export function cluster(data?: Data, options?: TreeOptions): CompoundMark; diff --git a/src/transforms/tree.d.ts b/src/transforms/tree.d.ts index b2750a76f8..74a35c8c50 100644 --- a/src/transforms/tree.d.ts +++ b/src/transforms/tree.d.ts @@ -2,14 +2,106 @@ import type {ChannelValue} from "../channel.js"; import type {CompareFunction, Transformed} from "./basic.js"; export interface TreeTransformOptions { + /** + * A channel specifying each node’s hierarchy location. The **path** channel + * is typically slash-separated, as with UNIX-based file systems or URLs. + * Defaults to identity. + */ path?: ChannelValue; + /** + * The path separator; defaults to forward slash (/). + */ delimiter?: string; + /** + * 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 if horizontal padding is + * desired, say to make room for labels. + */ treeAnchor?: "left" | "right"; + /** + * 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**. + */ treeLayout?: () => any; + /** + * 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. + */ treeSeparation?: CompareFunction | null; + /** + * 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 such as node:name. + */ treeSort?: CompareFunction | {node: (node: any) => any} | string | null; } +/** + * Based on the **tree** options (**path**, **delimiter**, **treeAnchor**, + * **treeLayout**, **treeSeparation**, and **treeSort**), 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**, **text**, and other + * point-based marks. This transform is rarely used directly; see the **tree** + * compound mark. + * + * The treeNode transform will derive output columns for any *options* that have + * one of the following named node values: + * + * * *node:name* - the node’s name (the last part of its path) + * * *node:path* - the node’s full, normalized, slash-separated path + * * *node:internal* - true if the node is internal, or false for leaves + * * *node:depth* - the distance from the node to the root + * * *node:height* - the distance from the node to its deepest descendant + * + * 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. + */ export function treeNode(options?: T & TreeTransformOptions): Transformed; +/** + * Based on the **tree** options (**path**, **delimiter**, **treeAnchor**, + * **treeLayout**, **treeSeparation**, and **treeSort**), 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**, **arrow**, and + * other two-point-based marks. This transform is rarely used directly; see the + * **tree** compound mark. + * + * The treeLink transform will derive output columns for any *options* that have + * one of the following named link values: + * + * * *node:name* - the child node’s name (the last part of its path) + * * *node:path* - the child node’s full, normalized, slash-separated path + * * *node:internal* - true if the child node is internal, or false for leaves + * * *node:depth* - the distance from the child node to the root + * * *node:height* - the distance from the child node to its deepest descendant + * * *parent:name* - the parent node’s name (the last part of its path) + * * *parent:path* - the parent node’s full, normalized, slash-separated path + * * *parent:depth* - the distance from the parent node to the root + * * *parent:height* - the distance from the parent node to its deepest + * descendant + * + * 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. + */ export function treeLink(options?: T & TreeTransformOptions): Transformed; diff --git a/test/output/greekGodsExplicit.svg b/test/output/greekGodsExplicit.svg new file mode 100644 index 0000000000..8f1adc4849 --- /dev/null +++ b/test/output/greekGodsExplicit.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + Chaos + Eros + Erebus + Tartarus + Gaia + Mountains + Pontus + Uranus + + \ No newline at end of file diff --git a/test/plots/greek-gods.ts b/test/plots/greek-gods.ts index b50c7655f3..ab53de312d 100644 --- a/test/plots/greek-gods.ts +++ b/test/plots/greek-gods.ts @@ -18,3 +18,24 @@ Chaos Tartarus` marks: [Plot.tree(gods)] }); } + +export async function greekGodsExplicit() { + const gods = `Chaos Gaia Mountains +Chaos Gaia Pontus +Chaos Gaia Uranus +Chaos Eros +Chaos Erebus +Chaos Tartarus`.split("\n"); + return Plot.plot({ + axis: null, + insetLeft: 10, + insetTop: 20, + insetBottom: 20, + insetRight: 120, + marks: [ + Plot.link(gods, Plot.treeLink({stroke: "node:internal", delimiter: " "})), + Plot.dot(gods, Plot.treeNode({fill: "node:internal", delimiter: " "})), + Plot.text(gods, Plot.treeNode({text: "node:name", stroke: "white", fill: "currentColor", dx: 6, delimiter: " "})) + ] + }); +} From 6437e3694fb8cf0b4967db77f63dda237e3eb32f Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 20 Mar 2023 10:03:20 -0700 Subject: [PATCH 2/3] tweaks --- src/marks/tree.d.ts | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/marks/tree.d.ts b/src/marks/tree.d.ts index 40f86aa247..4291d1ab48 100644 --- a/src/marks/tree.d.ts +++ b/src/marks/tree.d.ts @@ -7,31 +7,46 @@ import type {TextOptions} from "./text.js"; // TODO tree channels, e.g., "node:name" | "node:path" | "node:internal"? export interface TreeOptions extends DotOptions, LinkOptions, TextOptions, TreeTransformOptions { /** - * Should the node be represented by a dot. Defaults to true unless a **marker** is specified. + * Whether to represent the node with a dot; defaults to true unless a + * **marker** is specified. */ dot?: boolean; /** - * A **stroke** color to pass to the text mark, to limit label occlusion. Defaults to white. + * The **stroke** color for the text mark to improve the legibility of labels + * atop other marks by creating a halo effect; defaults to *white*. */ textStroke?: MarkOptions["stroke"]; } /** - * Transforms a tabular dataset into a hierarchy according to the given **path** input channel, which is typically a slash-separated string; then executes a tree layout algorithm to compute **x** and **y** output channels; these channels can then be fed to other marks to construct a node-link diagram. + * Transforms a tabular dataset into a hierarchy according to the given **path** + * input channel, which typically contains slash-separated strings; then + * executes a tree layout algorithm, by default Reingold–Tilford’s “tidy” + * algorithm, to compute *x* and *y* output channels; these channels can then be + * fed to other marks to construct a node-link diagram. * - * The following options control how the tabular data is organized into a hierarchy: + * These options control how the tabular data is organized into a hierarchy: * * * **path** - a channel specifying each node’s hierarchy location; defaults to identity * * **delimiter** - the path separator; defaults to forward slash (/) * - * The following options control how the node-link diagram is laid out: + * These options control how the node-link diagram is laid out: * * * **treeLayout** - a tree layout algorithm; defaults to [d3.tree](https://github.com/d3/d3-hierarchy/blob/main/README.md#tree) * * **treeAnchor** - a tree layout orientation, either *left* or *right*; defaults to *left* * * **treeSort** - a node comparator, or null to preserve input order * * **treeSeparation** - a node separation function, or null for uniform separation - * */ export function tree(data?: Data, options?: TreeOptions): CompoundMark; +/** + * Shorthand for Plot.tree using + * [d3.cluster](https://github.com/d3/d3-hierarchy/blob/main/README.md#cluster) + * as the **treeLayout** option, placing leaf nodes of the tree at the same + * depth. Equivalent to: + * + * ```js + * Plot.tree(data, {...options, treeLayout: d3.cluster}) + * ``` + */ export function cluster(data?: Data, options?: TreeOptions): CompoundMark; From 3e3f764715c4af05ece2cd8f91bcbe2128dcc9ea Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Mon, 20 Mar 2023 10:19:12 -0700 Subject: [PATCH 3/3] tweaks --- src/transforms/tree.d.ts | 69 ++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/src/transforms/tree.d.ts b/src/transforms/tree.d.ts index 74a35c8c50..8d66912ccf 100644 --- a/src/transforms/tree.d.ts +++ b/src/transforms/tree.d.ts @@ -3,42 +3,50 @@ import type {CompareFunction, Transformed} from "./basic.js"; export interface TreeTransformOptions { /** - * A channel specifying each node’s hierarchy location. The **path** channel - * is typically slash-separated, as with UNIX-based file systems or URLs. - * Defaults to identity. + * The location of each node in the hierarchy; typically slash-separated + * strings, as with UNIX-based file systems or URLs. Defaults to identity, + * assuming the mark’s data are path strings. */ path?: ChannelValue; + /** - * The path separator; defaults to forward slash (/). + * The path separator, used for inferring the hierarchy from the **path** + * channel; defaults to forward slash (/). */ delimiter?: string; + /** - * 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 if horizontal padding is - * desired, say to make room for labels. + * How to orient the tree. 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** *x* scale options if + * horizontal padding is desired, say to make room for labels. */ treeAnchor?: "left" | "right"; + /** - * The default **treeLayout** implements the Reingold–Tilford “tidy” algorithm - * based on Buchheim _et al._’s linear time approach. Use + * How to layout the tree. The default **treeLayout** implements the + * Reingold–Tilford “tidy” algorithm. Use * [d3.cluster](https://github.com/d3/d3-hierarchy/blob/main/README.md#cluster) - * instead to align leaf nodes; see also **Plot.cluster**. + * instead to align leaf nodes; see also Plot.cluster. */ treeLayout?: () => any; + /** - * 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 + * How much space to reserve between adjacent nodes in the layout. 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. */ treeSeparation?: CompareFunction | null; + /** - * 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 + * How to order nodes prior to laying them out. 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) @@ -50,13 +58,10 @@ export interface TreeTransformOptions { } /** - * Based on the **tree** options (**path**, **delimiter**, **treeAnchor**, - * **treeLayout**, **treeSeparation**, and **treeSort**), 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**, **text**, and other - * point-based marks. This transform is rarely used directly; see the **tree** - * compound mark. + * Populates the *x* and *y* channels with the positions for each node, and + * applies a default **frameAnchor** based on the specified **treeAnchor**. This + * transform is intended to be used with dot, text, and other point-based marks. + * This transform is rarely used directly; see the tree mark. * * The treeNode transform will derive output columns for any *options* that have * one of the following named node values: @@ -74,14 +79,11 @@ export interface TreeTransformOptions { export function treeNode(options?: T & TreeTransformOptions): Transformed; /** - * Based on the **tree** options (**path**, **delimiter**, **treeAnchor**, - * **treeLayout**, **treeSeparation**, and **treeSort**), 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**, **arrow**, and - * other two-point-based marks. This transform is rarely used directly; see the - * **tree** compound mark. + * Populates the *x1*, *y1*, *x2*, and *y2* channels, and applies the following + * defaults: **curve** is *bump-x*, **stroke** is #555, **strokeWidth** is 1.5, + * and **strokeOpacity** is 0.5. This transform is intended to be used with + * link, arrow, and other two-point-based marks. This transform is rarely used + * directly; see the tree mark. * * The treeLink transform will derive output columns for any *options* that have * one of the following named link values: @@ -94,8 +96,7 @@ export function treeNode(options?: T & TreeTransformOptions): Transformed; * * *parent:name* - the parent node’s name (the last part of its path) * * *parent:path* - the parent node’s full, normalized, slash-separated path * * *parent:depth* - the distance from the parent node to the root - * * *parent:height* - the distance from the parent node to its deepest - * descendant + * * *parent:height* - the distance from the parent node to its deepest descendant * * 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**