UNPKG

d3-dag

Version:

Layout algorithms for visualizing directed acylic graphs.

253 lines (252 loc) 9.62 kB
/** * 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;