diff --git a/lib/network/modules/KamadaKawai.js b/lib/network/modules/KamadaKawai.js index 63d620ff..50e84c83 100644 --- a/lib/network/modules/KamadaKawai.js +++ b/lib/network/modules/KamadaKawai.js @@ -49,11 +49,14 @@ class KamadaKawai { // get the K Matrix this._createK_matrix(D_matrix); + // initial E Matrix + this._createE_matrix(); + // calculate positions let threshold = 0.01; let innerThreshold = 1; let iterations = 0; - let maxIterations = Math.max(1000,Math.min(10*this.body.nodeIndices.length,6000)); + let maxIterations = Math.max(1000, Math.min(10 * this.body.nodeIndices.length, 6000)); let maxInnerIterations = 5; let maxEnergy = 1e9; @@ -64,10 +67,10 @@ class KamadaKawai { [highE_nodeId, maxEnergy, dE_dx, dE_dy] = this._getHighestEnergyNode(ignoreClusters); delta_m = maxEnergy; subIterations = 0; - while(delta_m > innerThreshold && subIterations < maxInnerIterations) { + while (delta_m > innerThreshold && subIterations < maxInnerIterations) { subIterations += 1; this._moveNode(highE_nodeId, dE_dx, dE_dy); - [delta_m,dE_dx,dE_dy] = this._getEnergy(highE_nodeId); + [delta_m, dE_dx, dE_dy] = this._getEnergy(highE_nodeId); } } } @@ -87,7 +90,7 @@ class KamadaKawai { for (let nodeIdx = 0; nodeIdx < nodesArray.length; nodeIdx++) { let m = nodesArray[nodeIdx]; // by not evaluating nodes with predefined positions we should only move nodes that have no positions. - if ((nodes[m].predefinedPosition === false || nodes[m].isCluster === true && ignoreClusters === true) || nodes[m].options.fixed.x === true || nodes[m].options.fixed.y === true) { + if ((nodes[m].predefinedPosition === false || nodes[m].isCluster === true && ignoreClusters === true) || nodes[m].options.fixed.x === true || nodes[m].options.fixed.y === true) { let [delta_m,dE_dx,dE_dy] = this._getEnergy(m); if (maxEnergy < delta_m) { maxEnergy = delta_m; @@ -108,24 +111,7 @@ class KamadaKawai { * @private */ _getEnergy(m) { - let nodesArray = this.body.nodeIndices; - let nodes = this.body.nodes; - - let x_m = nodes[m].x; - let y_m = nodes[m].y; - let dE_dx = 0; - let dE_dy = 0; - for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) { - let i = nodesArray[iIdx]; - if (i !== m) { - let x_i = nodes[i].x; - let y_i = nodes[i].y; - let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2)); - dE_dx += this.K_matrix[m][i] * ((x_m - x_i) - this.L_matrix[m][i] * (x_m - x_i) * denominator); - dE_dy += this.K_matrix[m][i] * ((y_m - y_i) - this.L_matrix[m][i] * (y_m - y_i) * denominator); - } - } - + let [dE_dx,dE_dy] = this.E_sums[m]; let delta_m = Math.sqrt(Math.pow(dE_dx, 2) + Math.pow(dE_dy, 2)); return [delta_m, dE_dx, dE_dy]; } @@ -147,15 +133,20 @@ class KamadaKawai { let x_m = nodes[m].x; let y_m = nodes[m].y; + let km = this.K_matrix[m]; + let lm = this.L_matrix[m]; + for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) { let i = nodesArray[iIdx]; if (i !== m) { let x_i = nodes[i].x; let y_i = nodes[i].y; + let kmat = km[i]; + let lmat = lm[i]; let denominator = 1.0 / Math.pow(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2), 1.5); - d2E_dx2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(y_m - y_i, 2) * denominator); - d2E_dxdy += this.K_matrix[m][i] * (this.L_matrix[m][i] * (x_m - x_i) * (y_m - y_i) * denominator); - d2E_dy2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(x_m - x_i, 2) * denominator); + d2E_dx2 += kmat * (1 - lmat * Math.pow(y_m - y_i, 2) * denominator); + d2E_dxdy += kmat * (lmat * (x_m - x_i) * (y_m - y_i) * denominator); + d2E_dy2 += kmat * (1 - lmat * Math.pow(x_m - x_i, 2) * denominator); } } // make the variable names easier to make the solving of the linear system easier to read @@ -168,6 +159,9 @@ class KamadaKawai { // move the node nodes[m].x += dx; nodes[m].y += dy; + + // Recalculate E_matrix (should be incremental) + this._updateE_matrix(m); } @@ -208,8 +202,82 @@ class KamadaKawai { } } + /** + * Create matrix with all energies between nodes + * @private + */ + _createE_matrix() { + let nodesArray = this.body.nodeIndices; + let nodes = this.body.nodes; + this.E_matrix = {}; + this.E_sums = {}; + for (let mIdx = 0; mIdx < nodesArray.length; mIdx++) { + this.E_matrix[nodesArray[mIdx]] = []; + } + for (let mIdx = 0; mIdx < nodesArray.length; mIdx++) { + let m = nodesArray[mIdx]; + let x_m = nodes[m].x; + let y_m = nodes[m].y; + let dE_dx = 0; + let dE_dy = 0; + for (let iIdx = mIdx; iIdx < nodesArray.length; iIdx++) { + let i = nodesArray[iIdx]; + if (i !== m) { + let x_i = nodes[i].x; + let y_i = nodes[i].y; + let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2)); + this.E_matrix[m][iIdx] = [ + this.K_matrix[m][i] * ((x_m - x_i) - this.L_matrix[m][i] * (x_m - x_i) * denominator), + this.K_matrix[m][i] * ((y_m - y_i) - this.L_matrix[m][i] * (y_m - y_i) * denominator) + ]; + this.E_matrix[i][mIdx] = this.E_matrix[m][iIdx]; + dE_dx += this.E_matrix[m][iIdx][0]; + dE_dy += this.E_matrix[m][iIdx][1]; + } + } + //Store sum + this.E_sums[m] = [dE_dx, dE_dy]; + } + } + + //Update method, just doing single column (rows are auto-updated) (update all sums) + _updateE_matrix(m) { + let nodesArray = this.body.nodeIndices; + let nodes = this.body.nodes; + let colm = this.E_matrix[m]; + let kcolm = this.K_matrix[m]; + let lcolm = this.L_matrix[m]; + let x_m = nodes[m].x; + let y_m = nodes[m].y; + let dE_dx = 0; + let dE_dy = 0; + for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) { + let i = nodesArray[iIdx]; + if (i !== m) { + //Keep old energy value for sum modification below + let cell = colm[iIdx]; + let oldDx = cell[0]; + let oldDy = cell[1]; + //Calc new energy: + let x_i = nodes[i].x; + let y_i = nodes[i].y; + let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2)); + let dx = kcolm[i] * ((x_m - x_i) - lcolm[i] * (x_m - x_i) * denominator); + let dy = kcolm[i] * ((y_m - y_i) - lcolm[i] * (y_m - y_i) * denominator); + colm[iIdx] = [dx, dy]; + dE_dx += dx; + dE_dy += dy; + //add new energy to sum of each column + let sum = this.E_sums[i]; + sum[0] += (dx-oldDx); + sum[1] += (dy-oldDy); + } + } + //Store sum at -1 index + this.E_sums[m] = [dE_dx, dE_dy]; + } } export default KamadaKawai; \ No newline at end of file diff --git a/lib/network/modules/LayoutEngine.js b/lib/network/modules/LayoutEngine.js index 166781e1..09468955 100644 --- a/lib/network/modules/LayoutEngine.js +++ b/lib/network/modules/LayoutEngine.js @@ -161,9 +161,9 @@ class LayoutEngine { positionInitially(nodesArray) { if (this.options.hierarchical.enabled !== true) { this.randomSeed = this.initialRandomSeed; + let radius = nodesArray.length + 50; for (let i = 0; i < nodesArray.length; i++) { let node = nodesArray[i]; - let radius = 10 * 0.1 * nodesArray.length + 10; let angle = 2 * Math.PI * this.seededRandom(); if (node.x === undefined) { node.x = radius * Math.cos(angle); @@ -196,34 +196,46 @@ class LayoutEngine { if (positionDefined < 0.5 * this.body.nodeIndices.length) { let MAX_LEVELS = 10; let level = 0; - let clusterThreshold = 100; + let clusterThreshold = 150; + //Performance enhancement, during clustering edges need only be simple straight lines. These options don't propagate outside the clustering phase. + let clusterOptions = { + clusterEdgeProperties:{ + smooth: { + enabled: false + } + } + }; + // if there are a lot of nodes, we cluster before we run the algorithm. if (this.body.nodeIndices.length > clusterThreshold) { let startLength = this.body.nodeIndices.length; - while (this.body.nodeIndices.length > clusterThreshold) { + while (this.body.nodeIndices.length > clusterThreshold && level <= MAX_LEVELS) { //console.time("clustering") level += 1; let before = this.body.nodeIndices.length; // if there are many nodes we do a hubsize cluster if (level % 3 === 0) { - this.body.modules.clustering.clusterBridges(); + this.body.modules.clustering.clusterBridges(clusterOptions); } else { - this.body.modules.clustering.clusterOutliers(); + this.body.modules.clustering.clusterOutliers(clusterOptions); } let after = this.body.nodeIndices.length; - if ((before == after && level % 3 !== 0) || level > MAX_LEVELS) { + if (before == after && level % 3 !== 0) { this._declusterAll(); this.body.emitter.emit("_layoutFailed"); console.info("This network could not be positioned by this version of the improved layout algorithm. Please disable improvedLayout for better performance."); return; } //console.timeEnd("clustering") - //console.log(level,after) + //console.log(before,level,after); } // increase the size of the edges this.body.modules.kamadaKawai.setOptions({springLength: Math.max(150, 2 * startLength)}) } + if (level > MAX_LEVELS){ + console.info("The clustering didn't succeed within the amount of interations allowed, progressing with partial result."); + } // position the system for these nodes and edges this.body.modules.kamadaKawai.solve(this.body.nodeIndices, this.body.edgeIndices, true); diff --git a/lib/network/modules/components/algorithms/FloydWarshall.js b/lib/network/modules/components/algorithms/FloydWarshall.js index e0d3f92d..42983755 100644 --- a/lib/network/modules/components/algorithms/FloydWarshall.js +++ b/lib/network/modules/components/algorithms/FloydWarshall.js @@ -4,7 +4,8 @@ class FloydWarshall { - constructor(){} + constructor() { + } getDistances(body, nodesArray, edgesArray) { let D_matrix = {}; @@ -12,11 +13,11 @@ class FloydWarshall { // prepare matrix with large numbers for (let i = 0; i < nodesArray.length; i++) { - D_matrix[nodesArray[i]] = {}; - D_matrix[nodesArray[i]] = {}; + let node = nodesArray[i]; + let cell = {}; + D_matrix[node] = cell; for (let j = 0; j < nodesArray.length; j++) { - D_matrix[nodesArray[i]][nodesArray[j]] = (i == j ? 0 : 1e9); - D_matrix[nodesArray[i]][nodesArray[j]] = (i == j ? 0 : 1e9); + cell[nodesArray[j]] = (i == j ? 0 : 1e9); } } @@ -34,10 +35,18 @@ class FloydWarshall { // Adapted FloydWarshall based on unidirectionality to greatly reduce complexity. for (let k = 0; k < nodeCount; k++) { - for (let i = 0; i < nodeCount-1; i++) { - for (let j = i+1; j < nodeCount; j++) { - D_matrix[nodesArray[i]][nodesArray[j]] = Math.min(D_matrix[nodesArray[i]][nodesArray[j]],D_matrix[nodesArray[i]][nodesArray[k]] + D_matrix[nodesArray[k]][nodesArray[j]]) - D_matrix[nodesArray[j]][nodesArray[i]] = D_matrix[nodesArray[i]][nodesArray[j]]; + let knode = nodesArray[k]; + let kcolm = D_matrix[knode]; + for (let i = 0; i < nodeCount - 1; i++) { + let inode = nodesArray[i]; + let icolm = D_matrix[inode]; + for (let j = i + 1; j < nodeCount; j++) { + let jnode = nodesArray[j]; + let jcolm = D_matrix[jnode]; + + let val = Math.min(icolm[jnode], icolm[knode] + kcolm[jnode]); + icolm[jnode] = val; + jcolm[inode] = val; } } }