From 0f2f73c1811cce5dd8f7a4be10b1e224a6477b0e Mon Sep 17 00:00:00 2001 From: katsuhisa yuasa Date: Sat, 21 Sep 2024 07:23:19 +0900 Subject: [PATCH 1/4] update dagre.Graph.removeNode and dagre.Graph.removeEdge --- source/dagre.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/source/dagre.js b/source/dagre.js index d691fa6e29..93e6fff146 100644 --- a/source/dagre.js +++ b/source/dagre.js @@ -2275,10 +2275,10 @@ dagre.Graph = class { } this._children.delete(v); } - for (const edge of node.in) { + for (const edge of node.in.concat()) { this.removeEdge(edge); } - for (const edge of node.out) { + for (const edge of node.out.concat()) { this.removeEdge(edge); } this.nodes.delete(v); @@ -2409,8 +2409,15 @@ dagre.Graph = class { vNode.successors.set(w, value - 1); } } - wNode.in = wNode.in.filter((edge) => edge.key !== key); - vNode.out = vNode.out.filter((edge) => edge.key !== key); + // Update arrays in-place + const idxIn = wNode.in.findIndex((e) => e.key === key); + if (idxIn != -1) { + wNode.in.splice(idxIn, 1); + } + const idxOut = vNode.out.findIndex((e) => e.key === key); + if (idxOut != -1) { + vNode.out.splice(idxOut, 1); + } this.edges.delete(key); } From f66b4ee9a6ea39f32bfa1349da85b99df220f97b Mon Sep 17 00:00:00 2001 From: katsuhisa yuasa Date: Sat, 21 Sep 2024 09:54:08 +0900 Subject: [PATCH 2/4] fix eslint errors --- source/dagre.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/dagre.js b/source/dagre.js index 93e6fff146..1da3629033 100644 --- a/source/dagre.js +++ b/source/dagre.js @@ -2411,11 +2411,11 @@ dagre.Graph = class { } // Update arrays in-place const idxIn = wNode.in.findIndex((e) => e.key === key); - if (idxIn != -1) { + if (idxIn !== -1) { wNode.in.splice(idxIn, 1); } const idxOut = vNode.out.findIndex((e) => e.key === key); - if (idxOut != -1) { + if (idxOut !== -1) { vNode.out.splice(idxOut, 1); } this.edges.delete(key); From e2f4551dece226214a7600d7500bb34715661e81 Mon Sep 17 00:00:00 2001 From: katsuhisa yuasa Date: Sun, 29 Sep 2024 23:53:04 +0900 Subject: [PATCH 3/4] optimize crossCount function --- source/dagre.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/source/dagre.js b/source/dagre.js index 1da3629033..fdf5f59338 100644 --- a/source/dagre.js +++ b/source/dagre.js @@ -1138,23 +1138,23 @@ dagre.layout = (nodes, edges, layout, state) => { // 1. The graph and layering matrix are left unchanged. // // This algorithm is derived from Barth, et al., 'Bilayer Cross Counting.' - const crossCount = (g, layering) => { + const crossCount = (g, layering, bestCC) => { let count = 0; for (let i = 1; i < layering.length; i++) { const northLayer = layering[i - 1]; const southLayer = layering[i]; // 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 their head in the south layer. - const southPos = {}; + const southPos = new Map(); for (let i = 0; i < southLayer.length; i++) { - southPos[southLayer[i]] = i; + southPos.set(southLayer[i], i); } const southEntries = []; for (const v of northLayer) { const entries = []; for (const e of g.node(v).out) { entries.push({ - pos: southPos[e.w], + pos: southPos.get(e.w), weight: e.label.weight }); } @@ -1185,6 +1185,9 @@ dagre.layout = (nodes, edges, layout, state) => { } count += entry.weight * weightSum; } + if (count > bestCC) { + break; + } } return count; }; @@ -1349,7 +1352,7 @@ dagre.layout = (nodes, edges, layout, state) => { for (let i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) { sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2); layering = buildLayerMatrix(g); - const cc = crossCount(g, layering); + const cc = crossCount(g, layering, bestCC); if (cc < bestCC) { lastBest = 0; const length = layering.length; From b95a0fe99fa2ff64960658ef820dceb2ecfe4836 Mon Sep 17 00:00:00 2001 From: katsuhisa yuasa Date: Mon, 30 Sep 2024 23:47:42 +0900 Subject: [PATCH 4/4] optimize dagre.layout --- source/dagre.js | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/source/dagre.js b/source/dagre.js index fdf5f59338..c614360910 100644 --- a/source/dagre.js +++ b/source/dagre.js @@ -1627,10 +1627,10 @@ dagre.layout = (nodes, edges, layout, state) => { if (v > w) { [v, w] = [w, v]; } - let conflictsV = conflicts[v]; + let conflictsV = conflicts.get(v); if (!conflictsV) { conflictsV = new Set(); - conflicts[v] = conflictsV; + conflicts.set(v, conflictsV); } conflictsV.add(w); }; @@ -1638,7 +1638,7 @@ dagre.layout = (nodes, edges, layout, state) => { if (v > w) { [v, w] = [w, v]; } - return conflicts[v] && conflicts[v].has(w); + return conflicts.has(v) && conflicts.get(v).has(w); }; const buildBlockGraph = (g, layout, layering, root, reverseSep) => { const nodeSep = layout.nodesep; @@ -1647,10 +1647,10 @@ dagre.layout = (nodes, edges, layout, state) => { for (const layer of layering) { let u = null; for (const v of layer) { - const vRoot = root[v]; + const vRoot = root.get(v); blockGraph.setNode(vRoot, {}); if (u) { - const uRoot = root[u]; + const uRoot = root.get(u); const vLabel = g.node(v).label; const wLabel = g.node(u).label; let sum = 0; @@ -1699,17 +1699,17 @@ dagre.layout = (nodes, edges, layout, state) => { // 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. const verticalAlignment = (layering, conflicts, neighborFn) => { - const root = {}; - const align = {}; - const pos = {}; + const root = new Map(); + const align = new Map(); + const pos = new Map(); // 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) { let order = 0; for (const v of layer) { - root[v] = v; - align[v] = v; - pos[v] = order; + root.set(v, v); + align.set(v, v); + pos.set(v, order); order++; } } @@ -1719,17 +1719,17 @@ dagre.layout = (nodes, edges, layout, state) => { let ws = neighborFn(v); if (ws.size > 0) { ws = Array.from(ws.keys()); - ws = ws.sort((a, b) => pos[a] - pos[b]); + ws = ws.sort((a, b) => pos.get(a) - pos.get(b)); const mp = (ws.length - 1) / 2.0000001; const il = Math.ceil(mp); for (let i = Math.floor(mp); i <= il; i++) { const w = ws[i]; - if (align[v] === v && prevIdx < pos[w] && !hasConflict(conflicts, v, w)) { - const x = root[w]; - align[w] = v; - align[v] = x; - root[v] = x; - prevIdx = pos[w]; + if (align.get(v) === v && prevIdx < pos.get(w) && !hasConflict(conflicts, v, w)) { + const x = root.get(w); + align.set(w, v); + align.set(v, x); + root.set(v, x); + prevIdx = pos.get(w); } } } @@ -1788,8 +1788,8 @@ dagre.layout = (nodes, edges, layout, state) => { } } // Assign x coordinates to all nodes - for (const v of Object.values(align)) { - xs.set(v, xs.get(root[v])); + for (const v of align.values()) { + xs.set(v, xs.get(root.get(v))); } return xs; }; @@ -1807,7 +1807,7 @@ dagre.layout = (nodes, edges, layout, state) => { // This algorithm (safely) assumes that a dummy node will only be incident on a // single node in the layers being scanned. const findType1Conflicts = (g, layering) => { - const conflicts = {}; + const conflicts = new Map(); if (layering.length > 0) { let [prev] = layering; for (let k = 1; k < layering.length; k++) { @@ -1847,7 +1847,7 @@ dagre.layout = (nodes, edges, layout, state) => { return conflicts; }; const findType2Conflicts = (g, layering) => { - const conflicts = {}; + const conflicts = new Map(); const scan = (south, southPos, southEnd, prevNorthBorder, nextNorthBorder) => { for (let i = southPos; i < southEnd; i++) { const v = south[i]; @@ -1901,7 +1901,7 @@ dagre.layout = (nodes, edges, layout, state) => { y += maxHeight + ranksep; } // Coordinate assignment based on Brandes and Köpf, 'Fast and Simple Horizontal Coordinate Assignment.' - const conflicts = Object.assign(findType1Conflicts(g, layering), findType2Conflicts(g, layering)); + const conflicts = new Map([...findType1Conflicts(g, layering).entries(), ...findType2Conflicts(g, layering).entries()]); const xss = {}; for (const vertical of ['u', 'd']) { let adjustedLayering = vertical === 'u' ? layering : Object.values(layering).reverse(); @@ -2381,7 +2381,7 @@ dagre.Graph = class { edge.wNode = wNode; edge.vNode = vNode; const incrementOrInitEntry = (map, k) => { - map.set(k, map.has(k) ? map.get(k) + 1 : 1); + map.set(k, (map.get(k) ?? 0) + 1); }; incrementOrInitEntry(wNode.predecessors, v); incrementOrInitEntry(vNode.successors, w);