/** * Positions the nodes on the layout according to the results of numerous iterations of the * Kamada-Kawai spring-embedding algorithm. Essentially, the network is modeled as a collection of * nodes connected by springs with resting lengths proportional to the length of the shortest path * distance between each node pair. Nodes are normally positioned in a circle, and then each node * in sequence is repositioned until the "energy" of all of its springs are minimized to a * parameter value epsilon. The location of the local minima for each node is estimated with * iterations of a Newtown-Raphson steepest descent method. Repositioning ceases when all nodes * have energy below epsilon. In this implementation, epsilon is initialized at a high value, and * than decreased as in simulated annealing. the layout SHOULD stop when a low value (epsilon < 1) * is reached or when energies of nodes can now longer be decreased. * * <p>Note: In the current implementation the layout may not always converge! however, the * maxPasses parameter can be set lower to interrupt cycling layouts. Also has not been tested/ * implemented on weighted graphs. The Kamada-Kawai algorithm was not intended to run on * disconnected graphs (graphs with multiple components. The kludgy solution implemented here is * to run the algorithm independently on each of the components (of size > 1). This is somewhat * unsatisfactory as the components will often overlap. * * <p>The KK algorithm is relatively slow, especially on the first round. However, it often * discovers layouts of regularly structured graphs which are "better" and more repeatable than * the Fruchmen-Reingold technique. Implementation of the numerics of the Newton-Raphson method * follows Shawn Lorae Stutzman, Auburn University, 12/12/96 <A * href="http://mathcs.mta.ca/research/rosebrugh/gdct/javasource.htm"> * http://mathcs.mta.ca/research/rosebrugh/gdct/javasource.htm</A> * * <p>Kamada, Tomihisa and Satoru Kawai (1989) "An Algorithm for Drawing Undirected Graphs" <CITE> * Information Processing Letters</CITE> 31:7-15 */ public void updateLayout() { // check that layout should be drawn if (update) { isEventThread = SwingUtilities.isEventDispatchThread(); stop = false; if (circleLayout) { // give nodes circular initial coord to begin with circleLayout(); } if (firstLayout) { firstLayout = false; circleLayout(); } // runs kk algorithm on each component individualy ArrayList components = NetUtilities.getComponents(nodeList); Iterator compIter = components.iterator(); while (compIter.hasNext() && !stop) { ArrayList comp = (ArrayList) compIter.next(); if (comp.size() > 1) runKamadaOn(comp); } // rescale node positions to fit in window if (rescaleLayout) rescalePositions(nodeList); } }
// RENORM COORDS TO 0-1 range before running ? private void runKamadaOn(ArrayList componentNodes) { int nNodes = componentNodes.size(); // sets up the matrix of path distances DenseDoubleMatrix2D distMatrix = NetUtilities.getAllShortPathMatrix(componentNodes); // sets up kmatrix of forces DenseDoubleMatrix2D kMatrix = calcKMatrix(distMatrix, springConst); // calc desired distance between nodes double optDist = Math.min(width, height) / Math.max(getDiam(distMatrix), 1); // sets up lMatrix of distance between nodes pairs DenseDoubleMatrix2D lMatrix = calcLMatrix(distMatrix, optDist); // arrays for quick acess to node coords double[] xPos = new double[nNodes]; double[] yPos = new double[nNodes]; int numEdges = 0; for (int i = 0; i < nNodes; i++) { DrawableNonGridNode workNode = (DrawableNonGridNode) componentNodes.get(i); xPos[i] = workNode.getX(); yPos[i] = workNode.getY(); numEdges += ((ArrayList) workNode.getOutEdges()).size(); } // calc value to start minimization from (should be based on previous?) // epsilon = (nNodes * numEdges)/2; // figure out the initial stat to compare to at the end double initialEnergy = getEnergy(lMatrix, kMatrix, xPos, yPos); double epsilon = initialEnergy / nNodes; // figure out which node to start moving first double deltaM; int maxDeltaMIndex = 0; double maxDeltaM = getDeltaM(0, lMatrix, kMatrix, xPos, yPos); for (int i = 1; i < nNodes; i++) { deltaM = getDeltaM(i, lMatrix, kMatrix, xPos, yPos); if (deltaM > maxDeltaM) { maxDeltaM = deltaM; maxDeltaMIndex = i; } } int passes = 0; int subPasses = 0; // epsilon minimizing loop while ((epsilon > minEpsilon) && !stop) { double previousMaxDeltaM = maxDeltaM + 1; // KAMADA-KAWAI LOOP: while the deltaM of the node with // the largest deltaM > epsilon.. while ((maxDeltaM > epsilon) && ((previousMaxDeltaM - maxDeltaM) > 0.1) && !stop) { double[] deltas; double moveNodeDeltaM = maxDeltaM; // double previousDeltaM = moveNodeDeltaM + 1; // KK INNER LOOP while the node with the largest energy > epsilon... while ((moveNodeDeltaM > epsilon) && !stop) { // get the deltas which will move node towards the local minima deltas = getDeltas(maxDeltaMIndex, lMatrix, kMatrix, xPos, yPos); // set coords of node to old coords + changes xPos[maxDeltaMIndex] += deltas[0]; yPos[maxDeltaMIndex] += deltas[1]; // previousDeltaM = moveNodeDeltaM; // recalculate the deltaM of the node w/ new vals moveNodeDeltaM = getDeltaM(maxDeltaMIndex, lMatrix, kMatrix, xPos, yPos); subPasses++; if (subPasses > maxPasses) stop = true; } // previousDeltaM = maxDeltaM; // recalculate deltaMs and find node with max maxDeltaMIndex = 0; maxDeltaM = getDeltaM(0, lMatrix, kMatrix, xPos, yPos); for (int i = 1; i < nNodes; i++) { deltaM = getDeltaM(i, lMatrix, kMatrix, xPos, yPos); if (deltaM > maxDeltaM) { maxDeltaM = deltaM; maxDeltaMIndex = i; } } // if set to update display, update on every nth pass if (updates > 0) { if ((passes % updates) == 0) { for (int i = 0; i < nNodes; i++) { DrawableNonGridNode node = (DrawableNonGridNode) componentNodes.get(i); node.setX(xPos[i]); node.setY(yPos[i]); } updateDisplay(); } } passes++; } epsilon -= epsilon / 4; } if (animate) animateTransition(8, componentNodes, xPos, yPos); else { // set positions of nodes to coord array vals for (int i = 0; i < nNodes; i++) { DrawableNonGridNode node = (DrawableNonGridNode) componentNodes.get(i); node.setX(xPos[i]); node.setY(yPos[i]); } } }