@ -2,8 +2,18 @@
* Created by Alex on 8 / 7 / 2015.
* /
import FloydWarshall from "./FloydWarshall.js"
// distance finding algorithm
import FloydWarshall from "./components/algorithms/FloydWarshall.js"
/ * *
* KamadaKawai positions the nodes initially based on
*
* "AN ALGORITHM FOR DRAWING GENERAL UNDIRECTED GRAPHS"
* -- Tomihisa KAMADA and Satoru KAWAI in 1989
*
* Possible optimizations in the distance calculation can be implemented .
* /
class KamadaKawai {
constructor ( body , edgeLength , edgeStrength ) {
this . body = body ;
@ -12,6 +22,10 @@ class KamadaKawai {
this . distanceSolver = new FloydWarshall ( ) ;
}
/ * *
* Not sure if needed but can be used to update the spring length and spring constant
* @ param options
* /
setOptions ( options ) {
if ( options ) {
if ( options . springLength ) {
@ -23,10 +37,15 @@ class KamadaKawai {
}
}
solve ( nodesArray , edgesArray ) {
console . time ( "FLOYD - getDistances" ) ;
/ * *
* Position the system
* @ param nodesArray
* @ param edgesArray
* /
solve ( nodesArray , edgesArray , ignoreClusters = false ) {
// get distance matrix
let D_matrix = this . distanceSolver . getDistances ( this . body , nodesArray , edgesArray ) ; // distance matrix
console . timeEnd ( "FLOYD - getDistances" ) ;
// get the L Matrix
this . _createL_matrix ( D_matrix ) ;
@ -34,42 +53,64 @@ class KamadaKawai {
// get the K Matrix
this . _createK_matrix ( D_matrix ) ;
console . time ( "positioning" )
// calculate positions
let threshold = 0.01 ;
let counter = 0 ;
let maxIterations = Math . min ( 10 * this . body . nodeIndices . length ) ; ;
let maxEnergy = 1 e9 ; // just to pass the first check.
let highE_nodeId = 0 , dE_dx = 0 , dE_dy = 0 ;
while ( maxEnergy > threshold && counter < maxIterations ) {
counter += 1 ;
[ highE_nodeId , maxEnergy , dE_dx , dE_dy ] = this . _getHighestEnergyNode ( ) ;
this . _moveNode ( highE_nodeId , dE_dx , dE_dy ) ;
let innerThreshold = 1 ;
let iterations = 0 ;
let maxIterations = Math . max ( 1000 , Math . min ( 10 * this . body . nodeIndices . length , 6000 ) ) ;
let maxInnerIterations = 5 ;
let maxEnergy = 1 e9 ;
let highE_nodeId = 0 , dE_dx = 0 , dE_dy = 0 , delta_m = 0 , subIterations = 0 ;
while ( maxEnergy > threshold && iterations < maxIterations ) {
iterations += 1 ;
[ highE_nodeId , maxEnergy , dE_dx , dE_dy ] = this . _getHighestEnergyNode ( ignoreClusters ) ;
delta_m = maxEnergy ;
subIterations = 0 ;
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 ) ;
}
}
console . timeEnd ( "positioning" )
}
_getHighestEnergyNode ( ) {
/ * *
* get the node with the highest energy
* @ returns { * [ ] }
* @ private
* /
_getHighestEnergyNode ( ignoreClusters ) {
let nodesArray = this . body . nodeIndices ;
let nodes = this . body . nodes ;
let maxEnergy = 0 ;
let maxEnergyNode = nodesArray [ 0 ] ;
let energies = { dE_dx : 0 , dE_dy : 0 } ;
let maxEnergyNodeId = nodesArray [ 0 ] ;
let dE_dx_max = 0 , dE_dy_max = 0 ;
for ( let nodeIdx = 0 ; nodeIdx < nodesArray . length ; nodeIdx ++ ) {
let m = nodesArray [ nodeIdx ] ;
let [ delta_m , dE_dx , dE_dy ] = this . _getEnergy ( m ) ;
if ( maxEnergy < delta_m ) {
maxEnergy = delta_m ;
maxEnergyNode = m ;
energies . dE_dx = dE_dx ;
energies . dE_dy = dE_dy ;
// 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 ) {
let [ delta_m , dE_dx , dE_dy ] = this . _getEnergy ( m ) ;
if ( maxEnergy < delta_m ) {
maxEnergy = delta_m ;
maxEnergyNodeId = m ;
dE_dx_max = dE_dx ;
dE_dy_max = dE_dy ;
}
}
}
return [ maxEnergyNode , maxEnergy , energies . dE_dx , energies . dE_dy ] ;
return [ maxEnergyNodeId , maxEnergy , dE_dx_max , dE_dy_max ] ;
}
/ * *
* calculate the energy of a single node
* @ param m
* @ returns { * [ ] }
* @ private
* /
_getEnergy ( m ) {
let nodesArray = this . body . nodeIndices ;
let nodes = this . body . nodes ;
@ -93,6 +134,14 @@ class KamadaKawai {
return [ delta_m , dE_dx , dE_dy ] ;
}
/ * *
* move the node based on it ' s energy
* the dx and dy are calculated from the linear system proposed by Kamada and Kawai
* @ param m
* @ param dE_dx
* @ param dE_dy
* @ private
* /
_moveNode ( m , dE_dx , dE_dy ) {
let nodesArray = this . body . nodeIndices ;
let nodes = this . body . nodes ;
@ -125,6 +174,12 @@ class KamadaKawai {
nodes [ m ] . y += dy ;
}
/ * *
* Create the L matrix : edge length times shortest path
* @ param D_matrix
* @ private
* /
_createL_matrix ( D_matrix ) {
let nodesArray = this . body . nodeIndices ;
let edgeLength = this . springLength ;
@ -138,6 +193,12 @@ class KamadaKawai {
}
}
/ * *
* Create the K matrix : spring constants times shortest path
* @ param D_matrix
* @ private
* /
_createK_matrix ( D_matrix ) {
let nodesArray = this . body . nodeIndices ;
let edgeStrength = this . springConstant ;