From 8740b83207d639ff9bfd61a5117e381db8db474a Mon Sep 17 00:00:00 2001 From: tbo47 Date: Sun, 6 Oct 2024 12:25:26 +0000 Subject: [PATCH 1/2] add types --- src/dagre-js/create-clusters.js | 6 +++ src/dagre-js/create-edge-paths.js | 15 ++++++ src/dagre-js/create-nodes.js | 12 +++-- src/dagre-js/render.js | 29 ++++------ src/dagre-js/util.js | 9 +++- src/dagre/acyclic.js | 18 +++++-- src/dagre/add-border-segments.js | 13 +++-- src/dagre/coordinate-system.js | 24 +++++++-- src/dagre/data/list.js | 7 +-- src/dagre/debug.js | 9 ++-- src/dagre/greedy-fas.js | 14 ++--- src/dagre/greedy-fas.test.js | 5 +- src/dagre/layout.js | 54 ++++++++++++++++--- src/dagre/nesting-graph.js | 21 ++++++-- src/dagre/normalize.js | 16 +++--- src/dagre/order/add-subgraph-constraints.js | 10 ++-- src/dagre/order/barycenter.js | 10 ++-- src/dagre/order/build-layer-graph.js | 10 ++-- src/dagre/order/cross-count.js | 13 +++-- src/dagre/order/index.js | 13 +++-- src/dagre/order/resolve-conflicts.js | 4 +- src/dagre/order/sort-subgraph.js | 10 ++-- src/dagre/order/sort.js | 4 +- src/dagre/parent-dummy-chains.js | 16 ++++-- src/dagre/position/bk.js | 27 ++++++++-- src/dagre/position/index.js | 13 +++-- src/dagre/rank/feasible-tree.js | 16 +++--- src/dagre/rank/index.js | 16 ++++-- src/dagre/rank/network-simplex.js | 24 +++++++-- src/dagre/rank/util.js | 15 +++--- src/dagre/util.js | 30 +++++++++-- src/graphlib/alg/dfs.js | 9 +++- src/graphlib/alg/dijkstra-all.js | 10 ++-- src/graphlib/alg/dijkstra.js | 9 ++++ src/graphlib/alg/floyd-warshall.js | 13 +++-- src/graphlib/alg/postorder.js | 6 +++ src/graphlib/alg/preorder.js | 10 ++-- src/graphlib/alg/prim.js | 3 ++ src/graphlib/graph.js | 59 ++++++++++++++++----- 39 files changed, 451 insertions(+), 151 deletions(-) diff --git a/src/dagre-js/create-clusters.js b/src/dagre-js/create-clusters.js index 8e6f917..9fccaf2 100644 --- a/src/dagre-js/create-clusters.js +++ b/src/dagre-js/create-clusters.js @@ -1,9 +1,15 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as d3 from 'd3'; import { addLabel } from './label/add-label.js'; import * as util from './util.js'; export { createClusters, setCreateClusters }; +/** + * @param { Graph } g + */ var createClusters = function (selection, g) { var clusters = g.nodes().filter(function (v) { return util.isSubgraph(g, v); diff --git a/src/dagre-js/create-edge-paths.js b/src/dagre-js/create-edge-paths.js index 80750c6..5377985 100644 --- a/src/dagre-js/create-edge-paths.js +++ b/src/dagre-js/create-edge-paths.js @@ -1,3 +1,6 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as d3 from 'd3'; import * as _ from 'lodash-es'; import { intersectNode } from './intersect/intersect-node.js'; @@ -5,6 +8,9 @@ import * as util from './util.js'; export { createEdgePaths, setCreateEdgePaths }; +/** + * @param { Graph } g + */ var createEdgePaths = function (selection, g, arrows) { var previousPaths = selection .selectAll('g.edgePath') @@ -73,6 +79,9 @@ function makeFragmentRef(url, fragmentId) { return baseUrl + '#' + fragmentId; } +/** + * @param { Graph } g + */ function calcPoints(g, e) { var edge = g.edge(e); var tail = g.node(e.v); @@ -109,6 +118,9 @@ function getCoords(elem) { return { x: matrix.e, y: matrix.f }; } +/** + * @param { Graph } g + */ function enter(svgPaths, g) { var svgPathsEnter = svgPaths.enter().append('g').attr('class', 'edgePath').style('opacity', 0); svgPathsEnter @@ -126,6 +138,9 @@ function enter(svgPaths, g) { return svgPathsEnter; } +/** + * @param { Graph } g + */ function exit(svgPaths, g) { var svgPathExit = svgPaths.exit(); util.applyTransition(svgPathExit, g).style('opacity', 0).remove(); diff --git a/src/dagre-js/create-nodes.js b/src/dagre-js/create-nodes.js index ec8edd2..be5dbd5 100644 --- a/src/dagre-js/create-nodes.js +++ b/src/dagre-js/create-nodes.js @@ -1,11 +1,15 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as d3 from 'd3'; import { pick } from 'lodash-es'; import { addLabel } from './label/add-label.js'; import * as util from './util.js'; -export { createNodes, setCreateNodes }; - -var createNodes = function (selection, g, shapes) { +/** + * @param { Graph } g + */ +export var createNodes = function (selection, g, shapes) { var simpleNodes = g.nodes().filter(function (v) { return !util.isSubgraph(g, v); }); @@ -87,6 +91,6 @@ var createNodes = function (selection, g, shapes) { return svgNodes; }; -function setCreateNodes(value) { +export function setCreateNodes(value) { createNodes = value; } diff --git a/src/dagre-js/render.js b/src/dagre-js/render.js index ba56edd..6f3fbd8 100644 --- a/src/dagre-js/render.js +++ b/src/dagre-js/render.js @@ -1,3 +1,6 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as d3 from 'd3'; import { defaults } from 'lodash-es'; import { layout } from '../dagre/index.js'; @@ -9,12 +12,15 @@ import { createNodes, setCreateNodes } from './create-nodes.js'; import { positionClusters } from './position-clusters.js'; import { positionEdgeLabels } from './position-edge-labels.js'; import { positionNodes } from './position-nodes.js'; -import { shapes, setShapes } from './shapes.js'; +import { setShapes, shapes } from './shapes.js'; export { render }; // This design is based on http://bost.ocks.org/mike/chart/. function render() { + /** + * @param { Graph } g + */ var fn = function (svg, g) { preProcessGraph(g); @@ -90,27 +96,12 @@ var EDGE_DEFAULT_ATTRS = { curve: d3.curveLinear, }; -/** - * @typedef {Object} Node - * @property {string} label - The label of the node. - * @property {number} [paddingX] - The horizontal padding of the node. - * @property {number} [paddingY] - The vertical padding of the node. - * @property {number} [padding] - The padding of the node for all directions. Overrides `paddingX` and `paddingY`. - * @property {number} [paddingLeft] - The left padding of the node. - * @property {number} [paddingRight] - The right padding of the node. - * @property {number} [_prevWidth] - * @property {number} [width] - * @property {number} [_prevHeight] - * @property {number} [height] - */ - /** * Pre-processes the graph by setting default labels and padding for nodes. - * @param {Object} g - The graph object. + * @param { Graph } g */ function preProcessGraph(g) { g.nodes().forEach((v) => { - /** @type {Node} */ const node = g.node(v); if (!Object.prototype.hasOwnProperty.call(node, 'label') && !g.children(v).length) { node.label = v; @@ -163,9 +154,11 @@ function preProcessGraph(g) { }); } +/** + * @param { Graph } g + */ function postProcessGraph(g) { g.nodes().forEach((v) => { - /** @type {Node} */ var node = g.node(v); // Restore original dimensions diff --git a/src/dagre-js/util.js b/src/dagre-js/util.js index 55b23b6..a1eeca9 100644 --- a/src/dagre-js/util.js +++ b/src/dagre-js/util.js @@ -1,11 +1,15 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; // Public utility functions export { isSubgraph, edgeToId, applyStyle, applyClass, applyTransition }; -/* +/** * Returns true if the specified node in the graph is a subgraph node. A * subgraph node is one that contains other nodes. + * @param { Graph } g */ function isSubgraph(g, v) { return !!g.children(v).length; @@ -32,6 +36,9 @@ function applyClass(dom, classFn, otherClasses) { } } +/** + * @param { Graph } g + */ function applyTransition(selection, g) { var graph = g.graph(); diff --git a/src/dagre/acyclic.js b/src/dagre/acyclic.js index 9b93fa3..3008275 100644 --- a/src/dagre/acyclic.js +++ b/src/dagre/acyclic.js @@ -1,9 +1,13 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; import { greedyFAS } from './greedy-fas.js'; -export { run, undo }; - -function run(g) { +/** + * @param { Graph } g + */ +export function run(g) { var fas = g.graph().acyclicer === 'greedy' ? greedyFAS(g, weightFn(g)) : dfsFAS(g); _.forEach(fas, function (e) { var label = g.edge(e); @@ -20,6 +24,9 @@ function run(g) { } } +/** + * @param { Graph } g + */ function dfsFAS(g) { var fas = []; var stack = {}; @@ -45,7 +52,10 @@ function dfsFAS(g) { return fas; } -function undo(g) { +/** + * @param { Graph } g + */ +export function undo(g) { _.forEach(g.edges(), function (e) { var label = g.edge(e); if (label.reversed) { diff --git a/src/dagre/add-border-segments.js b/src/dagre/add-border-segments.js index 08c6a4e..da6e54a 100644 --- a/src/dagre/add-border-segments.js +++ b/src/dagre/add-border-segments.js @@ -1,9 +1,13 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; import * as util from './util.js'; -export { addBorderSegments }; - -function addBorderSegments(g) { +/** + * @param { Graph } g + */ +export function addBorderSegments(g) { function dfs(v) { var children = g.children(v); var node = g.node(v); @@ -24,6 +28,9 @@ function addBorderSegments(g) { _.forEach(g.children(), dfs); } +/** + * @param { Graph } g + */ function addBorderNode(g, prop, prefix, sg, sgNode, rank) { var label = { width: 0, height: 0, rank: rank, borderType: prop }; var prev = sgNode[prop][rank - 1]; diff --git a/src/dagre/coordinate-system.js b/src/dagre/coordinate-system.js index be2e52f..7751988 100644 --- a/src/dagre/coordinate-system.js +++ b/src/dagre/coordinate-system.js @@ -1,15 +1,22 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; -export { adjust, undo }; - -function adjust(g) { +/** + * @param { Graph } g + */ +export function adjust(g) { var rankDir = g.graph().rankdir.toLowerCase(); if (rankDir === 'lr' || rankDir === 'rl') { swapWidthHeight(g); } } -function undo(g) { +/** + * @param { Graph } g + */ +export function undo(g) { var rankDir = g.graph().rankdir.toLowerCase(); if (rankDir === 'bt' || rankDir === 'rl') { reverseY(g); @@ -21,6 +28,9 @@ function undo(g) { } } +/** + * @param { Graph } g + */ function swapWidthHeight(g) { _.forEach(g.nodes(), function (v) { swapWidthHeightOne(g.node(v)); @@ -36,6 +46,9 @@ function swapWidthHeightOne(attrs) { attrs.height = w; } +/** + * @param { Graph } g + */ function reverseY(g) { _.forEach(g.nodes(), function (v) { reverseYOne(g.node(v)); @@ -54,6 +67,9 @@ function reverseYOne(attrs) { attrs.y = -attrs.y; } +/** + * @param { Graph } g + */ function swapXY(g) { _.forEach(g.nodes(), function (v) { swapXYOne(g.node(v)); diff --git a/src/dagre/data/list.js b/src/dagre/data/list.js index 9b4609d..79ecb5b 100644 --- a/src/dagre/data/list.js +++ b/src/dagre/data/list.js @@ -1,11 +1,8 @@ -/* +/** * Simple doubly linked list implementation derived from Cormen, et al., * "Introduction to Algorithms". */ - -export { List }; - -class List { +export class List { constructor() { var sentinel = {}; sentinel._next = sentinel._prev = sentinel; diff --git a/src/dagre/debug.js b/src/dagre/debug.js index f8c9e38..3274190 100644 --- a/src/dagre/debug.js +++ b/src/dagre/debug.js @@ -2,10 +2,11 @@ import * as _ from 'lodash-es'; import { Graph } from '../graphlib/index.js'; import * as util from './util.js'; -export { debugOrdering }; - -/* istanbul ignore next */ -function debugOrdering(g) { +/** + * istanbul ignore next + * @param { Graph } g + */ +export function debugOrdering(g) { var layerMatrix = util.buildLayerMatrix(g); var h = new Graph({ compound: true, multigraph: true }).setGraph({}); diff --git a/src/dagre/greedy-fas.js b/src/dagre/greedy-fas.js index 5cdd295..f0b4101 100644 --- a/src/dagre/greedy-fas.js +++ b/src/dagre/greedy-fas.js @@ -2,18 +2,17 @@ import * as _ from 'lodash-es'; import { Graph } from '../graphlib/index.js'; import { List } from './data/list.js'; -/* +var DEFAULT_WEIGHT_FN = _.constant(1); + +/** * A greedy heuristic for finding a feedback arc set for a graph. A feedback * arc set is a set of edges that can be removed to make a graph acyclic. * The algorithm comes from: P. Eades, X. Lin, and W. F. Smyth, "A fast and * effective heuristic for the feedback arc set problem." This implementation * adjusts that from the paper to allow for weighted edges. + * @param { Graph } g */ -export { greedyFAS }; - -var DEFAULT_WEIGHT_FN = _.constant(1); - -function greedyFAS(g, weightFn) { +export function greedyFAS(g, weightFn) { if (g.nodeCount() <= 1) { return []; } @@ -28,6 +27,9 @@ function greedyFAS(g, weightFn) { ); } +/** + * @param { Graph } g + */ function doGreedyFAS(g, buckets, zeroIdx) { var results = []; var sources = buckets[buckets.length - 1]; diff --git a/src/dagre/greedy-fas.test.js b/src/dagre/greedy-fas.test.js index 1c97d8e..ba51aa6 100644 --- a/src/dagre/greedy-fas.test.js +++ b/src/dagre/greedy-fas.test.js @@ -6,7 +6,10 @@ import { findCycles } from '../graphlib/alg/find-cycles.js'; import { greedyFAS } from './greedy-fas.js'; describe('greedyFAS', function () { - var g; + /** + * @param {Graph} g + */ + let g; beforeEach(function () { g = new Graph(); diff --git a/src/dagre/layout.js b/src/dagre/layout.js index ee10670..02e73aa 100644 --- a/src/dagre/layout.js +++ b/src/dagre/layout.js @@ -1,19 +1,20 @@ import * as _ from 'lodash-es'; import { Graph } from '../graphlib/index.js'; +import * as acyclic from './acyclic.js'; import { addBorderSegments } from './add-border-segments.js'; import * as coordinateSystem from './coordinate-system.js'; -import * as acyclic from './acyclic.js'; -import * as normalize from './normalize.js'; -import { rank } from './rank/index.js'; import * as nestingGraph from './nesting-graph.js'; +import * as normalize from './normalize.js'; import { order } from './order/index.js'; import { parentDummyChains } from './parent-dummy-chains.js'; import { position } from './position/index.js'; +import { rank } from './rank/index.js'; import * as util from './util.js'; -export { layout }; - -function layout(g, opts) { +/** + * @param { Graph } g + */ +export function layout(g, opts) { var time = opts && opts.debugTiming ? util.time : util.notime; time('layout', () => { var layoutGraph = time(' buildLayoutGraph', () => buildLayoutGraph(g)); @@ -22,6 +23,9 @@ function layout(g, opts) { }); } +/** + * @param { Graph } g + */ function runLayout(g, time) { time(' makeSpaceForEdgeLabels', () => makeSpaceForEdgeLabels(g)); time(' removeSelfEdges', () => removeSelfEdges(g)); @@ -112,7 +116,7 @@ var edgeAttrs = ['labelpos']; * attributes can influence layout. */ function buildLayoutGraph(inputGraph) { - var g = new Graph({ multigraph: true, compound: true }); + const g = new Graph({ multigraph: true, compound: true }); var graph = canonicalize(inputGraph.graph()); g.setGraph( @@ -144,6 +148,9 @@ function buildLayoutGraph(inputGraph) { * We also add some minimal padding to the width to push the label for the edge * away from the edge itself a bit. */ +/** + * @param { Graph } g + */ function makeSpaceForEdgeLabels(g) { var graph = g.graph(); graph.ranksep /= 2; @@ -166,6 +173,9 @@ function makeSpaceForEdgeLabels(g) { * so that we can safely remove empty ranks while preserving balance for the * label's position. */ +/** + * @param { Graph } g + */ function injectEdgeLabelProxies(g) { _.forEach(g.edges(), function (e) { var edge = g.edge(e); @@ -178,6 +188,9 @@ function injectEdgeLabelProxies(g) { }); } +/** + * @param { Graph } g + */ function assignRankMinMax(g) { var maxRank = 0; _.forEach(g.nodes(), function (v) { @@ -192,6 +205,9 @@ function assignRankMinMax(g) { g.graph().maxRank = maxRank; } +/** + * @param { Graph } g + */ function removeEdgeLabelProxies(g) { _.forEach(g.nodes(), function (v) { var node = g.node(v); @@ -202,6 +218,9 @@ function removeEdgeLabelProxies(g) { }); } +/** + * @param { Graph } g + */ function translateGraph(g) { var minX = Number.POSITIVE_INFINITY; var maxX = 0; @@ -259,6 +278,9 @@ function translateGraph(g) { graphLabel.height = maxY - minY + marginY; } +/** + * @param { Graph } g + */ function assignNodeIntersects(g) { _.forEach(g.edges(), function (e) { var edge = g.edge(e); @@ -278,6 +300,9 @@ function assignNodeIntersects(g) { }); } +/** + * @param { Graph } g + */ function fixupEdgeLabelCoords(g) { _.forEach(g.edges(), function (e) { var edge = g.edge(e); @@ -297,6 +322,9 @@ function fixupEdgeLabelCoords(g) { }); } +/** + * @param { Graph } g + */ function reversePointsForReversedEdges(g) { _.forEach(g.edges(), function (e) { var edge = g.edge(e); @@ -306,6 +334,9 @@ function reversePointsForReversedEdges(g) { }); } +/** + * @param { Graph } g + */ function removeBorderNodes(g) { _.forEach(g.nodes(), function (v) { if (g.children(v).length) { @@ -329,6 +360,9 @@ function removeBorderNodes(g) { }); } +/** + * @param { Graph } g + */ function removeSelfEdges(g) { _.forEach(g.edges(), function (e) { if (e.v === e.w) { @@ -342,6 +376,9 @@ function removeSelfEdges(g) { }); } +/** + * @param { Graph } g + */ function insertSelfEdges(g) { var layers = util.buildLayerMatrix(g); _.forEach(layers, function (layer) { @@ -369,6 +406,9 @@ function insertSelfEdges(g) { }); } +/** + * @param { Graph } g + */ function positionSelfEdges(g) { _.forEach(g.nodes(), function (v) { var node = g.node(v); diff --git a/src/dagre/nesting-graph.js b/src/dagre/nesting-graph.js index c346bb6..439ce68 100644 --- a/src/dagre/nesting-graph.js +++ b/src/dagre/nesting-graph.js @@ -1,9 +1,10 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; import * as util from './util.js'; -export { run, cleanup }; - -/* +/** * A nesting graph creates dummy nodes for the tops and bottoms of subgraphs, * adds appropriate edges to ensure that all cluster nodes are placed between * these boundries, and ensures that the graph is connected. @@ -25,8 +26,9 @@ export { run, cleanup }; * * The nesting graph idea comes from Sander, "Layout of Compound Directed * Graphs." + * @param { Graph } g */ -function run(g) { +export function run(g) { var root = util.addDummyNode(g, 'root', {}, '_root'); var depths = treeDepths(g); var height = _.max(_.values(depths)) - 1; // Note: depths is an Object not an array @@ -52,6 +54,9 @@ function run(g) { g.graph().nodeRankFactor = nodeSep; } +/** + * @param { Graph } g + */ function dfs(g, root, nodeSep, weight, height, depths, v) { var children = g.children(v); if (!children.length) { @@ -97,6 +102,9 @@ function dfs(g, root, nodeSep, weight, height, depths, v) { } } +/** + * @param { Graph } g + */ function treeDepths(g) { var depths = {}; function dfs(v, depth) { @@ -124,7 +132,10 @@ function sumWeights(g) { ); } -function cleanup(g) { +/** + * @param { Graph } g + */ +export function cleanup(g) { var graphLabel = g.graph(); g.removeNode(graphLabel.nestingRoot); delete graphLabel.nestingRoot; diff --git a/src/dagre/normalize.js b/src/dagre/normalize.js index de6b94c..73273f8 100644 --- a/src/dagre/normalize.js +++ b/src/dagre/normalize.js @@ -1,14 +1,10 @@ /** - * TypeScript type imports: - * * @import { Graph } from '../graphlib/graph.js'; */ import * as _ from 'lodash-es'; import * as util from './util.js'; -export { run, undo }; - -/* +/** * Breaks any long edges in the graph into short segments that span 1 layer * each. This operation is undoable with the denormalize function. * @@ -23,8 +19,9 @@ export { run, undo }; * 2. Dummy nodes are added where edges have been split into segments. * 3. The graph is augmented with a "dummyChains" attribute which contains * the first dummy in each chain of dummy nodes produced. + * @param { Graph } g */ -function run(g) { +export function run(g) { g.graph().dummyChains = []; _.forEach(g.edges(), function (edge) { normalizeEdge(g, edge); @@ -32,7 +29,7 @@ function run(g) { } /** - * @param {Graph} g + * @param { Graph } g */ function normalizeEdge(g, e) { var v = e.v; @@ -87,7 +84,10 @@ function normalizeEdge(g, e) { g.setEdge(v, w, { weight: edgeLabel.weight }, name); } -function undo(g) { +/** + * @param { Graph } g + */ +export function undo(g) { _.forEach(g.graph().dummyChains, function (v) { var node = g.node(v); var origLabel = node.edgeLabel; diff --git a/src/dagre/order/add-subgraph-constraints.js b/src/dagre/order/add-subgraph-constraints.js index f4efbc6..3c921d0 100644 --- a/src/dagre/order/add-subgraph-constraints.js +++ b/src/dagre/order/add-subgraph-constraints.js @@ -1,8 +1,12 @@ +/** + * @import { Graph } from '../../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; -export { addSubgraphConstraints }; - -function addSubgraphConstraints(g, cg, vs) { +/** + * @param { Graph } g + */ +export function addSubgraphConstraints(g, cg, vs) { var prev = {}, rootPrev; diff --git a/src/dagre/order/barycenter.js b/src/dagre/order/barycenter.js index 7dabefe..0528241 100644 --- a/src/dagre/order/barycenter.js +++ b/src/dagre/order/barycenter.js @@ -1,8 +1,12 @@ +/** + * @import { Graph } from '../../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; -export { barycenter }; - -function barycenter(g, movable) { +/** + * @param { Graph } g + */ +export function barycenter(g, movable) { return _.map(movable, function (v) { var inV = g.inEdges(v); if (!inV.length) { diff --git a/src/dagre/order/build-layer-graph.js b/src/dagre/order/build-layer-graph.js index 2ac36a7..5b09b58 100644 --- a/src/dagre/order/build-layer-graph.js +++ b/src/dagre/order/build-layer-graph.js @@ -1,9 +1,7 @@ import * as _ from 'lodash-es'; import { Graph } from '../../graphlib/index.js'; -export { buildLayerGraph }; - -/* +/** * Constructs a graph that can be used to sort a layer of nodes. The graph will * contain all base and subgraph nodes from the request layer in their original * hierarchy and any edges that are incident on these nodes and are of the type @@ -32,8 +30,9 @@ export { buildLayerGraph }; * parameter, are added to the output graph. * 5. The weights for copied edges are aggregated as need, since the output * graph is not a multi-graph. + * @param { Graph } g */ -function buildLayerGraph(g, rank, relationship) { +export function buildLayerGraph(g, rank, relationship) { var root = createRootNode(g), result = new Graph({ compound: true }) .setGraph({ root: root }) @@ -69,6 +68,9 @@ function buildLayerGraph(g, rank, relationship) { return result; } +/** + * @param { Graph } g + */ function createRootNode(g) { var v; while (g.hasNode((v = _.uniqueId('_root')))); diff --git a/src/dagre/order/cross-count.js b/src/dagre/order/cross-count.js index cf9d195..82332b8 100644 --- a/src/dagre/order/cross-count.js +++ b/src/dagre/order/cross-count.js @@ -1,8 +1,9 @@ +/** + * @import { Graph } from '../../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; -export { crossCount }; - -/* +/** * A function that takes a layering (an array of layers, each with an array of * ordererd nodes) and a graph and returns a weighted crossing count. * @@ -17,8 +18,9 @@ export { crossCount }; * 1. The graph and layering matrix are left unchanged. * * This algorithm is derived from Barth, et al., "Bilayer Cross Counting." + * @param { Graph } g */ -function crossCount(g, layering) { +export function crossCount(g, layering) { var cc = 0; for (var i = 1; i < layering.length; ++i) { cc += twoLayerCrossCount(g, layering[i - 1], layering[i]); @@ -26,6 +28,9 @@ function crossCount(g, layering) { return cc; } +/** + * @param { Graph } g + */ function twoLayerCrossCount(g, northLayer, southLayer) { // Sort all of the edges between the north and south layers by their position // in the north layer and then the south. Map these edges to the position of diff --git a/src/dagre/order/index.js b/src/dagre/order/index.js index f29508a..751386b 100644 --- a/src/dagre/order/index.js +++ b/src/dagre/order/index.js @@ -7,9 +7,7 @@ import { crossCount } from './cross-count.js'; import { initOrder } from './init-order.js'; import { sortSubgraph } from './sort-subgraph.js'; -export { order }; - -/* +/** * Applies heuristics to minimize edge crossings in the graph and sets the best * order solution as an order attribute on each node. * @@ -23,8 +21,9 @@ export { order }; * * 1. Graph nodes will have an "order" attribute based on the results of the * algorithm. + * @param { Graph } g */ -function order(g) { +export function order(g) { var maxRank = util.maxRank(g), downLayerGraphs = buildLayerGraphs(g, _.range(1, maxRank + 1), 'inEdges'), upLayerGraphs = buildLayerGraphs(g, _.range(maxRank - 1, -1, -1), 'outEdges'); @@ -50,6 +49,9 @@ function order(g) { assignOrder(g, best); } +/** + * @param { Graph } g + */ function buildLayerGraphs(g, ranks, relationship) { return _.map(ranks, function (rank) { return buildLayerGraph(g, rank, relationship); @@ -68,6 +70,9 @@ function sweepLayerGraphs(layerGraphs, biasRight) { }); } +/** + * @param { Graph } g + */ function assignOrder(g, layering) { _.forEach(layering, function (layer) { _.forEach(layer, function (v, i) { diff --git a/src/dagre/order/resolve-conflicts.js b/src/dagre/order/resolve-conflicts.js index cca18fa..b406ea2 100644 --- a/src/dagre/order/resolve-conflicts.js +++ b/src/dagre/order/resolve-conflicts.js @@ -1,7 +1,5 @@ import * as _ from 'lodash-es'; -export { resolveConflicts }; - /* * Given a list of entries of the form {v, barycenter, weight} and a * constraint graph this function will resolve any conflicts between the @@ -27,7 +25,7 @@ export { resolveConflicts }; * graph. The property `i` is the lowest original index of any of the * elements in `vs`. */ -function resolveConflicts(entries, cg) { +export function resolveConflicts(entries, cg) { var mappedEntries = {}; _.forEach(entries, function (entry, i) { var tmp = (mappedEntries[entry.v] = { diff --git a/src/dagre/order/sort-subgraph.js b/src/dagre/order/sort-subgraph.js index 2467304..c43731b 100644 --- a/src/dagre/order/sort-subgraph.js +++ b/src/dagre/order/sort-subgraph.js @@ -1,11 +1,15 @@ +/** + * @import { Graph } from '../../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; import { barycenter } from './barycenter.js'; import { resolveConflicts } from './resolve-conflicts.js'; import { sort } from './sort.js'; -export { sortSubgraph }; - -function sortSubgraph(g, v, cg, biasRight) { +/** + * @param { Graph } g + */ +export function sortSubgraph(g, v, cg, biasRight) { var movable = g.children(v); var node = g.node(v); var bl = node ? node.borderLeft : undefined; diff --git a/src/dagre/order/sort.js b/src/dagre/order/sort.js index 539978b..500a12b 100644 --- a/src/dagre/order/sort.js +++ b/src/dagre/order/sort.js @@ -1,9 +1,7 @@ import * as _ from 'lodash-es'; import * as util from '../util.js'; -export { sort }; - -function sort(entries, biasRight) { +export function sort(entries, biasRight) { var parts = util.partition(entries, function (entry) { return Object.prototype.hasOwnProperty.call(entry, 'barycenter'); }); diff --git a/src/dagre/parent-dummy-chains.js b/src/dagre/parent-dummy-chains.js index 367fd37..08bf615 100644 --- a/src/dagre/parent-dummy-chains.js +++ b/src/dagre/parent-dummy-chains.js @@ -1,8 +1,12 @@ +/** + * @import { Graph } from '../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; -export { parentDummyChains }; - -function parentDummyChains(g) { +/** + * @param { Graph } g + */ +export function parentDummyChains(g) { var postorderNums = postorder(g); _.forEach(g.graph().dummyChains, function (v) { @@ -46,6 +50,9 @@ function parentDummyChains(g) { // Find a path from v to w through the lowest common ancestor (LCA). Return the // full path and the LCA. +/** + * @param { Graph } g + */ function findPath(g, postorderNums, v, w) { var vPath = []; var wPath = []; @@ -71,6 +78,9 @@ function findPath(g, postorderNums, v, w) { return { path: vPath.concat(wPath.reverse()), lca: lca }; } +/** + * @param { Graph } g + */ function postorder(g) { var result = {}; var lim = 0; diff --git a/src/dagre/position/bk.js b/src/dagre/position/bk.js index d4aabdc..6f178fd 100644 --- a/src/dagre/position/bk.js +++ b/src/dagre/position/bk.js @@ -20,7 +20,7 @@ export { balance, }; -/* +/** * Marks all edges in the graph with a type-1 conflict with the "type1Conflict" * property. A type-1 conflict is one where a non-inner segment crosses an * inner segment. An inner segment is an edge with both incident nodes marked @@ -36,6 +36,7 @@ export { * * This algorithm (safely) assumes that a dummy node will only be incident on a * single node in the layers being scanned. + * @param { Graph } g */ function findType1Conflicts(g, layering) { var conflicts = {}; @@ -77,6 +78,9 @@ function findType1Conflicts(g, layering) { return conflicts; } +/** + * @param { Graph } g + */ function findType2Conflicts(g, layering) { var conflicts = {}; @@ -121,6 +125,9 @@ function findType2Conflicts(g, layering) { return conflicts; } +/** + * @param { Graph } g + */ function findOtherInnerSegmentNode(g, v) { if (g.node(v).dummy) { return _.find(g.predecessors(v), function (u) { @@ -152,13 +159,14 @@ function hasConflict(conflicts, v, w) { return !!conflicts[v] && Object.prototype.hasOwnProperty.call(conflicts[v], w); } -/* +/** * Try to align nodes into vertical "blocks" where possible. This algorithm * attempts to align a node with one of its median neighbors. If the edge * connecting a neighbor is a type-1 conflict then we ignore that possibility. * If a previous node has already formed a block with a node after the node * we're trying to form a block with, we also ignore that possibility - our * blocks would be split in that scenario. + * @param { Graph } g */ function verticalAlignment(g, layering, conflicts, neighborFn) { var root = {}, @@ -200,6 +208,9 @@ function verticalAlignment(g, layering, conflicts, neighborFn) { return { root: root, align: align }; } +/** + * @param { Graph } g + */ function horizontalCompaction(g, layering, root, align, reverseSep) { // This portion of the algorithm differs from BK due to a number of problems. // Instead of their algorithm we construct a new block graph and do two @@ -257,6 +268,9 @@ function horizontalCompaction(g, layering, root, align, reverseSep) { return xs; } +/** + * @param { Graph } g + */ function buildBlockGraph(g, layering, root, reverseSep) { var blockGraph = new Graph(), graphLabel = g.graph(), @@ -279,8 +293,9 @@ function buildBlockGraph(g, layering, root, reverseSep) { return blockGraph; } -/* +/** * Returns the alignment that has the smallest width of the given alignments. + * @param { Graph } g */ function findSmallestWidthAlignment(g, xss) { return _.minBy(_.values(xss), function (xs) { @@ -340,6 +355,9 @@ function balance(xss, align) { }); } +/** + * @param { Graph } g + */ function positionX(g) { var layering = util.buildLayerMatrix(g); var conflicts = _.merge(findType1Conflicts(g, layering), findType2Conflicts(g, layering)); @@ -418,6 +436,9 @@ function sep(nodeSep, edgeSep, reverseSep) { }; } +/** + * @param { Graph } g + */ function width(g, v) { return g.node(v).width; } diff --git a/src/dagre/position/index.js b/src/dagre/position/index.js index 68ee820..a70a575 100644 --- a/src/dagre/position/index.js +++ b/src/dagre/position/index.js @@ -1,10 +1,14 @@ +/** + * @import { Graph } from '../../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; import * as util from '../util.js'; import { positionX } from './bk.js'; -export { position }; - -function position(g) { +/** + * @param { Graph } g + */ +export function position(g) { g = util.asNonCompoundGraph(g); positionY(g); @@ -13,6 +17,9 @@ function position(g) { }); } +/** + * @param { Graph } g + */ function positionY(g) { var layering = util.buildLayerMatrix(g); var rankSep = g.graph().ranksep; diff --git a/src/dagre/rank/feasible-tree.js b/src/dagre/rank/feasible-tree.js index fdb2b01..1fa8d8c 100644 --- a/src/dagre/rank/feasible-tree.js +++ b/src/dagre/rank/feasible-tree.js @@ -2,9 +2,7 @@ import * as _ from 'lodash-es'; import { Graph } from '../../graphlib/index.js'; import { slack } from './util.js'; -export { feasibleTree }; - -/* +/** * Constructs a spanning tree with tight edges and adjusted the input node's * ranks to achieve this. A tight edge is one that is has a length that matches * its "minlen" attribute. @@ -28,8 +26,9 @@ export { feasibleTree }; * * Returns a tree (undirected graph) that is constructed using only "tight" * edges. + * @param { Graph } g */ -function feasibleTree(g) { +export function feasibleTree(g) { var t = new Graph({ directed: false }); // Choose arbitrary node from which to start our tree @@ -47,9 +46,10 @@ function feasibleTree(g) { return t; } -/* +/** * Finds a maximal tree of tight edges and returns the number of nodes in the * tree. + * @param { Graph } g */ function tightTree(t, g) { function dfs(v) { @@ -68,9 +68,10 @@ function tightTree(t, g) { return t.nodeCount(); } -/* +/** * Finds the edge with the smallest slack that is incident on tree and returns * it. + * @param { Graph } g */ function findMinSlackEdge(t, g) { return _.minBy(g.edges(), function (e) { @@ -80,6 +81,9 @@ function findMinSlackEdge(t, g) { }); } +/** + * @param { Graph } g + */ function shiftRanks(t, g, delta) { _.forEach(t.nodes(), function (v) { g.node(v).rank += delta; diff --git a/src/dagre/rank/index.js b/src/dagre/rank/index.js index 92022fb..053b514 100644 --- a/src/dagre/rank/index.js +++ b/src/dagre/rank/index.js @@ -1,10 +1,11 @@ +/** + * @import { Graph } from '../../graphlib/graph.js'; + */ import { feasibleTree } from './feasible-tree.js'; import { networkSimplex } from './network-simplex.js'; import { longestPath } from './util.js'; -export { rank }; - -/* +/** * Assigns a rank to each node in the input graph that respects the "minlen" * constraint specified on edges between nodes. * @@ -22,8 +23,9 @@ export { rank }; * 1. Graph nodes will have a "rank" attribute based on the results of the * algorithm. Ranks can start at any index (including negative), we'll * fix them up later. + * @param { Graph } g */ -function rank(g) { +export function rank(g) { switch (g.graph().ranker) { case 'network-simplex': networkSimplexRanker(g); @@ -42,11 +44,17 @@ function rank(g) { // A fast and simple ranker, but results are far from optimal. var longestPathRanker = longestPath; +/** + * @param { Graph } g + */ function tightTreeRanker(g) { longestPath(g); feasibleTree(g); } +/** + * @param { Graph } g + */ function networkSimplexRanker(g) { networkSimplex(g); } diff --git a/src/dagre/rank/network-simplex.js b/src/dagre/rank/network-simplex.js index e4913e8..79e3fa1 100644 --- a/src/dagre/rank/network-simplex.js +++ b/src/dagre/rank/network-simplex.js @@ -1,3 +1,6 @@ +/** + * @import { Graph } from '../../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; import * as alg from '../../graphlib/alg/index.js'; import { simplify } from '../util.js'; @@ -14,7 +17,7 @@ networkSimplex.leaveEdge = leaveEdge; networkSimplex.enterEdge = enterEdge; networkSimplex.exchangeEdges = exchangeEdges; -/* +/** * The network simplex algorithm assigns ranks to each node in the input graph * and iteratively improves the ranking to reduce the length of edges. * @@ -46,6 +49,7 @@ networkSimplex.exchangeEdges = exchangeEdges; * Much of the algorithms here are derived from Gansner, et al., "A Technique * for Drawing Directed Graphs." The structure of the file roughly follows the * structure of the overall algorithm. + * @param { Graph } g */ function networkSimplex(g) { g = simplify(g); @@ -61,8 +65,9 @@ function networkSimplex(g) { } } -/* +/** * Initializes cut values for all edges in the tree. + * @param { Graph } g */ function initCutValues(t, g) { var vs = alg.postorder(t, t.nodes()); @@ -72,15 +77,19 @@ function initCutValues(t, g) { }); } +/** + * @param { Graph } g + */ function assignCutValue(t, g, child) { var childLab = t.node(child); var parent = childLab.parent; t.edge(child, parent).cutvalue = calcCutValue(t, g, child); } -/* +/** * Given the tight tree, its graph, and a child in the graph calculate and * return the cut value for the edge between the child and its parent. + * @param { Graph } g */ function calcCutValue(t, g, child) { var childLab = t.node(child); @@ -154,6 +163,9 @@ function leaveEdge(tree) { }); } +/** + * @param { Graph } g + */ function enterEdge(t, g, edge) { var v = edge.v; var w = edge.w; @@ -190,6 +202,9 @@ function enterEdge(t, g, edge) { }); } +/** + * @param { Graph } g + */ function exchangeEdges(t, g, e, f) { var v = e.v; var w = e.w; @@ -200,6 +215,9 @@ function exchangeEdges(t, g, e, f) { updateRanks(t, g); } +/** + * @param { Graph } g + */ function updateRanks(t, g) { var root = _.find(t.nodes(), function (v) { return !g.node(v).parent; diff --git a/src/dagre/rank/util.js b/src/dagre/rank/util.js index 8759a9f..fa73df0 100644 --- a/src/dagre/rank/util.js +++ b/src/dagre/rank/util.js @@ -1,8 +1,9 @@ +/** + * @import { Graph } from '../../graphlib/graph.js'; + */ import * as _ from 'lodash-es'; -export { longestPath, slack }; - -/* +/** * Initializes ranks for the input graph using the longest path algorithm. This * algorithm scales well and is fast in practice, it yields rather poor * solutions. Nodes are pushed to the lowest layer possible, leaving the bottom @@ -22,8 +23,9 @@ export { longestPath, slack }; * Post-conditions: * * 1. Each node will be assign an (unnormalized) "rank" property. + * @param { Graph } g */ -function longestPath(g) { +export function longestPath(g) { var visited = {}; function dfs(v) { @@ -54,10 +56,11 @@ function longestPath(g) { _.forEach(g.sources(), dfs); } -/* +/** * Returns the amount of slack for the given edge. The slack is defined as the * difference between the length of the edge and its minimum length. + * @param { Graph } g */ -function slack(g, e) { +export function slack(g, e) { return g.node(e.w).rank - g.node(e.v).rank - g.edge(e).minlen; } diff --git a/src/dagre/util.js b/src/dagre/util.js index f526e82..e973ae2 100644 --- a/src/dagre/util.js +++ b/src/dagre/util.js @@ -18,8 +18,9 @@ export { notime, }; -/* +/** * Adds a dummy node to the graph and return v. + * @param { Graph } g */ function addDummyNode(g, type, attrs, name) { var v; @@ -32,9 +33,10 @@ function addDummyNode(g, type, attrs, name) { return v; } -/* +/** * Returns a new graph with only simple edges. Handles aggregation of data * associated with multi-edges. + * @param { Graph } g */ function simplify(g) { var simplified = new Graph().setGraph(g.graph()); @@ -52,6 +54,9 @@ function simplify(g) { return simplified; } +/** + * @param { Graph } g + */ function asNonCompoundGraph(g) { var simplified = new Graph({ multigraph: g.isMultigraph() }).setGraph(g.graph()); _.forEach(g.nodes(), function (v) { @@ -65,6 +70,9 @@ function asNonCompoundGraph(g) { return simplified; } +/** + * @param { Graph } g + */ function successorWeights(g) { var weightMap = _.map(g.nodes(), function (v) { var sucs = {}; @@ -76,6 +84,9 @@ function successorWeights(g) { return _.zipObject(g.nodes(), weightMap); } +/** + * @param { Graph } g + */ function predecessorWeights(g) { var weightMap = _.map(g.nodes(), function (v) { var preds = {}; @@ -126,9 +137,10 @@ function intersectRect(rect, point) { return { x: x + sx, y: y + sy }; } -/* +/** * Given a DAG with each node assigned "rank" and "order" properties, this * function will produce a matrix with the ids of each node. + * @param { Graph } g */ function buildLayerMatrix(g) { var layering = _.map(_.range(maxRank(g) + 1), function () { @@ -144,9 +156,10 @@ function buildLayerMatrix(g) { return layering; } -/* +/** * Adjusts the ranks for all nodes in the graph such that all nodes v have * rank(v) >= 0 and at least one node w has rank(w) = 0. + * @param { Graph } g */ function normalizeRanks(g) { var min = _.min( @@ -162,6 +175,9 @@ function normalizeRanks(g) { }); } +/** + * @param { Graph } g + */ function removeEmptyRanks(g) { // Ranks may not start at 0, so we need to offset them var offset = _.min( @@ -192,6 +208,9 @@ function removeEmptyRanks(g) { }); } +/** + * @param { Graph } g + */ function addBorderNode(g, prefix, rank, order) { var node = { width: 0, @@ -204,6 +223,9 @@ function addBorderNode(g, prefix, rank, order) { return addDummyNode(g, 'border', node, prefix); } +/** + * @param { Graph } g + */ function maxRank(g) { return _.max( _.map(g.nodes(), function (v) { diff --git a/src/graphlib/alg/dfs.js b/src/graphlib/alg/dfs.js index 4278570..8029fc8 100644 --- a/src/graphlib/alg/dfs.js +++ b/src/graphlib/alg/dfs.js @@ -1,14 +1,18 @@ +/** + * @import { Graph } from '../graph.js'; + */ import * as _ from 'lodash-es'; export { dfs }; -/* +/** * A helper that preforms a pre- or post-order traversal on the input graph * and returns the nodes in the order they were visited. If the graph is * undirected then this algorithm will navigate using neighbors. If the graph * is directed then this algorithm will navigate using successors. * * Order must be one of "pre" or "post". + * @param { Graph } g */ function dfs(g, vs, order) { if (!_.isArray(vs)) { @@ -29,6 +33,9 @@ function dfs(g, vs, order) { return acc; } +/** + * @param { Graph } g + */ function doDfs(g, v, postorder, visited, navigation, acc) { if (!Object.prototype.hasOwnProperty.call(visited, v)) { visited[v] = true; diff --git a/src/graphlib/alg/dijkstra-all.js b/src/graphlib/alg/dijkstra-all.js index 7363874..d200d2f 100644 --- a/src/graphlib/alg/dijkstra-all.js +++ b/src/graphlib/alg/dijkstra-all.js @@ -1,9 +1,13 @@ +/** + * @import { Graph } from '../graph.js'; + */ import * as _ from 'lodash-es'; import { dijkstra } from './dijkstra.js'; -export { dijkstraAll }; - -function dijkstraAll(g, weightFunc, edgeFunc) { +/** + * @param { Graph } g + */ +export function dijkstraAll(g, weightFunc, edgeFunc) { return _.transform( g.nodes(), function (acc, v) { diff --git a/src/graphlib/alg/dijkstra.js b/src/graphlib/alg/dijkstra.js index 55a70a3..f3032b7 100644 --- a/src/graphlib/alg/dijkstra.js +++ b/src/graphlib/alg/dijkstra.js @@ -1,3 +1,6 @@ +/** + * @import { Graph } from '../graph.js'; + */ import * as _ from 'lodash-es'; import { PriorityQueue } from '../data/priority-queue.js'; @@ -5,6 +8,9 @@ export { dijkstra }; var DEFAULT_WEIGHT_FUNC = _.constant(1); +/** + * @param { Graph } g + */ function dijkstra(g, source, weightFn, edgeFn) { return runDijkstra( g, @@ -17,6 +23,9 @@ function dijkstra(g, source, weightFn, edgeFn) { ); } +/** + * @param { Graph } g + */ function runDijkstra(g, source, weightFn, edgeFn) { var results = {}; var pq = new PriorityQueue(); diff --git a/src/graphlib/alg/floyd-warshall.js b/src/graphlib/alg/floyd-warshall.js index 495331e..8c71d77 100644 --- a/src/graphlib/alg/floyd-warshall.js +++ b/src/graphlib/alg/floyd-warshall.js @@ -1,10 +1,14 @@ +/** + * @import { Graph } from '../graph.js'; + */ import * as _ from 'lodash-es'; -export { floydWarshall }; - var DEFAULT_WEIGHT_FUNC = _.constant(1); -function floydWarshall(g, weightFn, edgeFn) { +/** + * @param { Graph } g + */ +export function floydWarshall(g, weightFn, edgeFn) { return runFloydWarshall( g, weightFn || DEFAULT_WEIGHT_FUNC, @@ -15,6 +19,9 @@ function floydWarshall(g, weightFn, edgeFn) { ); } +/** + * @param { Graph } g + */ function runFloydWarshall(g, weightFn, edgeFn) { var results = {}; var nodes = g.nodes(); diff --git a/src/graphlib/alg/postorder.js b/src/graphlib/alg/postorder.js index a94dafe..e7d146d 100644 --- a/src/graphlib/alg/postorder.js +++ b/src/graphlib/alg/postorder.js @@ -1,7 +1,13 @@ +/** + * @import { Graph } from '../graph.js'; + */ import { dfs } from './dfs.js'; export { postorder }; +/** + * @param { Graph } g + */ function postorder(g, vs) { return dfs(g, vs, 'post'); } diff --git a/src/graphlib/alg/preorder.js b/src/graphlib/alg/preorder.js index b623f05..b96f22c 100644 --- a/src/graphlib/alg/preorder.js +++ b/src/graphlib/alg/preorder.js @@ -1,7 +1,11 @@ +/** + * @import { Graph } from '../graph.js'; + */ import { dfs } from './dfs.js'; -export { preorder }; - -function preorder(g, vs) { +/** + * @param { Graph } g + */ +export function preorder(g, vs) { return dfs(g, vs, 'pre'); } diff --git a/src/graphlib/alg/prim.js b/src/graphlib/alg/prim.js index 59a7cc5..eb733a6 100644 --- a/src/graphlib/alg/prim.js +++ b/src/graphlib/alg/prim.js @@ -4,6 +4,9 @@ import { Graph } from '../graph.js'; export { prim }; +/** + * @param { Graph } g + */ function prim(g, weightFunc) { var result = new Graph(); var parents = {}; diff --git a/src/graphlib/graph.js b/src/graphlib/graph.js index 5eca3f7..d7e9544 100644 --- a/src/graphlib/graph.js +++ b/src/graphlib/graph.js @@ -4,15 +4,29 @@ var DEFAULT_EDGE_NAME = '\x00'; var GRAPH_NODE = '\x00'; var EDGE_KEY_DELIM = '\x01'; -// Implementation notes: -// -// * Node id query functions should return string ids for the nodes -// * Edge id query functions should return an "edgeObj", edge object, that is -// composed of enough information to uniquely identify an edge: {v, w, name}. -// * Internally we use an "edgeId", a stringified form of the edgeObj, to -// reference edges. This is because we need a performant way to look these -// edges up and, object properties, which have string keys, are the closest -// we're going to get to a performant hashtable in JavaScript. +/** + * @typedef {Object} Edge + * @property {string} [v] + * @property {string} [w] + * @property {string} [name] - The name that uniquely identifies a multi-edge. + * + * @typedef {Object} GraphOptions + * @property {boolean} [directed=true] + * @property {boolean} [multigraph=false] + * @property {boolean} [compound=false] + * + * @typedef {Object} Node + * @property {string} label - The label of the node. + * @property {number} [paddingX] - The horizontal padding of the node. + * @property {number} [paddingY] - The vertical padding of the node. + * @property {number} [padding] - The padding of the node for all directions. Overrides `paddingX` and `paddingY`. + * @property {number} [paddingLeft] - The left padding of the node. + * @property {number} [paddingRight] - The right padding of the node. + * @property {number} [_prevWidth] + * @property {number} [width] + * @property {number} [_prevHeight] + * @property {number} [height] + */ // Implementation notes: // @@ -24,13 +38,16 @@ var EDGE_KEY_DELIM = '\x01'; // edges up and, object properties, which have string keys, are the closest // we're going to get to a performant hashtable in JavaScript. export class Graph { - constructor(opts = {}) { + constructor(/** @type {GraphOptions} */ opts = {}) { + /** @type {boolean} */ this._isDirected = Object.prototype.hasOwnProperty.call(opts, 'directed') ? opts.directed : true; + /** @type {boolean} */ this._isMultigraph = Object.prototype.hasOwnProperty.call(opts, 'multigraph') ? opts.multigraph : false; + /** @type {boolean} */ this._isCompound = Object.prototype.hasOwnProperty.call(opts, 'compound') ? opts.compound : false; @@ -69,6 +86,10 @@ export class Graph { this._sucs = {}; // e -> edgeObj + /** + * Edge objects for each edge. + * @type {Object} + */ this._edgeObjs = {}; // e -> label @@ -276,7 +297,6 @@ export class Graph { }); _.each(this._edgeObjs, function (e) { - // @ts-expect-error if (copy.hasNode(e.v) && copy.hasNode(e.w)) { copy.setEdge(e, self.edge(e)); } @@ -383,6 +403,7 @@ export class Graph { // @ts-expect-error this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name); + /** @type {Edge} */ var edgeObj = edgeArgsToObj(this._isDirected, v, w, name); // Ensure we add undirected edges in a consistent way. v = edgeObj.v; @@ -482,6 +503,13 @@ function decrementOrRemoveEntry(map, k) { } } +/** + * @param {boolean} isDirected + * @param {string} v_ + * @param {string} w_ + * @param {string} name + * @returns {string} + */ function edgeArgsToId(isDirected, v_, w_, name) { var v = '' + v_; var w = '' + w_; @@ -493,6 +521,13 @@ function edgeArgsToId(isDirected, v_, w_, name) { return v + EDGE_KEY_DELIM + w + EDGE_KEY_DELIM + (_.isUndefined(name) ? DEFAULT_EDGE_NAME : name); } +/** + * @param {boolean} isDirected + * @param {string} v_ + * @param {string} w_ + * @param {string} name + * @returns {Edge} + */ function edgeArgsToObj(isDirected, v_, w_, name) { var v = '' + v_; var w = '' + w_; @@ -508,6 +543,6 @@ function edgeArgsToObj(isDirected, v_, w_, name) { return edgeObj; } -function edgeObjToId(isDirected, edgeObj) { +function edgeObjToId(/** @type {boolean} */ isDirected, /** @type {Edge} */ edgeObj) { return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name); } From 83d5efe3d081971056a936e46ffdacda076e7f24 Mon Sep 17 00:00:00 2001 From: tbo47 Date: Mon, 7 Oct 2024 04:59:53 +0000 Subject: [PATCH 2/2] add types --- src/graphlib/graph.js | 275 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 273 insertions(+), 2 deletions(-) diff --git a/src/graphlib/graph.js b/src/graphlib/graph.js index d7e9544..667a0c7 100644 --- a/src/graphlib/graph.js +++ b/src/graphlib/graph.js @@ -52,7 +52,10 @@ export class Graph { ? opts.compound : false; - // Label for the graph itself + /** + * Label for the graph itself + * @type {string | undefined} + */ this._label = undefined; // Defaults to be set when creating a new node @@ -62,10 +65,12 @@ export class Graph { this._defaultEdgeLabelFn = _.constant(undefined); // v -> label + /** @type {Object} */ this._nodes = {}; if (this._isCompound) { // v -> parent + /** @type {Object} */ this._parent = {}; // v -> children @@ -74,9 +79,11 @@ export class Graph { } // v -> edgeObj + /** @type {Object>} */ this._in = {}; // u -> v -> Number + /* @type {Object>} */ this._preds = {}; // v -> edgeObj @@ -93,26 +100,72 @@ export class Graph { this._edgeObjs = {}; // e -> label + /** @type {Object} */ this._edgeLabels = {}; } /* === Graph functions ========= */ + + /** + * Whether graph was created with 'directed' flag set to true or not. + * + * @returns whether the graph edges have an orientation. + */ isDirected() { return this._isDirected; } + + /** + * Whether graph was created with 'multigraph' flag set to true or not. + * + * @returns whether the pair of nodes of the graph can have multiple edges. + */ isMultigraph() { return this._isMultigraph; } + + /** + * Whether graph was created with 'compound' flag set to true or not. + * + * @returns whether a node of the graph can have subnodes. + */ isCompound() { return this._isCompound; } + + /** + * Sets the label of the graph. + * + * @argument label - label value. + * @returns the graph, allowing this to be chained with other functions. + */ setGraph(label) { this._label = label; return this; } + + /** + * Gets the graph label. + * + * @returns {string | undefined} currently assigned label for the graph or undefined if no label assigned. + */ graph() { return this._label; } /* === Node functions ========== */ + + /** + * Sets the default node label. This label will be assigned as default label + * in case if no label was specified while setting a node. + * Complexity: O(1). + * + * Sets the default node label factory function. This function will be invoked + * each time when setting a node with no label specified and returned value + * will be used as a label for node. + * Complexity: O(1). + * + * @argument newDefault - default node label. + * @returns the graph, allowing this to be chained with other functions. + */ setDefaultNodeLabel(newDefault) { if (!_.isFunction(newDefault)) { newDefault = _.constant(newDefault); @@ -123,6 +176,14 @@ export class Graph { nodeCount() { return this._nodeCount; } + + /** + * Gets all nodes of the graph. Note, the in case of compound graph subnodes are + * not included in list. + * Complexity: O(1). + * + * @returns {string[]} list of graph nodes. + */ nodes() { return _.keys(this._nodes); } @@ -138,6 +199,15 @@ export class Graph { return _.isEmpty(self._out[v]); }); } + + /** + * Invokes setNode method for each node in names list. + * Complexity: O(|names|). + * + * @argument {string[]} vs - list of nodes names to be set. + * @argument {string} value - value to set for each node in list. + * @returns the graph, allowing this to be chained with other functions. + */ setNodes(vs, value) { var args = arguments; var self = this; @@ -150,6 +220,17 @@ export class Graph { }); return this; } + + /** + * Creates or updates the value for the node v in the graph. If label is supplied + * it is set as the value for the node. If label is not supplied and the node was + * created by this call then the default node label will be assigned. + * Complexity: O(1). + * + * @argument {string} v - node name. + * @argument {string} [value] - value to set for node. + * @returns {Graph} the graph, allowing this to be chained with other functions. + */ setNode(v, value) { if (Object.prototype.hasOwnProperty.call(this._nodes, v)) { if (arguments.length > 1) { @@ -172,12 +253,37 @@ export class Graph { ++this._nodeCount; return this; } + + /** + * Gets the label of node with specified name. + * Complexity: O(|V|). + * + * @argument {string} v - name of the node. + * @returns label value of the node. + */ node(v) { return this._nodes[v]; } + + /** + * Detects whether graph has a node with specified name or not. + * + * @argument {string} v - name of the node. + * @returns {boolean} true if graph has node with specified name, false - otherwise. + */ hasNode(v) { return Object.prototype.hasOwnProperty.call(this._nodes, v); } + + /** + * Remove the node with the name from the graph or do nothing if the node is not in + * the graph. If the node was removed this function also removes any incident + * edges. + * Complexity: O(1). + * + * @argument {string} v - name of the node. + * @returns {Graph} the graph, allowing this to be chained with other functions. + */ removeNode(v) { if (Object.prototype.hasOwnProperty.call(this._nodes, v)) { var removeEdge = (e) => this.removeEdge(this._edgeObjs[e]); @@ -200,6 +306,17 @@ export class Graph { } return this; } + + /** + * Sets node p as a parent for node v if it is defined, or removes the + * parent for v if p is undefined. Method throws an exception in case of + * invoking it in context of noncompound graph. + * Average-case complexity: O(1). + * + * @argument {string} v - node to be child for p. + * @argument {string} [parent] - node to be parent for v. + * @returns {Graph} the graph, allowing this to be chained with other functions. + */ setParent(v, parent) { if (!this._isCompound) { throw new Error('Cannot set parent in a non-compound graph'); @@ -228,6 +345,14 @@ export class Graph { _removeFromParentsChildList(v) { delete this._children[this._parent[v]][v]; } + + /** + * Gets parent node for node v. + * Complexity: O(1). + * + * @argument {string} v - node to get parent of. + * @returns {string | undefined} parent node name or void if v has no parent. + */ parent(v) { if (this._isCompound) { var parent = this._parent[v]; @@ -236,6 +361,14 @@ export class Graph { } } } + + /** + * Gets list of direct children of node v. + * Complexity: O(1). + * + * @argument {string} [v] - node to get children of. + * @returns {string[]} children nodes names list. + */ children(v) { if (_.isUndefined(v)) { v = GRAPH_NODE; @@ -252,18 +385,45 @@ export class Graph { return []; } } + + /** + * Return all nodes that are predecessors of the specified node or undefined if node v is not in + * the graph. Behavior is undefined for undirected graphs - use neighbors instead. + * Complexity: O(|V|). + * + * @argument {string} v - node identifier. + * @returns {undefined | string[]} node identifiers list or undefined if v is not in the graph. + */ predecessors(v) { var predsV = this._preds[v]; if (predsV) { return _.keys(predsV); } } + + /** + * Return all nodes that are successors of the specified node or undefined if node v is not in + * the graph. Behavior is undefined for undirected graphs - use neighbors instead. + * Complexity: O(|V|). + * + * @argument {string} v - node identifier. + * @returns {undefined | string[]} node identifiers list or undefined if v is not in the graph. + */ successors(v) { var sucsV = this._sucs[v]; if (sucsV) { return _.keys(sucsV); } } + + /** + * Return all nodes that are predecessors or successors of the specified node or undefined if + * node v is not in the graph. + * Complexity: O(|V|). + * + * @argument {string} v - node identifier. + * @returns {undefined | string[]} node identifiers list or undefined if v is not in the graph. + */ neighbors(v) { var preds = this.predecessors(v); if (preds) { @@ -279,6 +439,16 @@ export class Graph { } return neighbors.length === 0; } + + /** + * Creates new graph with nodes filtered via filter. Edges incident to rejected node + * are also removed. In case of compound graph, if parent is rejected by filter, + * than all its children are rejected too. + * Average-case complexity: O(|E|+|V|). + * + * @argument {Function} filter - filtration function detecting whether the node should stay or not. + * @returns {string} new graph made from current and nodes filtered. + */ filterNodes(filter) { // @ts-expect-error var copy = new this.constructor({ @@ -302,6 +472,7 @@ export class Graph { } }); + /** @type {Object} */ var parents = {}; function findParent(v) { var parent = self.parent(v); @@ -324,6 +495,15 @@ export class Graph { return copy; } /* === Edge functions ========== */ + + /** + * Sets the default edge label. This label will be assigned as default label + * in case if no label was specified while setting an edge. + * Complexity: O(1). + * + * @argument newDefault - default edge label. String or function. + * @returns {Graph} the graph, allowing this to be chained with other functions. + */ setDefaultEdgeLabel(newDefault) { if (!_.isFunction(newDefault)) { newDefault = _.constant(newDefault); @@ -334,10 +514,29 @@ export class Graph { edgeCount() { return this._edgeCount; } + + /** + * Gets edges of the graph. In case of compound graph subgraphs are not considered. + * Complexity: O(|E|). + * + * @return {Edge[]} graph edges list. + */ edges() { return _.values(this._edgeObjs); } + + /** + * Establish an edges path over the nodes in nodes list. If some edge is already + * exists, it will update its label, otherwise it will create an edge between pair + * of nodes with label provided or default label if no label provided. + * Complexity: O(|nodes|). + * + * @argument {string[]} vs - list of nodes to be connected in series. + * @argument [value] - value to set for each edge between pairs of nodes. + * @returns {Graph} the graph, allowing this to be chained with other functions. + */ setPath(vs, value) { + /** @type Graph */ var self = this; var args = arguments; _.reduce(vs, function (v, w) { @@ -353,6 +552,18 @@ export class Graph { /* * setEdge(v, w, [value, [name]]) * setEdge({ v, w, [name] }, [value]) + * + * Creates or updates the label for the edge (v, w) with the optionally supplied + * name. If label is supplied it is set as the value for the edge. If label is not + * supplied and the edge was created by this call then the default edge label will + * be assigned. The name parameter is only useful with multigraphs. + * Complexity: O(1). + * + * @argument v - edge source node. + * @argument w - edge sink node. + * @argument label - value to associate with the edge. + * @argument name - unique name of the edge in order to identify it in multigraph. + * @returns the graph, allowing this to be chained with other functions. */ setEdge() { var v, w, name, value; @@ -418,6 +629,16 @@ export class Graph { this._edgeCount++; return this; } + + /** + * Gets the label for the specified edge. + * Complexity: O(1). + * + * @argument {Edge} v - edge source node. + * @argument {string} [w] - edge sink node. + * @argument {string} [name] - name of the edge (actual for multigraph). + * @returns value associated with specified edge. + */ edge(v, w, name) { var e = arguments.length === 1 @@ -425,6 +646,16 @@ export class Graph { : edgeArgsToId(this._isDirected, v, w, name); return this._edgeLabels[e]; } + + /** + * Detects whether the graph contains specified edge or not. No subgraphs are considered. + * Complexity: O(1). + * + * @argument {Edge | string} v - edge source node. + * @argument {string} [w] - edge sink node. + * @argument {string} [name] - name of the edge (actual for multigraph). + * @returns whether the graph contains the specified edge or not. + */ hasEdge(v, w, name) { var e = arguments.length === 1 @@ -432,6 +663,16 @@ export class Graph { : edgeArgsToId(this._isDirected, v, w, name); return Object.prototype.hasOwnProperty.call(this._edgeLabels, e); } + + /** + * Removes the specified edge from the graph. No subgraphs are considered. + * Complexity: O(1). + * + * @argument {Edge | string} v - edge source node. + * @argument {string} [w] - edge sink node. + * @argument {string} [name] - name of the edge (actual for multigraph). + * @returns the graph, allowing this to be chained with other functions. + */ removeEdge(v, w, name) { var e = arguments.length === 1 @@ -451,6 +692,16 @@ export class Graph { } return this; } + + /** + * Return all edges that point to the node v. Optionally filters those edges down to just those + * coming from node u. Behavior is undefined for undirected graphs - use nodeEdges instead. + * Complexity: O(|E|). + * + * @argument {string} v - edge sink node. + * @argument {string} [u] - edge source node. + * @returns {Edge[] | undefined} edges descriptors list if v is in the graph, or undefined otherwise. + */ inEdges(v, u) { var inV = this._in[v]; if (inV) { @@ -463,6 +714,16 @@ export class Graph { }); } } + + /** + * Return all edges that are pointed at by node v. Optionally filters those edges down to just + * those point to w. Behavior is undefined for undirected graphs - use nodeEdges instead. + * Complexity: O(|E|). + * + * @argument {string} v - edge sink node. + * @argument {string} [w] - edge source node. + * @returns {Edge[] | undefined} edges descriptors list if v is in the graph, or undefined otherwise. + */ outEdges(v, w) { var outV = this._out[v]; if (outV) { @@ -475,6 +736,16 @@ export class Graph { }); } } + + /** + * Returns all edges to or from node v regardless of direction. Optionally filters those edges + * down to just those between nodes v and w regardless of direction. + * Complexity: O(|E|). + * + * @argument {string} v - edge adjacent node. + * @argument {string} [w] - edge adjacent node. + * @returns {undefined | Edge[]} edges descriptors list if v is in the graph, or undefined otherwise. + */ nodeEdges(v, w) { var inEdges = this.inEdges(v, w); if (inEdges) { @@ -505,7 +776,7 @@ function decrementOrRemoveEntry(map, k) { /** * @param {boolean} isDirected - * @param {string} v_ + * @param {string | Edge} v_ * @param {string} w_ * @param {string} name * @returns {string}