Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

documentation for the tree mark and the treeLink, treeNode transforms #1351

Merged
merged 3 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 22 additions & 0 deletions src/marks/tree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can’t really explain why, but I think grammatically this should read as “whether to represent the node with a dot” instead of “should the node be represented by a dot”. “Should” can imply correctness or appropriateness, but we’re not really making a judgment about what is correct or appropriate here, just honoring the user’s intention. (I will fix.)

*/
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs wrapping. (I will fix.)

*
* 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing documentation. (I have fixed.)

92 changes: 92 additions & 0 deletions src/transforms/tree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These options could use a summary sentence prior to diving into the possible ways in which the options could be specified. For example:

  • treeAnchor - how to orient the tree
  • treeLayout - how to position nodes in the tree
  • treeSeparation - how much space to reserve between adjacent nodes
  • treeSort - how to order nodes prior to computing the layout

etc.

* 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**,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation should have one space, not two. (I will fix.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I don’t think we need to repeat the tree options here. They should be clear from autocomplete (or jumping to the TreeTransformOptions interface).

* **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<T>(options?: T & TreeTransformOptions): Transformed<T>;

/**
* 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<T>(options?: T & TreeTransformOptions): Transformed<T>;
45 changes: 45 additions & 0 deletions test/output/greekGodsExplicit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions test/plots/greek-gods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: " "}))
]
});
}