d3-dag
Version:
Layout algorithms for visualizing directed acylic graphs.
364 lines (363 loc) • 15.2 kB
TypeScript
/**
* a way to enforce specific rankings of nodes
*
* A rank accessor assigns a numeric rank to some nodes. In general, nodes with
* a lower rank come *before* nodes with a higher rank. Layering operators that
* take a rank accessor additionally guarantee that nodes with the same rank
* will share the same layer (if possible).
*
* @example
*
* This example demonstrates passing through a rank value that might exist on a
* node.
*
* ```ts
* function memberRank({ data }: GraphNode<{ rank?: number }>): number | undefined {
* return data.rank;
* }
* ```
*/
export interface Rank<in NodeDatum = never, in LinkDatum = never> {
/**
* compute the rank of a node
*
* @param node - the node to compute the rank of
* @returns rank - the rank of the node; if defined lower ranks indicate
* nodes that come earlier (lower y's)
*/
(node: GraphNode<NodeDatum, LinkDatum>): number | undefined;
}
/**
* a link between nodes, with attached information
*
* The immutable version of {@link MutGraphLink}.
*/
export interface GraphLink<out NodeDatum = unknown, out LinkDatum = unknown> {
/** The dag node this link comes from */
readonly source: GraphNode<NodeDatum, LinkDatum>;
/** The dag node this link goes to */
readonly target: GraphNode<NodeDatum, LinkDatum>;
/** layout control points assigned to the link */
points: [number, number][];
/** user data attached to this link */
data: LinkDatum;
}
/**
* a mutable link between nodes
*
* The mutable version of {@link GraphLink}.
*/
export interface MutGraphLink<in out NodeDatum, in out LinkDatum> extends GraphLink<NodeDatum, LinkDatum> {
readonly source: MutGraphNode<NodeDatum, LinkDatum>;
readonly target: MutGraphNode<NodeDatum, LinkDatum>;
/** remove this link from the graph */
delete(): void;
}
/**
* A graph representation
*
* Graphs are collections of {@link GraphNode}s, held together by
* {@link GraphLink}s, although there's no requirement that they're connected.
* Graphs support iterating over their {@link nodes} and {@link links} as well
* as checking if certain properties hold. Each node is also defined as a graph
* of just its connected component, so all methods of a graph also apply to any
* individual node, treated as all nodes reachable from the current node.
*
* The structure of a Graph is immutable, allowing for appropriate variance
* with readonly methods. For a graph whos structure can be modified, see
* {@link MutGraph}.
*
* Methods names preceeded by an `n` will return a number, often the length of
* the iterable produces by the method sans-prefix.
*/
export interface Graph<out NodeDatum = unknown, out LinkDatum = unknown> {
/**
* an iterator over every {@link GraphNode | node} in the graph
*
* @remarks
* Be careful not to modify the graph structure while iterating as any
* modification, including adding or removing links, can change the behavior
* of iteration giving unexpected results. If you want to guarantee
* consistent iteration, allocating an array first with `[...graph.nodes()]` will
* ensure consistent iteration.
*/
nodes(): IterableIterator<GraphNode<NodeDatum, LinkDatum>>;
/**
* compute a topological order of the graph
*
* If the graph can't be represented in topological order, this will try to
* minimize the number of edge inversions. Optimally minimizing inversions is
* np-complete, so this will only be approximate.
*
* You can optionally specify a {@link Rank} accessor that defines a
* numerical rank for every node. Nodes with a lower rank will come before
* nodes of a higher rank even if that requires more edge inversions. Nodes
* without a rank are unconstrained.
*/
topological(rank?: Rank<NodeDatum, LinkDatum>): GraphNode<NodeDatum, LinkDatum>[];
/**
* an iterator over every {@link GraphLink | link} in the graph
*/
links(): IterableIterator<GraphLink<NodeDatum, LinkDatum>>;
/** the number of nodes in the graph */
nnodes(): number;
/** the number of links in the graph */
nlinks(): number;
/**
* an iterator over the roots of the graph
*
* The roots are defined as a set of nodes such that every node in the graph
* is a descendant of one of the roots, an no root is a descendant of any
* other root. It is guaranteed to include every node with no parents, but
* one node in a cycle may be chosen if there is no unique root for that
* cycle.
*
* @remarks the current implementation will return a minimal root set as long
* as source cycles contain a node with a single parent.
*/
roots(): IterableIterator<GraphNode<NodeDatum, LinkDatum>>;
/**
* an iterator over the leaves of the graph
*
* The leaves are defined as a set of nodes such that every node in the graph
* is an ancestor of one of the leaves, an no leaf is an ancestor of any
* other leaf. It is guaranteed to include every node with no children, but
* one node in a cycle may be chosen if there is no unique leaf for that
* cycle.
*
* @remarks the current implementation will return a minimal leaf set as long
* as target cycles contain a node with a single child.
*/
leaves(): IterableIterator<GraphNode<NodeDatum, LinkDatum>>;
/**
* split a graph into connected components
*
* @remarks
* Since each node behaves like a graph of its connected component,
* this is equivalent with returning a graph of each connected component.
*
* @returns splits an iterable over a single node in each connected
* component
*/
split(): IterableIterator<GraphNode<NodeDatum, LinkDatum>>;
/**
* true if every node in the graph is reachable from every other
*/
connected(): boolean;
/**
* true if at least one node in this graph has multiple links to the same child
*/
multi(): boolean;
/**
* true if there no cycles in the graph
*/
acyclic(): boolean;
/**
* serialize the graph
*
* @remarks this is intended to be called automatically by `JSON.stringify`.
*/
toJSON(): unknown;
}
/**
* a mutable graph representation
*
* In addition to all of the methods inherent to {@link Graph}s, this also
* allows structure graph modification with the {@link node} and {@link link}
* methods to either create an empty node or create a link between two nodes.
* Nodes and links can be removed with the delete method on
* {@link MutGraphNode#delete | nodes} and {@link MutGraphLink#delete | links}.
*
* ## Performance
*
* In order to keep track of caches, internally adding links runs union-find
* which has near, but not quite, constant complexity. However, various methods
* can run in linear time making some access patterns run in worst case
* quadratic if they're called in between modifications. This was ultimately
* deemed acceptable as all modification will likely come before a layout, and
* that will always take linear time.
*/
export interface MutGraph<in out NodeDatum, in out LinkDatum> extends Graph<NodeDatum, LinkDatum> {
nodes(): IterableIterator<MutGraphNode<NodeDatum, LinkDatum>>;
links(): IterableIterator<MutGraphLink<NodeDatum, LinkDatum>>;
split(): IterableIterator<MutGraphNode<NodeDatum, LinkDatum>>;
roots(): IterableIterator<MutGraphNode<NodeDatum, LinkDatum>>;
leaves(): IterableIterator<MutGraphNode<NodeDatum, LinkDatum>>;
/**
* add a new node with datum
*
* If `undefined` extends NodeDatum, datum can be elided and simply called as
* ```ts
* grf.node();
* ```
*/
node(...datum: undefined extends NodeDatum ? [datum?: NodeDatum] : [datum: NodeDatum]): MutGraphNode<NodeDatum, LinkDatum>;
/**
* add a new link from source to target
*
* If `undefined` extends LinkDatum, datum can be elided and simply called as
* ```ts
* grf.link(source, target);
* ```
*/
link(source: MutGraphNode<NodeDatum, LinkDatum>, target: MutGraphNode<NodeDatum, LinkDatum>, ...datum: undefined extends LinkDatum ? [datum?: LinkDatum] : [datum: LinkDatum]): MutGraphLink<NodeDatum, LinkDatum>;
}
export interface GraphNodeMixin<out NodeDatum, out LinkDatum> {
/** user data for this node */
data: NodeDatum;
/** raw (possibly undefined) x coordinate for a node */
ux?: number | undefined;
/** raw (possibly undefined) y coordinate for a node */
uy?: number | undefined;
/**
* view of {@link ux} that throws if ux is undefined
*/
x: number;
/**
* view of {@link uy} that throws if uy is undefined
*/
y: number;
/** the number of unique parent nodes */
nparents(): number;
/** the number of unique child nodes */
nchildren(): number;
/** the number of parent links */
nparentLinks(): number;
/** the number of child links */
nchildLinks(): number;
/** the number of links from a specific node */
nparentLinksTo(node: GraphNode<NodeDatum, LinkDatum>): number;
/** iterator over every link from a specific node */
parentLinksTo(node: GraphNode<NodeDatum, LinkDatum>): IterableIterator<GraphLink<NodeDatum, LinkDatum>>;
/** the number of links to a specific node */
nchildLinksTo(node: GraphNode<NodeDatum, LinkDatum>): number;
/** iterator over every link to a specific node */
childLinksTo(node: GraphNode<NodeDatum, LinkDatum>): IterableIterator<GraphLink<NodeDatum, LinkDatum>>;
/** iterator over this node's unique parent nodes */
parents(): IterableIterator<GraphNode<NodeDatum, LinkDatum>>;
/** iterator over this node's unique child nodes */
children(): IterableIterator<GraphNode<NodeDatum, LinkDatum>>;
/** iterator over this node's unique parent nodes and the number of links from them */
parentCounts(): IterableIterator<[GraphNode<NodeDatum, LinkDatum>, number]>;
/** iterator over this node's unique child nodes and the number of links to them */
childCounts(): IterableIterator<[GraphNode<NodeDatum, LinkDatum>, number]>;
/**
* iterator of links to this node from its parents
*
* The order of links is guaranteed to not change between iterations.
*/
parentLinks(): IterableIterator<GraphLink<NodeDatum, LinkDatum>>;
/**
* iterator of links from this node to its children
*
* The order of links is guaranteed to not change between iterations.
*/
childLinks(): IterableIterator<GraphLink<NodeDatum, LinkDatum>>;
/**
* iterator of all nodes reachable though parents
*
* The iterator includes this node.
*/
ancestors(): IterableIterator<GraphNode<NodeDatum, LinkDatum>>;
/**
* iterator of all nodes reachable though children
*
* The iterator includes this node.
*/
descendants(): IterableIterator<GraphNode<NodeDatum, LinkDatum>>;
}
/**
* a node in a graph
*
* Nodes provide all the same interfaces that {@link Graph}s do in terms of
* node properties and iteration. When called on a node, the nodes behaves as a
* graph of just its connected component. Therefore new nodes will always be
* {@link acyclic}, {@link connected}, and not {@link multi}.
*
* In addition, nodes also expose properties of their immediate neighborhoods,
* iterators and counts of nodes they directly touch.
*
* This is the immutable version of {@link MutGraphNode}.
*/
export interface GraphNode<out NodeDatum = unknown, out LinkDatum = unknown> extends Graph<NodeDatum, LinkDatum>, GraphNodeMixin<NodeDatum, LinkDatum> {
}
/**
* a mutable node in a graph
*
* This possesses all of the methods of both {@link MutGraph} and {@link
* GraphNode}, while also adding the ability to directly add {@link parent}s or
* {@link child}ren and to {@link delete | remove} the node and all links from
* the graph.
*/
export interface MutGraphNode<in out NodeDatum, in out LinkDatum> extends MutGraph<NodeDatum, LinkDatum>, GraphNodeMixin<NodeDatum, LinkDatum> {
/** {@inheritDoc GraphNode#parentLinksTo} */
parentLinksTo(node: GraphNode<NodeDatum, LinkDatum>): IterableIterator<MutGraphLink<NodeDatum, LinkDatum>>;
/** {@inheritDoc GraphNode#childLinksTo} */
childLinksTo(node: GraphNode<NodeDatum, LinkDatum>): IterableIterator<MutGraphLink<NodeDatum, LinkDatum>>;
/** {@inheritDoc GraphNode#parents} */
parents(): IterableIterator<MutGraphNode<NodeDatum, LinkDatum>>;
/** {@inheritDoc GraphNode#children} */
children(): IterableIterator<MutGraphNode<NodeDatum, LinkDatum>>;
/** {@inheritDoc GraphNode#parentCounts} */
parentCounts(): IterableIterator<[
MutGraphNode<NodeDatum, LinkDatum>,
number
]>;
/** {@inheritDoc GraphNode#childCounts} */
childCounts(): IterableIterator<[MutGraphNode<NodeDatum, LinkDatum>, number]>;
/** {@inheritDoc GraphNode#parentLinks} */
parentLinks(): IterableIterator<MutGraphLink<NodeDatum, LinkDatum>>;
/** {@inheritDoc GraphNode#childLinks} */
childLinks(): IterableIterator<MutGraphLink<NodeDatum, LinkDatum>>;
/** {@inheritDoc GraphNode#ancestors} */
ancestors(): IterableIterator<MutGraphNode<NodeDatum, LinkDatum>>;
/** {@inheritDoc GraphNode#descendants} */
descendants(): IterableIterator<MutGraphNode<NodeDatum, LinkDatum>>;
/** add a new link from a parent node */
parent(par: MutGraphNode<NodeDatum, LinkDatum>, ...datum: undefined extends LinkDatum ? [datum?: LinkDatum] : [datum: LinkDatum]): MutGraphLink<NodeDatum, LinkDatum>;
/** add a new link to a child node */
child(chi: MutGraphNode<NodeDatum, LinkDatum>, ...datum: undefined extends LinkDatum ? [datum?: LinkDatum] : [datum: LinkDatum]): MutGraphLink<NodeDatum, LinkDatum>;
/**
* remove this node and all of its links from the graph
*
* Once a node is deleted, none of its methods are valid or guaranteed to do
* the correct thing.
*/
delete(): void;
}
/**
* create a new mutable empty {@link MutGraph}
*
* When creating a new mutable graph via typescript, you must specify the type
* of the node data and link data you intend on using, since these types are
* invariant for mutable graphs.
*
* @example
* Creating a graph with node and link data:
*
* ```ts
* // create a new graph
* const grf = graph<string, number>();
* // add two nodes with some data
* const a = grf.node("a");
* const b = grf.node("b");
* // add a new link with the data `1`
* const link = grf.link(a, b, 1);
* grf.connected(); // true
* ```
*
* @example
* If `undefined` extends the data types then they can be omitted:
* ```
* // create a new graph
* const grf = graph<undefined | string, undefined | number>();
* const a = grf.node();
* const b = grf.node();
* const link = grf.link(a, b);
* grf.connected(); // true
* ```
*
* @typeParam NodeDatum - the extra user data attached to each node
* @typeParam LinkDatum - the extra data attached to each link
*/
export declare function graph<NodeDatum, LinkDatum>(): MutGraph<NodeDatum, LinkDatum>;