d3-dag
Version:
Layout algorithms for visualizing directed acylic graphs.
253 lines (252 loc) • 9.62 kB
TypeScript
/**
* A {@link Sugiyama} for computing a layered layout of a dag
*
* @packageDocumentation
*/
import { Graph } from "../graph";
import { LayoutResult, NodeSize } from "../layout";
import { Tweak } from "../tweaks";
import { U } from "../utils";
import { Coord } from "./coord";
import { DefaultCoordSimplex } from "./coord/simplex";
import { Decross } from "./decross";
import { DefaultDecrossTwoLayer } from "./decross/two-layer";
import { Layering } from "./layering";
import { DefaultLayeringSimplex } from "./layering/simplex";
/** sugiyama operators */
export interface SugiyamaOps<in N = never, in L = never> {
/** layering operator */
layering: Layering<N, L>;
/** decross operator */
decross: Decross<N, L>;
/** coord operator */
coord: Coord<N, L>;
/** node size operator */
nodeSize: NodeSize<N, L>;
/** tweaks */
tweaks: readonly Tweak<N, L>[];
}
/**
* the operator used to layout a {@link Graph} using the sugiyama layered method
*
* The algorithm is roughly comprised of three steps:
* 1. {@link Layering} - in this step, every node is assigned a non-negative
* integer later such that children are guaranteed to have higher layers
* than their parents. (modified with {@link layering})
* 2. {@link Decross} - in the step, nodes in each layer are reordered to
* minimize the number of crossings. (modified with {@link decross})
* 3. {@link Coord} - in the step, the nodes are assigned x and y coordinates
* that respect their layer, layer ordering, and size. (modified with
* {@link coord} and {@link nodeSize})
*
* The algorithm is based off ideas presented in K. Sugiyama et al. [1979], but
* described by {@link http://www.it.usyd.edu.au/~shhong/fab.pdf | S. Hong}.
* The sugiyama layout can be configured with different algorithms for each
* stage of the layout. For each stage there should be adecuate choices for
* methods that balance speed and quality for your desired layout. In the
* absence of those, any function that meets the interface for that stage is
* valid.
*
* Create with {@link sugiyama}.
*
* @remarks
*
* If one wants even more control over the algorithm, each step is broken down
* in the source code and can be achieved by calling an exported utility
* function. If one wants to call certain pieces incrementally, or adjust how
* things are called, it's recommended to look at the source and call each
* component function successively.
*/
export interface Sugiyama<Ops extends SugiyamaOps = SugiyamaOps> {
/**
* layout the graph using the sugiyama layout
*
* @param graph - the graph to layout
* @returns dimension - the width and height of resulting layout
*/
(graph: Ops extends SugiyamaOps<infer N, infer L> ? Graph<N, L> : never): LayoutResult;
/**
* set the {@link Layering} operator
*
* The layering operator takes the graph, and assigns each node layer that
* respects
*
* There are three built-in layering operators:
* - {@link layeringSimplex} - This minimizes the overall length of edges,
* and is reasonably fast for most graphs. Minimizing edges also tends to
* make the next steps faster.
* - {@link layeringLongestPath} - This minimizes the height of the overall
* graph.
* - {@link layeringTopological} - This creates a topological ordering, which
* inherently minimizes the width of the graph, but will only produce good
* layouts with other topological operators.
*
* You can also supply any function that satisfies the {@link Layering}
* interface. See that documentation for more information about implementing
* your own layering operator.
*
* (default: {@link layeringSimplex})
*
* @example
*
* ```ts
* const layout = sugiyama().layering(layeringLongestPath());
* ```
*/
layering<NewLayering extends Layering>(layer: NewLayering): Sugiyama<U<Ops, "layering", NewLayering>>;
/**
* get the current {@link Layering}.
*/
layering(): Ops["layering"];
/**
* set the {@link Decross} operator
*
* The decross operator takes a layered graph with extra nodes for long
* paths, and reorders nodes along a layer to minimize the number of edge
* crossings (or other desired properties).
*
* There are three built-in decrossing operators:
* - {@link decrossOpt} - This optimally minimizes edge crossings, but due to
* the complex nature of the task, only works for reasonably small graphs.
* - {@link decrossTwoLayer} - This is a heuristic method for decrossing
* minimization that tries to strike a balance between a small number of
* edge crossings, and reasonable running time.
* - {@link decrossDfs} - This is a very cheap decrossing operator that
* orders nodes via a depth first search. It's mostly used for
* {@link DecrossTwoLayer#inits} of the two-layer operator.
*
* You can also supply any function that satisfies the {@link Decross}
* interface. See that documentation for more information about implementing
* your own decrossing operator.
*
* (default: {@link decrossTwoLayer})
*
* @example
*
* ```ts
* const layout = sugiyama().decross(decrossOpt());
* ```
*/
decross<NewDecross extends Decross>(dec: NewDecross): Sugiyama<U<Ops, "decross", NewDecross>>;
/**
* get the current {@link Decross}.
*/
decross(): Ops["decross"];
/**
* set the {@link Coord} operator
*
* The final stage is coordinate assignment, which takes a layered graph with
* nodes in the correct order, and assigns them x coordinates.
*
* There are four built-in coordinate assignment operators:
* - {@link coordSimplex} - This assigns x coordinates based on a simplex
* minimization that tries to produce long vertical edges. It usually
* produces attractive layouts in a reasonable amount of time.
* - {@link coordQuad} - This uses a quadratic optimization that tries to
* minimize the curvature of lines, but usually produces worse layouts
* than the simplex variant.
* - {@link coordGreedy} - If either of the above methods take too long, this
* uses a heuristic to assign x coordinates meaning it will run more
* quickly, but usually produce slightly worse layouts.
* - {@link coordTopological} - This is a coordinate assignment tailored for
* a {@link layeringTopological | topological layout}. It can use a
* simplex or quadratic optimization to create a topological layout.
*
* You can also supply any function that satisfies the {@link Coord}
* interface. See that documentation for more information about implementing
* your own coordinate assignment operator.
*
* (default: {@link coordSimplex})
*
* @example
*
* ```ts
* const layout = sugiyama().coord(coordQuad());
* ```
*/
coord<NewCoord extends Coord>(crd: NewCoord): Sugiyama<U<Ops, "coord", NewCoord>>;
/**
* get the current {@link Coord}.
*/
coord(): Ops["coord"];
/**
* set the {@link Tweak}s to apply after layout
*/
tweaks<const NewTweaks extends readonly Tweak[]>(val: NewTweaks): Sugiyama<U<Ops, "tweaks", NewTweaks>>;
/**
* get the current {@link Tweak}s.
*/
tweaks(): Ops["tweaks"];
/**
* sets the {@link NodeSize}
*
* (default: `[1, 1]`)
*/
nodeSize<NewNodeSize extends NodeSize>(acc: NewNodeSize): Sugiyama<U<Ops, "nodeSize", NewNodeSize>>;
/** get the current node size */
nodeSize(): Ops["nodeSize"];
/**
* get the gap size between nodes
*
* (default: `[1, 1]`)
*/
gap(val: readonly [number, number]): Sugiyama<Ops>;
/** get the current gap size */
gap(): readonly [number, number];
}
/** default sugiyama operator */
export type DefaultSugiyama = Sugiyama<{
/** default layering */
layering: DefaultLayeringSimplex;
/** default decross */
decross: DefaultDecrossTwoLayer;
/** default coord */
coord: DefaultCoordSimplex;
/** default node size */
nodeSize: readonly [1, 1];
/** default tweaks */
tweaks: readonly [];
}>;
/**
* construct a new {@link Sugiyama} with the default settings
*
* The sugiyama layout takes a three step layering approach. First it calls
* {@link Sugiyama#layering} to assign a layer to each node. Then it calls
* {@link Sugiyama#decross} to arrange nodes in each layer to minimize edge
* crossings. Finally it calls {@link Sugiyama#coord} to assign actual
* coordinates given the ordering.
*
* Finally, you can also tweak the standard settings of
* {@link Sugiyama#nodeSize}, {@link Sugiyama#gap}, and
* {@link Sugiyama#tweaks}. Note that {@link Rank} can be set in some {@link
* Layering} operators.
*
* <img alt="Sugiyama example" src="media://sugi-simplex-twolayer-simplex.png" width="400">
*
* @example
*
* To use the default layout:
*
* ```ts
* const grf: Graph = ...
* const layout = sugiyama();
* layout(dag);
* for (const node of dag.nodes()) {
* console.log(node.x, node.y);
* }
* ```
*
* @example
*
* To use optimal decrossing, which will only work for small graphs:
*
* ```ts
* const grf: Graph = ...
* const layout = sugiyama().decross(decrossOpt());
* layout(dag);
* for (const node of dag.nodes()) {
* console.log(node.x, node.y);
* }
* ```
*/
export declare function sugiyama(...args: never[]): DefaultSugiyama;