Lutz Roeder 4 лет назад
Родитель
Сommit
73e99b649b
1 измененных файлов с 123 добавлено и 100 удалено
  1. 123 100
      source/dagre.js

+ 123 - 100
source/dagre.js

@@ -36,7 +36,7 @@ dagre.layout = (graph, options) => {
         for (const e of graph.edges()) {
             const edge = graph.edge(e);
             g.setEdge(e, {
-                minlen: edge.minlin || 1,
+                minlen: edge.minlen || 1,
                 weight: edge.weight || 1,
                 width: edge.width || 0,
                 height: edge.height || 0,
@@ -124,14 +124,9 @@ dagre.layout = (graph, options) => {
             return layering;
         };
 
-        /*
-        * This idea comes from the Gansner paper: to account for edge labels in our
-        * layout we split each rank in half by doubling minlen and halving ranksep.
-        * Then we can place labels at these mid-points between nodes.
-        *
-        * We also add some minimal padding to the width to push the label for the edge
-        * away from the edge itself a bit.
-        */
+        // This idea comes from the Gansner paper: to account for edge labels in our layout we split each rank in half by doubling minlen and halving ranksep.
+        // Then we can place labels at these mid-points between nodes.
+        // We also add some minimal padding to the width to push the label for the edge away from the edge itself a bit.
         const makeSpaceForEdgeLabels = (g) => {
             const graph = g.graph();
             graph.ranksep /= 2;
@@ -235,13 +230,13 @@ dagre.layout = (graph, options) => {
             function greedyFAS(g, weightFn) {
                 const assignBucket = (buckets, zeroIdx, entry) => {
                     if (!entry.out) {
-                        buckets[0].enqueue(entry);
+                        buckets[0].push(entry);
                     }
                     else if (!entry['in']) {
-                        buckets[buckets.length - 1].enqueue(entry);
+                        buckets[buckets.length - 1].push(entry);
                     }
                     else {
-                        buckets[entry.out - entry['in'] + zeroIdx].enqueue(entry);
+                        buckets[entry.out - entry['in'] + zeroIdx].push(entry);
                     }
                 };
                 const buildState = (g, weightFn) => {
@@ -270,7 +265,7 @@ dagre.layout = (graph, options) => {
                 const doGreedyFAS = (g, buckets, zeroIdx) => {
                     const removeNode = (g, buckets, zeroIdx, entry, collectPredecessors) => {
                         const results = collectPredecessors ? [] : undefined;
-                        for (const edge of g.inEdges(entry.v)) {
+                        for (const edge of g.inEdges(entry.v) || []) {
                             const weight = g.edge(edge);
                             const uEntry = g.node(edge.v);
                             if (collectPredecessors) {
@@ -279,7 +274,7 @@ dagre.layout = (graph, options) => {
                             uEntry.out -= weight;
                             assignBucket(buckets, zeroIdx, uEntry);
                         }
-                        for (const edge of g.outEdges(entry.v)) {
+                        for (const edge of g.outEdges(entry.v) || []) {
                             const weight = g.edge(edge);
                             const w = edge.w;
                             const wEntry = g.node(w);
@@ -294,11 +289,15 @@ dagre.layout = (graph, options) => {
                     let results = [];
                     let entry;
                     while (g.nodeCount()) {
-                        while ((entry = sinks.dequeue()))   { removeNode(g, buckets, zeroIdx, entry); }
-                        while ((entry = sources.dequeue())) { removeNode(g, buckets, zeroIdx, entry); }
+                        while ((entry = sinks.shift())) {
+                            removeNode(g, buckets, zeroIdx, entry);
+                        }
+                        while ((entry = sources.shift())) {
+                            removeNode(g, buckets, zeroIdx, entry);
+                        }
                         if (g.nodeCount()) {
                             for (let i = buckets.length - 2; i > 0; --i) {
-                                entry = buckets[i].dequeue();
+                                entry = buckets[i].shift();
                                 if (entry) {
                                     results = results.concat(removeNode(g, buckets, zeroIdx, entry, true));
                                     break;
@@ -706,12 +705,8 @@ dagre.layout = (graph, options) => {
             }
         };
 
-        /*
-        * Creates temporary dummy nodes that capture the rank in which each edge's
-        * label is going to, if it has one of non-zero width and height. We do this
-        * so that we can safely remove empty ranks while preserving balance for the
-        * label's position.
-        */
+        // Creates temporary dummy nodes that capture the rank in which each edge's label is going to, if it has one of non-zero width and height.
+        // We do this so that we can safely remove empty ranks while preserving balance for the label's position.
         const injectEdgeLabelProxies = (g) => {
             for (const e of g.edges()) {
                 const edge = g.edge(e);
@@ -726,28 +721,45 @@ dagre.layout = (graph, options) => {
 
         const removeEmptyRanks = (g) => {
             // Ranks may not start at 0, so we need to offset them
-            const layers = [];
             if (g.nodes().length > 0) {
-                const ranks = g.nodes().map((v) => g.node(v).rank).filter((rank) => rank != undefined);
-                const offset = Math.min(...ranks);
+                let minRank = Number.POSITIVE_INFINITY;
+                let maxRank = Number.NEGATIVE_INFINITY;
                 for (const v of g.nodes()) {
-                    const rank = g.node(v).rank - offset;
-                    if (!layers[rank]) {
-                        layers[rank] = [];
+                    const node = g.node(v);
+                    if (node.rank !== undefined) {
+                        if (node.rank < minRank) {
+                            minRank = node.rank;
+                        }
+                        if (node.rank > maxRank) {
+                            maxRank = node.rank;
+                        }
                     }
-                    layers[rank].push(v);
-                }
-            }
-            let delta = 0;
-            const nodeRankFactor = g.graph().nodeRankFactor;
-            for (let i = 0; i < layers.length; i++) {
-                const vs = layers[i];
-                if (vs === undefined && i % nodeRankFactor !== 0) {
-                    --delta;
                 }
-                else if (delta && vs) {
-                    for (const v of vs) {
-                        g.node(v).rank += delta;
+                const size = maxRank - minRank;
+                if (size > 0) {
+                    const layers = new Array(size);
+                    for (const v of g.nodes()) {
+                        const node = g.node(v);
+                        if (node.rank !== undefined) {
+                            const rank = node.rank - minRank;
+                            if (!layers[rank]) {
+                                layers[rank] = [];
+                            }
+                            layers[rank].push(v);
+                        }
+                    }
+                    let delta = 0;
+                    const nodeRankFactor = g.graph().nodeRankFactor;
+                    for (let i = 0; i < layers.length; i++) {
+                        const vs = layers[i];
+                        if (vs === undefined && i % nodeRankFactor !== 0) {
+                            --delta;
+                        }
+                        else if (delta && vs) {
+                            for (const v of vs) {
+                                g.node(v).rank += delta;
+                            }
+                        }
                     }
                 }
             }
@@ -1374,14 +1386,29 @@ dagre.layout = (graph, options) => {
                 for (let i = 0; i < southLayer.length; i++) {
                     southPos[southLayer[i]] = i;
                 }
-                const southEntries = flat(northLayer.map((v) => g.outEdges(v).map((e) => { return { pos: southPos[e.w], weight: g.edge(e).weight }; }).sort((a, b) => a.pos - b.pos)));
+                const southEntries = [];
+                for (const v of northLayer) {
+                    const edges = g.outEdges(v);
+                    const entries = [];
+                    for (const e of edges) {
+                        entries.push({
+                            pos: southPos[e.w],
+                            weight: g.edge(e).weight
+                        });
+                    }
+                    entries.sort((a, b) => a.pos - b.pos);
+                    for (const entry of entries) {
+                        southEntries.push(entry);
+                    }
+                }
                 // Build the accumulator tree
                 let firstIndex = 1;
                 while (firstIndex < southLayer.length) {
                     firstIndex <<= 1;
                 }
-                const tree = Array.from(new Array(2 * firstIndex - 1), () => 0);
+                const treeSize = 2 * firstIndex - 1;
                 firstIndex -= 1;
+                const tree = Array.from(new Array(treeSize), () => 0);
                 // Calculate the weighted crossings
                 let cc = 0;
                 for (const entry of southEntries) {
@@ -1527,6 +1554,8 @@ dagre.layout = (graph, options) => {
             const upLayerGraphs = new Array(rank !== undefined ? rank : 0);
             for (let i = 0; i < rank; i++) {
                 downLayerGraphs[i] = buildLayerGraph(g, i + 1, 'inEdges');
+            }
+            for (let i = 0; i < rank; i++) {
                 upLayerGraphs[i] = buildLayerGraph(g, rank - i - 1, 'outEdges');
             }
             let layering = initOrder(g);
@@ -1548,8 +1577,8 @@ dagre.layout = (graph, options) => {
                     lastBest = 0;
                     const length = layering.length;
                     best = new Array(length);
-                    for (let i = 0; i < length; i++) {
-                        best[i] = layering[i].slice();
+                    for (let j = 0; j < length; j++) {
+                        best[j] = layering[j].slice();
                     }
                     bestCC = cc;
                 }
@@ -1683,15 +1712,16 @@ dagre.layout = (graph, options) => {
                     const root = {};
                     const align = {};
                     const pos = {};
-                    // We cache the position here based on the layering because the graph and
-                    // layering may be out of sync. The layering matrix is manipulated to
-                    // generate different extreme alignments.
+                    // We cache the position here based on the layering because the graph and layering may be out of sync.
+                    // The layering matrix is manipulated to generate different extreme alignments.
                     for (const layer of layering) {
-                        layer.forEach(function(v, order) {
+                        let order = 0;
+                        for (const v of layer) {
                             root[v] = v;
                             align[v] = v;
                             pos[v] = order;
-                        });
+                            order++;
+                        }
                     }
                     for (const layer of layering) {
                         let prevIdx = -1;
@@ -1715,10 +1745,9 @@ dagre.layout = (graph, options) => {
                 };
                 const 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
-                    // sweeps. The first sweep places blocks with the smallest possible
-                    // coordinates. The second sweep removes unused space by moving blocks to the
-                    // greatest coordinates without violating separation.
+                    // Instead of their algorithm we construct a new block graph and do two sweeps.
+                    // The first sweep places blocks with the smallest possible coordinates.
+                    // The second sweep removes unused space by moving blocks to the greatest coordinates without violating separation.
                     const xs = {};
                     const blockG = buildBlockGraph(g, layering, root, reverseSep);
                     const borderType = reverseSep ? 'borderLeft' : 'borderRight';
@@ -1795,7 +1824,7 @@ dagre.layout = (graph, options) => {
                     const blockGraph = new dagre.Graph();
                     const graphLabel = g.graph();
                     const sepFn = sep(graphLabel.nodesep, graphLabel.edgesep, reverseSep);
-                    layering.forEach(function(layer) {
+                    for (const layer of layering) {
                         let u;
                         for (const v of layer) {
                             const vRoot = root[v];
@@ -1807,7 +1836,7 @@ dagre.layout = (graph, options) => {
                             }
                             u = v;
                         }
-                    });
+                    }
                     return blockGraph;
                 };
 
@@ -1851,10 +1880,9 @@ dagre.layout = (graph, options) => {
                 };
 
                 /*
-                * 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
-                * with the 'dummy' property.
+                * 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 with the 'dummy' property.
                 *
                 * This algorithm scans layer by layer, starting with the second, for type-1
                 * conflicts between the current layer and the previous layer. For each layer
@@ -1870,11 +1898,9 @@ dagre.layout = (graph, options) => {
                 const findType1Conflicts = (g, layering) => {
                     const conflicts = {};
                     const visitLayer = (prevLayer, layer) => {
-                        // last visited node in the previous layer that is incident on an inner
-                        // segment.
+                        // last visited node in the previous layer that is incident on an inner segment.
                         let k0 = 0;
-                        // Tracks the last node in this layer scanned for crossings with a type-1
-                        // segment.
+                        // Tracks the last node in this layer scanned for crossings with a type-1 segment.
                         let scanPos = 0;
                         const prevLayerLength = prevLayer.length;
                         const lastNode = layer[layer.length - 1];
@@ -1886,8 +1912,7 @@ dagre.layout = (graph, options) => {
                                     for (const u of g.predecessors(scanNode)) {
                                         const uLabel = g.node(u);
                                         const uPos = uLabel.order;
-                                        if ((uPos < k0 || k1 < uPos) &&
-                                                !(uLabel.dummy && g.node(scanNode).dummy)) {
+                                        if ((uPos < k0 || k1 < uPos) && !(uLabel.dummy && g.node(scanNode).dummy)) {
                                             addConflict(conflicts, u, scanNode);
                                         }
                                     }
@@ -1966,13 +1991,11 @@ dagre.layout = (graph, options) => {
                 }
 
                 const smallestWidth = findSmallestWidthAlignment(g, xss);
-                /*
-                * Align the coordinates of each of the layout alignments such that
-                * left-biased alignments have their minimum coordinate at the same point as
-                * the minimum coordinate of the smallest width alignment and right-biased
-                * alignments have their maximum coordinate at the same point as the maximum
-                * coordinate of the smallest width alignment.
-                */
+                // Align the coordinates of each of the layout alignments such that
+                // left-biased alignments have their minimum coordinate at the same point as
+                // the minimum coordinate of the smallest width alignment and right-biased
+                // alignments have their maximum coordinate at the same point as the maximum
+                // coordinate of the smallest width alignment.
                 const alignCoordinates = (xss, alignTo) => {
                     const range = (values) => {
                         let min = Number.POSITIVE_INFINITY;
@@ -2202,33 +2225,33 @@ dagre.layout = (graph, options) => {
             }
         };
 
-        time('    makeSpaceForEdgeLabels',  function() { makeSpaceForEdgeLabels(g); });
-        time('    removeSelfEdges',         function() { removeSelfEdges(g); });
-        time('    acyclic_run',             function() { acyclic_run(g); });
-        time('    nestingGraph_run',        function() { nestingGraph_run(g); });
-        time('    rank',                    function() { rank(util_asNonCompoundGraph(g)); });
-        time('    injectEdgeLabelProxies',  function() { injectEdgeLabelProxies(g); });
-        time('    removeEmptyRanks',        function() { removeEmptyRanks(g); });
-        time('    nestingGraph_cleanup',    function() { nestingGraph_cleanup(g); });
-        time('    normalizeRanks',          function() { normalizeRanks(g); });
-        time('    assignRankMinMax',        function() { assignRankMinMax(g); });
-        time('    removeEdgeLabelProxies',  function() { removeEdgeLabelProxies(g); });
-        time('    normalize',               function() { normalize(g); });
-        time('    parentDummyChains',       function() { parentDummyChains(g); });
-        time('    addBorderSegments',       function() { addBorderSegments(g); });
-        time('    order',                   function() { order(g); });
-        time('    insertSelfEdges',         function() { insertSelfEdges(g); });
-        time('    coordinateSystem_adjust', function() { coordinateSystem_adjust(g); });
-        time('    position',                function() { position(g); });
-        time('    positionSelfEdges',       function() { positionSelfEdges(g); });
-        time('    removeBorderNodes',       function() { removeBorderNodes(g); });
-        time('    denormalize',             function() { denormalize(g); });
-        time('    fixupEdgeLabelCoords',    function() { fixupEdgeLabelCoords(g); });
-        time('    CoordinateSystem_undo',   function() { coordinateSystem_undo(g); });
-        time('    translateGraph',          function() { translateGraph(g); });
-        time('    assignNodeIntersects',    function() { assignNodeIntersects(g); });
-        time('    reversePoints',           function() { reversePointsForReversedEdges(g); });
-        time('    acyclic_undo',            function() { acyclic_undo(g); });
+        time('    makeSpaceForEdgeLabels',        function() { makeSpaceForEdgeLabels(g); });
+        time('    removeSelfEdges',               function() { removeSelfEdges(g); });
+        time('    acyclic_run',                   function() { acyclic_run(g); });
+        time('    nestingGraph_run',              function() { nestingGraph_run(g); });
+        time('    rank',                          function() { rank(util_asNonCompoundGraph(g)); });
+        time('    injectEdgeLabelProxies',        function() { injectEdgeLabelProxies(g); });
+        time('    removeEmptyRanks',              function() { removeEmptyRanks(g); });
+        time('    nestingGraph_cleanup',          function() { nestingGraph_cleanup(g); });
+        time('    normalizeRanks',                function() { normalizeRanks(g); });
+        time('    assignRankMinMax',              function() { assignRankMinMax(g); });
+        time('    removeEdgeLabelProxies',        function() { removeEdgeLabelProxies(g); });
+        time('    normalize',                     function() { normalize(g); });
+        time('    parentDummyChains',             function() { parentDummyChains(g); });
+        time('    addBorderSegments',             function() { addBorderSegments(g); });
+        time('    order',                         function() { order(g); });
+        time('    insertSelfEdges',               function() { insertSelfEdges(g); });
+        time('    coordinateSystem_adjust',       function() { coordinateSystem_adjust(g); });
+        time('    position',                      function() { position(g); });
+        time('    positionSelfEdges',             function() { positionSelfEdges(g); });
+        time('    removeBorderNodes',             function() { removeBorderNodes(g); });
+        time('    denormalize',                   function() { denormalize(g); });
+        time('    fixupEdgeLabelCoords',          function() { fixupEdgeLabelCoords(g); });
+        time('    coordinateSystem_undo',         function() { coordinateSystem_undo(g); });
+        time('    translateGraph',                function() { translateGraph(g); });
+        time('    assignNodeIntersects',          function() { assignNodeIntersects(g); });
+        time('    reversePointsForReversedEdges', function() { reversePointsForReversedEdges(g); });
+        time('    acyclic_undo',                  function() { acyclic_undo(g); });
     };
 
     /*