All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jgraph.layout.organic.JGraphOrganicLayout Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2005, Gaudenz Alder
 * Copyright (c) 2005-2006, David Benson
 *
 * All rights reserved.
 *
 * This file is licensed under the JGraph software license, a copy of which
 * will have been provided to you in the file LICENSE at the root of your
 * installation directory. If you are unable to locate this file please
 * contact JGraph sales for another copy.
 */
package com.jgraph.layout.organic;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jgraph.layout.JGraphFacade;
import com.jgraph.layout.JGraphLayout;
import com.jgraph.layout.JGraphLayoutProgress;

/**
 * An implementation of a simulated annealing layout, based on "Drawing Graphs
 * Nicely Using Simulated Annealing" by Davidson and Harel (1996). This
 * paper describes these criteria as being favourable in a graph layout: (1)
 * distributing nodes evenly, (2) making edge-lengths uniform, (3)
 * minimizing cross-crossings, and (4) keeping nodes from coming too close
 * to edges. These criteria are translated into energy cost functions in the
 * layout. Nodes or edges breaking these criteria create a larger cost function
 * , the total cost they contribute related to the extent that they break it.
 * The idea of the algorithm is to minimise the total system energy. Factors
 * are assigned to each of the criteria describing how important that
 * criteria is. Higher factors mean that those criteria are deemed to be
 * relatively preferable in the final layout. Most of  the criteria conflict
 * with the others to some extent and so the setting of the factors determines
 * the general look of the resulting graph.
 * 

* In addition to the four aesthetic criteria the concept of a border line * which induces an energy cost to nodes in proximity to the graph bounds is * introduced to attempt to restrain the graph. All of the 5 factors can be * switched on or off using the isOptimize... variables. *

* Simulated Annealing is a force-directed layout and is one of the more * expensive, but generally effective layouts of this type. Layouts like * the spring layout only really factor in edge length and inter-node * distance being the lowest CPU intensive for the most aesthetic gain. The * additional factors are more expensive but can have very attractive results. *

* The main loop of the algorithm consist of processing the nodes in a random * order. During the processing of each node a circle of radius * moveRadius is made around the node and split into * triesPerCell equal segments. Each point between neighbour * segments is determined and the new energy of the system if the node were * moved to that position calculated. Only the necessary nodes and edges are * processed new energy values resulting in quadratic performance, O(VE), * whereas calculating the total system energy would be cubic. The default * implementation only checks 8 points around the radius of the circle, as * opposed to the suggested 30 in the paper. Doubling the number of points * double the CPU load and 8 works almost as well as 30. *

* The moveRadius replaces the temperature as the influencing * factor in the way the graph settles in later iterations. If the user does * not set the initial move radius it is set to half the maximum dimension * of the graph. Thus, in 2 iterations a node may traverse the entire graph, * and it is more sensible to find minima this way that uphill moves, which * are little more than an expensive 'tilt' method. The factor but which * the radius is multiplied by after each iteration is important, lowering * it improves performance but raising it towards 1.0 can improve the * resulting graph aesthetics. When the radius hits the minimum move radius * defined, the layout terminates. The minimum move radius should be set * a value where the move distance is too minor to be of interest. *

* Also, the idea of a fine tuning phase is used, as described in the paper. * This involves only calculating the edge to node distance energy cost * at the end of the algorithm since it is an expensive calculation and * it really an 'optimizating' function. fineTuningRadius * defines the radius value that, when reached, causes the edge to node * distance to be calculated. *

* There are other special cases that are processed after each iteration. * unchangedEnergyRoundTermination defines the number of * iterations, after which the layout terminates. If nothing is being moved * it is assumed a good layout has been found. In addition to this if * no nodes are moved during an iteration the move radius is halved, presuming * that a finer granularity is required. * */ public class JGraphOrganicLayout implements JGraphLayout, JGraphLayout.Stoppable { /** * Whether or not the distance between edge and nodes will be calculated * as an energy cost function. This function is CPU intensive and is best * only used in the fine tuning phase. */ protected boolean isOptimizeEdgeDistance = true; /** * Whether or not edges crosses will be calculated as an energy cost * function. This function is CPU intensive, though if some iterations * without it are required, it is best to have a few cycles at the start * of the algorithm using it, then use it intermittantly through the rest * of the layout. */ protected boolean isOptimizeEdgeCrossing = true; /** * Whether or not edge lengths will be calculated as an energy cost * function. This function not CPU intensive. */ protected boolean isOptimizeEdgeLength = true; /** * Whether or not nodes will contribute an energy cost as they approach * the bound of the graph. The cost increases to a limit close to the * border and stays constant outside the bounds of the graph. This function * is not CPU intensive */ protected boolean isOptimizeBorderLine = true; /** * Whether or not node distribute will contribute an energy cost where * nodes are close together. The function is moderately CPU intensive. */ protected boolean isOptimizeNodeDistribution = true; /** * when {@link #moveRadius}reaches this value, the algorithm is terminated */ protected double minMoveRadius = 2.0; /** * The current radius around each node where the next position energy * values will be calculated for a possible move */ protected double moveRadius; /** * The initial value of moveRadius. If this is set to zero * the layout will automatically determine a suitable value. */ protected double initialMoveRadius = 0.0; /** * The factor by which the moveRadius is multiplied by after * every iteration. A value of 0.75 is a good balance between performance * and aesthetics. Increasing the value provides more chances to find * minimum energy positions and decreasing it causes the minimum radius * termination condition to occur more quickly. */ protected double radiusScaleFactor = 0.75; /** * The average amount of area allocated per node. If bounds * is not set this value mutiplied by the number of nodes to find * the total graph area. The graph is assumed square. */ protected double averageNodeArea = 0.0; /** * The radius below which fine tuning of the layout should start * This involves allowing the distance between nodes and edges to be * taken into account in the total energy calculation. If this is set to * zero, the layout will automatically determine a suitable value */ protected double fineTuningRadius = 40.0; /** * Limit to the number of iterations that may take place. This is only * reached if one of the termination conditions does not occur first. */ protected int maxIterations = 100; /** * Cost factor applied to energy calculations involving the distance * nodes and edges. Increasing this value tends to cause nodes to move away * from edges, at the partial cost of other graph aesthetics. * isOptimizeEdgeDistance must be true for edge to nodes * distances to be taken into account. */ protected double edgeDistanceCostFactor = 4000; /** * Cost factor applied to energy calculations involving edges that cross * over one another. Increasing this value tends to result in fewer edge * crossings, at the partial cost of other graph aesthetics. * isOptimizeEdgeCrossing must be true for edge crossings * to be taken into account. */ protected double edgeCrossingCostFactor = 2000; /** * Cost factor applied to energy calculations involving the general node * distribution of the graph. Increasing this value tends to result in * a better distribution of nodes across the available space, at the * partial cost of other graph aesthetics. * isOptimizeNodeDistribution must be true for this general * distribution to be applied. */ protected double nodeDistributionCostFactor = 300000; /** * Cost factor applied to energy calculations for node promixity to the * notional border of the graph. Increasing this value results in * nodes tending towards the centre of the drawing space, at the * partial cost of other graph aesthetics. * isOptimizeBorderLine must be true for border * repulsion to be applied. */ protected double borderLineCostFactor = 5; /** * Cost factor applied to energy calculations for the edge lengths. * Increasing this value results in the layout attempting to shorten all * edges to the minimum edge length, at the partial cost of other graph * aesthetics. * isOptimizeEdgeLength must be true for edge length * shortening to be applied. */ protected double edgeLengthCostFactor = 0.02; /** * The x coordinate of the final graph */ protected double boundsX = 0.0; /** * The y coordinate of the final graph */ protected double boundsY = 0.0; /** * The width coordinate of the final graph */ protected double boundsWidth = 0.0; /** * The height coordinate of the final graph */ protected double boundsHeight = 0.0; /** * current iteration number of the layout */ protected int iteration; /** * determines, in how many segments the circle around cells is divided, to * find a new position for the cell. Doubling this value double the CPU * load. Increasing it beyond 16 might mean a change to the * performRound method might further improve accuracy for a * small performance hit. The change is described in the method comment. */ protected int triesPerCell = 8; /** * prevents from dividing with zero and from creating excessive energy * values */ protected double minDistanceLimit = 2; /** * cached version of minDistanceLimit squared */ protected double minDistanceLimitSquared; /** * distance limit beyond which energy costs due to object repulsive is * not calculated as it would be too insignificant */ protected double maxDistanceLimit = 100; /** * cached version of maxDistanceLimit squared */ protected double maxDistanceLimitSquared; /** * Keeps track of how many consecutive round have passed without any energy * changes */ protected int unchangedEnergyRoundCount; /** * The number of round of no node moves taking placed that the layout * terminates */ protected int unchangedEnergyRoundTermination = 5; /** * Whether or not nodes should be processed in the same order every time. * If true the outcome for a given graph will always be the * same for a constant set of conditions. Random processing of nodes might * produce a slightly better looking results, but the difference is * marginal. */ protected boolean isDeterministic = true; /** * Whether or not to use approximate node dimensions or not. Set to true * the radius squared of the smaller dimension is used. Set to false the * radiusSquared variable of the CellWrapper contains the width squared * and heightSquared is used in the obvious manner. */ protected boolean approxNodeDimensions = true; /** * Internal models collection of nodes ( vertices ) to be laid out */ protected CellWrapper[] v; /** * Internal models collection of edges to be laid out */ protected CellWrapper[] e; /** * Whether or not fine tuning is on. The determines whether or not * node to edge distances are calculated in the total system energy. * This cost function , besides detecting line intersection, is a * performance intensive component of this algorithm and best left * to optimization phase. isFineTuning is switched to * true if and when the fineTuningRadius * radius is reached. Switching this variable to true * before the algorithm runs mean the node to edge cost function * is always calculated. */ protected boolean isFineTuning = true; /** * The facade describing the graph to be laid out */ protected JGraphFacade facade; /** * The layout progress bar */ protected JGraphLayoutProgress progress = new JGraphLayoutProgress(); /** The logger for this class */ private static Logger logger = Logger .getLogger("com.jgraph.layout.organic.JGraphOrganicLayout"); /** * Constructor for JGraphOrganicLayout. */ public JGraphOrganicLayout() { } /** * Constructor for JGraphOrganicLayout. */ public JGraphOrganicLayout(Rectangle2D bounds) { boundsX = bounds.getX(); boundsY = bounds.getY(); boundsWidth = bounds.getWidth(); boundsHeight = bounds.getHeight(); } /** * @return Returns the progress. */ public JGraphLayoutProgress getProgress() { return progress; } /** * Initializes and runs the layout */ public void run(JGraphFacade graph) { this.facade = graph; // Set the facade to be non-directed, directed graphs produce incorrect // results. Store the directed state to reset it after this method boolean directed = facade.isDirected(); facade.setDirected(false); Object[] vertices = graph.getVertices().toArray(); Object[] edges = graph.getEdges().toArray(); // If the bounds dimensions have not been set see if the average area // per node has been Rectangle2D bounds = facade.getGraphBounds(); if (averageNodeArea == 0.0) { if (boundsWidth == 0.0 && bounds != null) { // Just use current bounds of graph boundsX = bounds.getX(); boundsY = bounds.getY(); boundsWidth = bounds.getWidth(); boundsHeight = bounds.getHeight(); } } else { // find the centre point of the current graph // based the new graph bounds on the average node area set double newArea = averageNodeArea * vertices.length; double squareLength = Math.sqrt(newArea); if (bounds != null) { double centreX = bounds.getX() + bounds.getWidth() / 2.0; double centreY = bounds.getY() + bounds.getHeight() / 2.0; boundsX = centreX - squareLength / 2.0; boundsY = centreY - squareLength / 2.0; } else { logger.info("facade.getGraphBounds() returned null"); boundsX = 0; boundsY = 0; } boundsWidth = squareLength; boundsHeight = squareLength; // Ensure x and y are 0 or positive if (boundsX < 0.0 || boundsY < 0.0) { double maxNegativeAxis = Math.min(boundsX, boundsY); double axisOffset = -maxNegativeAxis; boundsX += axisOffset; boundsY += axisOffset; } } // If the initial move radius has not been set find a suitable value. // A good value is half the maximum dimension of the final graph area if (initialMoveRadius == 0.0) { initialMoveRadius = Math.max(boundsWidth, boundsHeight)/2.0; } moveRadius = initialMoveRadius; minDistanceLimitSquared = minDistanceLimit * minDistanceLimit; maxDistanceLimitSquared = maxDistanceLimit * maxDistanceLimit; unchangedEnergyRoundCount = 0; progress.reset(maxIterations); // Form internal model of nodes Map vertexMap = new Hashtable(); v = new CellWrapper[vertices.length]; for (int i = 0; i < vertices.length; i++) { v[i] = new CellWrapper(vertices[i]); vertexMap.put(vertices[i], new Integer(i)); bounds = graph.getBounds(vertices[i]); // Set the X,Y value of the internal version of the cell to // the center point of the vertex for better positioning double width = bounds.getWidth(); double height = bounds.getHeight(); v[i].x = bounds.getX() + width / 2.0; v[i].y = bounds.getY() + height / 2.0; if (approxNodeDimensions) { v[i].radiusSquared = Math.min(width, height); v[i].radiusSquared *= v[i].radiusSquared; } else { v[i].radiusSquared = width * width; v[i].heightSquared = height * height; } } // Form internal model of edges e = new CellWrapper[edges.length]; for (int i = 0; i < e.length; i++) { e[i] = new CellWrapper(edges[i]); Object sourceCell = graph.getSource(edges[i]); Object targetCell = graph.getTarget(edges[i]); Integer source = null; Integer target = null; // Check if either end of the edge is not connected if (sourceCell != null) { source = (Integer) vertexMap.get(sourceCell); } if (targetCell != null) { target = (Integer) vertexMap.get(targetCell); } if (source != null) { e[i].source = source.intValue(); } else { // source end is not connected e[i].source = -1; } if (target != null) { e[i].target = target.intValue(); } else { // target end is not connected e[i].target = -1; } } // Set up internal nodes with information about whether edges // are connected to them or not for (int i = 0; i < v.length; i++) { v[i].relevantEdges = getRelevantEdges(i); v[i].connectedEdges = getConnectedEdges(i); } // The main layout loop for (iteration = 0; iteration < maxIterations && !progress.isStopped(); iteration++) { progress.setProgress(iteration); performRound(); } // Obtain the final positions post them to the facade double[][] result = new double[v.length][2]; for (int i = 0; i < v.length; i++) { vertices[i] = v[i].cell; bounds = graph.getBounds(vertices[i]); // Convert from vertex center points to top left points result[i][0] = v[i].x - bounds.getWidth()/2; result[i][1] = v[i].y - bounds.getHeight()/2; } graph.setLocations(vertices, result); // Reset the directed state of the facade facade.setDirected(directed); } /** * The main round of the algorithm. Firstly, a permutation of nodes * is created and worked through in that random order. Then, for each node * a number of point of a circle of radius moveRadius are * selected and the total energy of the system calculated if that node * were moved to that new position. If a lower energy position is found * this is accepted and the algorithm moves onto the next node. There * may be a slightly lower energy value yet to be found, but forcing * the loop to check all possible positions adds nearly the current * processing time again, and for little benefit. Another possible * strategy would be to take account of the fact that the energy values * around the circle decrease for half the loop and increase for the * other, as a general rule. If part of the decrease were seen, then * when the energy of a node increased, the previous node position was * almost always the lowest energy position. This adds about two loop * iterations to the inner loop and only makes sense with 16 tries or more. */ protected void performRound() { // sequencial order cells are computed (every round the same order) int[] sequence = null; if (!isDeterministic) { sequence = new int[v.length]; sequence = createPermutation(v.length); } // boolean to keep track of whether any moves were made in this round boolean energyHasChanged = false; for (int i = 0; i < v.length; i++) { int index; if (sequence == null) { index = i; } else { index = sequence[i]; } // Obtain the energies for the node is its current position double oldNodeDistribution = getNodeDistribution(index); double oldEdgeDistance = getEdgeDistanceFromNode(index); oldEdgeDistance += getEdgeDistanceAffectedNodes(index); double oldEdgeCrossing = getEdgeCrossingAffectedEdges(index); double oldBorderLine = getBorderline(index); double oldEdgeLength = getEdgeLengthAffectedEdges(index); double oldAdditionFactors = getAdditionFactorsEnergy(index); double offset = 0.0; if (!isDeterministic) { // random offset offset = Math.random() * 2.0 * Math.PI; } for (int j = 0; j < triesPerCell; j++) { if (progress.isStopped()) { return; } double angle = (double) j * ((2.0 * Math.PI) / (double) triesPerCell); angle += offset; double movex = moveRadius * Math.cos(angle); double movey = moveRadius * Math.sin(angle); // applying new move double oldx = v[index].x; double oldy = v[index].y; v[index].x = v[index].x + movex; v[index].y = v[index].y + movey; // calculate the energy delta from this move double energyDelta = calcEnergyDelta(index,oldNodeDistribution, oldEdgeDistance, oldEdgeCrossing, oldBorderLine, oldEdgeLength, oldAdditionFactors); if (energyDelta < 0) { // energy of moved node is lower, finish tries for this // node energyHasChanged = true; break; // exits loop } else { // Revert node coordinates v[index].x = oldx; v[index].y = oldy; } } } // Check if we've hit the limit number of unchanged rounds that cause // a termination condition if (energyHasChanged) { unchangedEnergyRoundCount = 0; } else { unchangedEnergyRoundCount++; // Half the move radius in case assuming it's set too high for // what might be an optimisation case moveRadius /= 2.0; } if (unchangedEnergyRoundCount >= unchangedEnergyRoundTermination){ iteration = maxIterations; } // decrement radius in controlled manner double newMoveRadius = moveRadius * radiusScaleFactor; // Don't waste time on tiny decrements, if the final pixel resolution // is 50 then there's no point doing 55,54.1, 53.2 etc if (moveRadius - newMoveRadius < minMoveRadius) { newMoveRadius = moveRadius - minMoveRadius; } // If the temperature reaches its minimum temperature then finish if (newMoveRadius <= minMoveRadius) iteration = maxIterations; // Switch on fine tuning below the specified temperature if (newMoveRadius < fineTuningRadius) isFineTuning = true; moveRadius = newMoveRadius; } /** * Calculates the change in energy for the specified node. The new energy is * calculated from the cost function methods and the old energy values for * each cost function are passed in as parameters * * @param index * The index of the node in the vertices array * @param oldNodeDistribution * The previous node distribution energy cost of this node * @param oldEdgeDistance * The previous edge distance energy cost of this node * @param oldEdgeCrossing * The previous edge crossing energy cost for edges connected to * this node * @param oldBorderLine * The previous border line energy cost for this node * @param oldEdgeLength * The previous edge length energy cost for edges connected to * this node * @param oldAdditionalFactorsEnergy * The previous energy cost for additional factors from * sub-classes * * @return the delta of the new energy cost to the old energy cost * */ protected double calcEnergyDelta(int index, double oldNodeDistribution, double oldEdgeDistance, double oldEdgeCrossing, double oldBorderLine, double oldEdgeLength, double oldAdditionalFactorsEnergy) { double energyDelta = 0.0; energyDelta += getNodeDistribution(index) * 2.0; energyDelta -= oldNodeDistribution * 2.0; energyDelta += getBorderline(index); energyDelta -= oldBorderLine; energyDelta += getEdgeDistanceFromNode(index); energyDelta += getEdgeDistanceAffectedNodes(index); energyDelta -= oldEdgeDistance; energyDelta -= oldEdgeLength; energyDelta += getEdgeLengthAffectedEdges(index); energyDelta -= oldEdgeCrossing; energyDelta += getEdgeCrossingAffectedEdges(index); energyDelta -= oldAdditionalFactorsEnergy; energyDelta += getAdditionFactorsEnergy(index); return energyDelta; } /** * Creates a random permutation of the numbers from 0 to * length * * @param length * number of different numbers to be generated * @return Permutation of the Numbers between 0 and length */ protected int[] createPermutation(int length) { int[] permutation = new int[length]; for (int i = 0; i < permutation.length; i++) permutation[i] = i; for (int i = 0; i < permutation.length; i++) { int t = permutation[i]; int randomIndex = (int) (Math.random() * (double) (length - 1)); // Swap values permutation[i] = permutation[randomIndex]; permutation[randomIndex] = t; } return permutation; } /** * Calculates the energy cost of the specified node relative to all other * nodes. Basically produces a higher energy the closer nodes are together. * * @param i the index of the node in the array v * @return the total node distribution energy of the specified node */ protected double getNodeDistribution(int i) { double energy = 0.0; // This check is placed outside of the inner loop for speed, even // though the code then has to be duplicated if (isOptimizeNodeDistribution == true) { if (approxNodeDimensions) { for (int j = 0; j < v.length; j++) { if (i != j) { double vx = v[i].x - v[j].x; double vy = v[i].y - v[j].y; double distanceSquared = vx * vx + vy * vy; distanceSquared -= v[i].radiusSquared; distanceSquared -= v[j].radiusSquared; // prevents from dividing with Zero. if (distanceSquared < minDistanceLimitSquared) { distanceSquared = minDistanceLimitSquared; } energy += nodeDistributionCostFactor / distanceSquared; } } } else { for (int j = 0; j < v.length; j++) { if (i != j) { double vx = v[i].x - v[j].x; double vy = v[i].y - v[j].y; double distanceSquared = vx * vx + vy * vy; distanceSquared -= v[i].radiusSquared; distanceSquared -= v[j].radiusSquared; // If the height seperation indicates overlap, subtract // the widths from the distance. Same for width overlap // TODO if () // prevents from dividing with Zero. if (distanceSquared < minDistanceLimitSquared) { distanceSquared = minDistanceLimitSquared; } energy += nodeDistributionCostFactor / distanceSquared; } } } } return energy; } /** * This method calculates the energy of the distance of the specified * node to the notional border of the graph. The energy increases up to * a limited maximum close to the border and stays at that maximum * up to and over the border. * * @param i the index of the node in the array v * @return the total border line energy of the specified node */ protected double getBorderline(int i) { double energy = 0.0; if (isOptimizeBorderLine) { // Avoid very small distances and convert negative distance (i.e // outside the border to small positive ones ) double l = v[i].x - boundsX; if (l < minDistanceLimit) l = minDistanceLimit; double t = v[i].y - boundsY; if (t < minDistanceLimit) t = minDistanceLimit; double r = boundsX + boundsWidth - v[i].x; if (r < minDistanceLimit) r = minDistanceLimit; double b = boundsY + boundsHeight - v[i].y; if (b < minDistanceLimit) b = minDistanceLimit; energy += borderLineCostFactor * ((1000000.0 / (t * t)) + (1000000.0 / (l * l)) + (1000000.0 / (b * b)) + (1000000.0 / (r * r))); } return energy; } /** * Obtains the energy cost function for the specified node being moved. * This involves calling getEdgeLength for all * edges connected to the specified node * @param node * the node whose connected edges cost functions are to be * calculated * @return the total edge length energy of the connected edges */ protected double getEdgeLengthAffectedEdges(int node) { double energy = 0.0; for (int i=0; i < v[node].connectedEdges.length; i++) { energy += getEdgeLength(v[node].connectedEdges[i]); } return energy; } /** * This method calculates the energy due to the length of the specified * edge. The energy is proportional to the length of the edge, making * shorter edges preferable in the layout. * * @param i the index of the edge in the array e * @return the total edge length energy of the specified edge */ protected double getEdgeLength(int i) { if (isOptimizeEdgeLength) { double edgeLength = Point2D.distance(v[e[i].source].x, v[e[i].source].y, v[e[i].target].x, v[e[i].target].y); return (edgeLengthCostFactor * edgeLength * edgeLength); } else { return 0.0; } } /** * Obtains the energy cost function for the specified node being moved. * This involves calling getEdgeCrossing for all * edges connected to the specified node * @param node * the node whose connected edges cost functions are to be * calculated * @return the total edge crossing energy of the connected edges */ protected double getEdgeCrossingAffectedEdges(int node) { double energy = 0.0; for (int i=0; i < v[node].connectedEdges.length; i++) { energy += getEdgeCrossing(v[node].connectedEdges[i]); } return energy; } /** * This method calculates the energy of the distance from the specified * edge crossing any other edges. Each crossing add a constant factor * to the total energy * * @param i the index of the edge in the array e * @return the total edge crossing energy of the specified edge */ protected double getEdgeCrossing(int i) { // TODO Could have a cost function per edge int n = 0; // counts energy of edgecrossings through edge i // max and min variable for minimum bounding rectangles overlapping // checks double minjX, minjY, miniX, miniY, maxjX, maxjY, maxiX, maxiY; if (isOptimizeEdgeCrossing) { double iP1X = v[e[i].source].x; double iP1Y = v[e[i].source].y; double iP2X = v[e[i].target].x; double iP2Y = v[e[i].target].y; for (int j = 0; j < e.length; j++) { double jP1X = v[e[j].source].x; double jP1Y = v[e[j].source].y; double jP2X = v[e[j].target].x; double jP2Y = v[e[j].target].y; if (j != i) { // First check is to see if the minimum bounding rectangles // of the edges overlap at all. Since the layout tries // to separate nodes and shorten edges, the majority do not // overlap and this is a cheap way to avoid most of the // processing // Some long code to avoid a Math.max call... if (iP1X < iP2X) { miniX = iP1X; maxiX = iP2X; } else { miniX = iP2X; maxiX = iP1X; } if (jP1X < jP2X) { minjX = jP1X; maxjX = jP2X; } else { minjX = jP2X; maxjX = jP1X; } if (maxiX < minjX || miniX > maxjX) continue; if (iP1Y < iP2Y) { miniY = iP1Y; maxiY = iP2Y; } else { miniY = iP2Y; maxiY = iP1Y; } if (jP1Y < jP2Y) { minjY = jP1Y; maxjY = jP2Y; } else { minjY = jP2Y; maxjY = jP1Y; } if (maxiY < minjY || miniY > maxjY) continue; // Ignore if any end points are coincident if (((iP1X != jP1X) && (iP1Y != jP1Y)) && ((iP1X != jP2X) && (iP1Y != jP2Y)) && ((iP2X != jP1X) && (iP2Y != jP1Y)) && ((iP2X != jP2X) && (iP2Y != jP2Y))) { // Values of zero returned from Line2D.relativeCCW are // ignored because the point being exactly on the line // is very rare for double and we've already checked if // any end point share the same vertex. Should zero // ever be returned, it would be the vertex connected // to the edge that's actually on the edge and this is // dealt with by the node to edge distance cost // function. The worst case is that the vertex is // pushed off the edge faster than it would be // otherwise. Because of ignoring the zero this code // below can behave like only a 1 or -1 will be // returned. See Lines2D.linesIntersects(). boolean intersects = ((Line2D.relativeCCW(iP1X, iP1Y, iP2X, iP2Y, jP1X, jP1Y) != Line2D.relativeCCW(iP1X, iP1Y, iP2X, iP2Y, jP2X, jP2Y)) && (Line2D.relativeCCW(jP1X, jP1Y, jP2X, jP2Y, iP1X, iP1Y) != Line2D.relativeCCW(jP1X, jP1Y, jP2X, jP2Y, iP2X, iP2Y))); if (intersects) { n++; } } } } } return edgeCrossingCostFactor * (double) n; } /** * This method calculates the energy of the distance between Cells and * Edges. This version of the edge distance cost calculates the energy * cost from a specified node. The distance cost to all * unconnected edges is calculated and the total returned. * * @param i the index of the node in the array v * @return the total edge distance energy of the node */ protected double getEdgeDistanceFromNode(int i) { double energy = 0.0; // This function is only performed during fine tuning for performance if (isOptimizeEdgeDistance && isFineTuning) { int[] edges = v[i].relevantEdges; for (int j = 0; j < edges.length; j++) { // Note that the distance value is squared double distSquare = Line2D.ptSegDistSq(v[e[edges[j]].source].x, v[e[edges[j]].source].y, v[e[edges[j]].target].x, v[e[edges[j]].target].y, v[i].x, v[i].y); distSquare -= v[i].radiusSquared; // prevents from dividing with Zero. No Math.abs() call // for performance if (distSquare < minDistanceLimitSquared) { distSquare = minDistanceLimitSquared; } // Only bother with the divide if the node and edge are // fairly close together if (distSquare < maxDistanceLimitSquared) { energy += edgeDistanceCostFactor / distSquare; } } } return energy; } /** * Obtains the energy cost function for the specified node being moved. * This involves calling getEdgeDistanceFromEdge for all * edges connected to the specified node * @param node * the node whose connected edges cost functions are to be * calculated * @return the total edge distance energy of the connected edges */ protected double getEdgeDistanceAffectedNodes(int node) { double energy = 0.0; for (int i=0; i < v[node].connectedEdges.length; i++) { energy += getEdgeDistanceFromEdge(v[node].connectedEdges[i]); } return energy; } /** * This method calculates the energy of the distance between Cells and * Edges. This version of the edge distance cost calculates the energy * cost from a specified edge. The distance cost to all * unconnected nodes is calculated and the total returned. * * @param i the index of the edge in the array e * @return the total edge distance energy of the edge */ protected double getEdgeDistanceFromEdge(int i) { double energy = 0.0; // This function is only performed during fine tuning for performance if (isOptimizeEdgeDistance && isFineTuning) { for (int j = 0; j < v.length; j++) { // Don't calculate for connected nodes if (e[i].source != j && e[i].target != j) { double distSquare = Line2D.ptSegDistSq(v[e[i].source].x, v[e[i].source].y, v[e[i].target].x, v[e[i].target].y, v[j].x, v[j].y); distSquare -= v[j].radiusSquared; // prevents from dividing with Zero. No Math.abs() call // for performance if (distSquare < minDistanceLimitSquared) distSquare = minDistanceLimitSquared; // Only bother with the divide if the node and edge are // fairly close together if (distSquare < maxDistanceLimitSquared) { energy += edgeDistanceCostFactor / distSquare; } } } } return energy; } /** * Hook method to adding additional energy factors into the layout. * Calculates the energy just for the specified node. * @param i the nodes whose energy is being calculated * @return the energy of this node caused by the additional factors */ protected double getAdditionFactorsEnergy(int i) { return 0.0; } /** * Returns all Edges that are not connected to the specifed cell * * @param cellIndex * the cell index to which the edges are not connected * @return Array of all interesting Edges */ protected int[] getRelevantEdges(int cellIndex) { ArrayList relevantEdgeList = new ArrayList(e.length); for (int i = 0; i < e.length; i++) if (e[i].source != cellIndex && e[i].target != cellIndex) // Add non-connected edges relevantEdgeList.add(new Integer(i)); int[] relevantEdgeArray = new int[relevantEdgeList.size()]; Iterator iter = relevantEdgeList.iterator(); //Reform the list into an array but replace Integer values with ints for (int i = 0; i < relevantEdgeArray.length; i++) { if (iter.hasNext()) { relevantEdgeArray[i] = ((Integer)iter.next()).intValue();; } } return relevantEdgeArray; } /** * Returns all Edges that are connected with the specified cell * * @param cellIndex * the cell index to which the edges are connected * @return Array of all connected Edges */ protected int[] getConnectedEdges(int cellIndex) { ArrayList connectedEdgeList = new ArrayList(e.length); for (int i = 0; i < e.length; i++) if (e[i].source == cellIndex || e[i].target == cellIndex) // Add connected edges to list by their index number connectedEdgeList.add(new Integer(i)); int[] connectedEdgeArray = new int[connectedEdgeList.size()]; Iterator iter = connectedEdgeList.iterator(); // Reform the list into an array but replace Integer values with ints for (int i = 0; i < connectedEdgeArray.length; i++) { if (iter.hasNext()) { connectedEdgeArray[i] = ((Integer)iter.next()).intValue();; } } return connectedEdgeArray; } /** * Returns Annealing, the name of this algorithm. */ public String toString() { return "Annealing"; } /** * Internal representation of a node or edge that holds cached information * to enable the layout to perform more quickly and to simplify the code */ public class CellWrapper { /** * The actual graph cell this wrapper represents */ protected Object cell; /** * All edge that repel this cell, only used for nodes. This array * is equivalent to all edges unconnected to this node */ protected int[] relevantEdges = null; /** * the index of all connected edges in the e array * to this node. This is only used for nodes. */ protected int[] connectedEdges = null; /** * The x-coordinate position of this cell, nodes only */ protected double x; /** * The y-coordinate position of this cell, nodes only */ protected double y; /** * The approximate radius squared of this cell, nodes only. If * approxNodeDimensions is true on the layout this value holds the * width of the node squared */ protected double radiusSquared; /** * The height of the node squared, only used if approxNodeDimensions * is set to true. */ protected double heightSquared; /** * The index of the node attached to this edge as source, edges only */ protected int source; /** * The index of the node attached to this edge as target, edges only */ protected int target; /** * Constructs a new CellWrapper * @param cell the graph cell this wrapper represents */ public CellWrapper(Object cell) { this.cell = cell; } /** * @return the relevantEdges */ public int[] getRelevantEdges() { return relevantEdges; } /** * @param relevantEdges the relevantEdges to set */ public void setRelevantEdges(int[] relevantEdges) { this.relevantEdges = relevantEdges; } /** * @return the connectedEdges */ public int[] getConnectedEdges() { return connectedEdges; } /** * @param connectedEdges the connectedEdges to set */ public void setConnectedEdges(int[] connectedEdges) { this.connectedEdges = connectedEdges; } /** * @return the x */ public double getX() { return x; } /** * @param x the x to set */ public void setX(double x) { this.x = x; } /** * @return the y */ public double getY() { return y; } /** * @param y the y to set */ public void setY(double y) { this.y = y; } /** * @return the radiusSquared */ public double getRadiusSquared() { return radiusSquared; } /** * @param radiusSquared the radiusSquared to set */ public void setRadiusSquared(double radiusSquared) { this.radiusSquared = radiusSquared; } /** * @return the heightSquared */ public double getHeightSquared() { return heightSquared; } /** * @param heightSquared the heightSquared to set */ public void setHeightSquared(double heightSquared) { this.heightSquared = heightSquared; } /** * @return the source */ public int getSource() { return source; } /** * @param source the source to set */ public void setSource(int source) { this.source = source; } /** * @return the target */ public int getTarget() { return target; } /** * @param target the target to set */ public void setTarget(int target) { this.target = target; } /** * @return the cell */ public Object getCell() { return cell; } } /** * @return Returns the averageNodeArea. */ public double getAverageNodeArea() { return averageNodeArea; } /** * @param averageNodeArea The averageNodeArea to set. */ public void setAverageNodeArea(double averageNodeArea) { this.averageNodeArea = averageNodeArea; } /** * @return Returns the borderLineCostFactor. */ public double getBorderLineCostFactor() { return borderLineCostFactor; } /** * @param borderLineCostFactor The borderLineCostFactor to set. */ public void setBorderLineCostFactor(double borderLineCostFactor) { this.borderLineCostFactor = borderLineCostFactor; } /** * @return Returns the edgeCrossingCostFactor. */ public double getEdgeCrossingCostFactor() { return edgeCrossingCostFactor; } /** * @param edgeCrossingCostFactor The edgeCrossingCostFactor to set. */ public void setEdgeCrossingCostFactor(double edgeCrossingCostFactor) { this.edgeCrossingCostFactor = edgeCrossingCostFactor; } /** * @return Returns the edgeDistanceCostFactor. */ public double getEdgeDistanceCostFactor() { return edgeDistanceCostFactor; } /** * @param edgeDistanceCostFactor The edgeDistanceCostFactor to set. */ public void setEdgeDistanceCostFactor(double edgeDistanceCostFactor) { this.edgeDistanceCostFactor = edgeDistanceCostFactor; } /** * @return Returns the edgeLengthCostFactor. */ public double getEdgeLengthCostFactor() { return edgeLengthCostFactor; } /** * @param edgeLengthCostFactor The edgeLengthCostFactor to set. */ public void setEdgeLengthCostFactor(double edgeLengthCostFactor) { this.edgeLengthCostFactor = edgeLengthCostFactor; } /** * @return Returns the fineTuningRadius. */ public double getFineTuningRadius() { return fineTuningRadius; } /** * @param fineTuningRadius The fineTuningRadius to set. */ public void setFineTuningRadius(double fineTuningRadius) { this.fineTuningRadius = fineTuningRadius; } /** * @return Returns the initialMoveRadius. */ public double getInitialMoveRadius() { return initialMoveRadius; } /** * @param initialMoveRadius The initialMoveRadius to set. */ public void setInitialMoveRadius(double initialMoveRadius) { this.initialMoveRadius = initialMoveRadius; } /** * @return Returns the isFineTuning. */ public boolean isFineTuning() { return isFineTuning; } /** * @param isFineTuning The isFineTuning to set. */ public void setFineTuning(boolean isFineTuning) { this.isFineTuning = isFineTuning; } /** * @return Returns the isOptimizeBorderLine. */ public boolean isOptimizeBorderLine() { return isOptimizeBorderLine; } /** * @param isOptimizeBorderLine The isOptimizeBorderLine to set. */ public void setOptimizeBorderLine(boolean isOptimizeBorderLine) { this.isOptimizeBorderLine = isOptimizeBorderLine; } /** * @return Returns the isOptimizeEdgeCrossing. */ public boolean isOptimizeEdgeCrossing() { return isOptimizeEdgeCrossing; } /** * @param isOptimizeEdgeCrossing The isOptimizeEdgeCrossing to set. */ public void setOptimizeEdgeCrossing(boolean isOptimizeEdgeCrossing) { this.isOptimizeEdgeCrossing = isOptimizeEdgeCrossing; } /** * @return Returns the isOptimizeEdgeDistance. */ public boolean isOptimizeEdgeDistance() { return isOptimizeEdgeDistance; } /** * @param isOptimizeEdgeDistance The isOptimizeEdgeDistance to set. */ public void setOptimizeEdgeDistance(boolean isOptimizeEdgeDistance) { this.isOptimizeEdgeDistance = isOptimizeEdgeDistance; } /** * @return Returns the isOptimizeEdgeLength. */ public boolean isOptimizeEdgeLength() { return isOptimizeEdgeLength; } /** * @param isOptimizeEdgeLength The isOptimizeEdgeLength to set. */ public void setOptimizeEdgeLength(boolean isOptimizeEdgeLength) { this.isOptimizeEdgeLength = isOptimizeEdgeLength; } /** * @return Returns the isOptimizeNodeDistribution. */ public boolean isOptimizeNodeDistribution() { return isOptimizeNodeDistribution; } /** * @param isOptimizeNodeDistribution The isOptimizeNodeDistribution to set. */ public void setOptimizeNodeDistribution(boolean isOptimizeNodeDistribution) { this.isOptimizeNodeDistribution = isOptimizeNodeDistribution; } /** * @return Returns the maxIterations. */ public int getMaxIterations() { return maxIterations; } /** * @param maxIterations The maxIterations to set. */ public void setMaxIterations(int maxIterations) { this.maxIterations = maxIterations; } /** * @return Returns the minDistanceLimit. */ public double getMinDistanceLimit() { return minDistanceLimit; } /** * @param minDistanceLimit The minDistanceLimit to set. */ public void setMinDistanceLimit(double minDistanceLimit) { this.minDistanceLimit = minDistanceLimit; } /** * @return Returns the minMoveRadius. */ public double getMinMoveRadius() { return minMoveRadius; } /** * @param minMoveRadius The minMoveRadius to set. */ public void setMinMoveRadius(double minMoveRadius) { this.minMoveRadius = minMoveRadius; } /** * @return Returns the nodeDistributionCostFactor. */ public double getNodeDistributionCostFactor() { return nodeDistributionCostFactor; } /** * @param nodeDistributionCostFactor The nodeDistributionCostFactor to set. */ public void setNodeDistributionCostFactor(double nodeDistributionCostFactor) { this.nodeDistributionCostFactor = nodeDistributionCostFactor; } /** * @return Returns the radiusScaleFactor. */ public double getRadiusScaleFactor() { return radiusScaleFactor; } /** * @param radiusScaleFactor The radiusScaleFactor to set. */ public void setRadiusScaleFactor(double radiusScaleFactor) { this.radiusScaleFactor = radiusScaleFactor; } /** * @return Returns the triesPerCell. */ public int getTriesPerCell() { return triesPerCell; } /** * @param triesPerCell The triesPerCell to set. */ public void setTriesPerCell(int triesPerCell) { this.triesPerCell = triesPerCell; } /** * @return Returns the unchangedEnergyRoundTermination. */ public int getUnchangedEnergyRoundTermination() { return unchangedEnergyRoundTermination; } /** * @param unchangedEnergyRoundTermination The unchangedEnergyRoundTermination to set. */ public void setUnchangedEnergyRoundTermination( int unchangedEnergyRoundTermination) { this.unchangedEnergyRoundTermination = unchangedEnergyRoundTermination; } /** * @return Returns the maxDistanceLimit. */ public double getMaxDistanceLimit() { return maxDistanceLimit; } /** * @param maxDistanceLimit The maxDistanceLimit to set. */ public void setMaxDistanceLimit(double maxDistanceLimit) { this.maxDistanceLimit = maxDistanceLimit; } /** * @return Returns the isDeterministic. */ public boolean isDeterministic() { return isDeterministic; } /** * @param isDeterministic The isDeterministic to set. */ public void setDeterministic(boolean isDeterministic) { this.isDeterministic = isDeterministic; } /** * Sets the logging level of this class * @param level the logging level to set */ public void setLoggerLevel(Level level) { try { logger.setLevel(level); } catch (SecurityException e) { // Probably running in an applet } } /** * @return the approxNodeDimensions */ public boolean isApproxNodeDimensions() { return approxNodeDimensions; } /** * @param approxNodeDimensions the approxNodeDimensions to set */ public void setApproxNodeDimensions(boolean approxNodeDimensions) { this.approxNodeDimensions = approxNodeDimensions; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy