| /******************************************************************************* |
| * Copyright 2005, CHISEL Group, University of Victoria, Victoria, BC, Canada. |
| * All rights reserved. This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License v1.0 which |
| * accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: The Chisel Group, University of Victoria |
| *******************************************************************************/ |
| package org.eclipse.zest.layouts.algorithms; |
| |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.zest.layouts.LayoutStyles; |
| import org.eclipse.zest.layouts.dataStructures.DisplayIndependentRectangle; |
| import org.eclipse.zest.layouts.dataStructures.InternalNode; |
| import org.eclipse.zest.layouts.dataStructures.InternalRelationship; |
| |
| /** |
| * The SpringLayoutAlgorithm has its own data repository and relation |
| * repository. A user can populate the repository, specify the layout |
| * conditions, do the computation and query the computed results. |
| * <p> |
| * Instructions for using SpringLayoutAlgorithm: <br> |
| * 1. Instantiate a SpringLayout object; <br> |
| * 2. Populate the data repository using {@link #add add(...)}; <br> |
| * 3. Populate the relation repository using |
| * {@link #addRelation addRelation(...)}; <br> |
| * 4. Execute {@link #compute compute()}; <br> |
| * 5. Execute {@link #fitWithinBounds fitWithinBounds(...)}; <br> |
| * 6. Query the computed results(node size and node position). |
| * |
| * @version 2.0 |
| * @author Ian Bull |
| * @author Casey Best (version 1.0 by Jingwei Wu/Rob Lintern) |
| */ |
| public class SpringLayoutAlgorithm extends ContinuousLayoutAlgorithm { |
| |
| private final static boolean DEFAULT_ANCHOR = false; |
| |
| /** |
| * The default value for the spring layout number of interations. |
| */ |
| public static final int DEFAULT_SPRING_ITERATIONS = 1000; |
| |
| /** |
| * the default value for the time algorithm runs. |
| */ |
| public static final long MAX_SPRING_TIME = 10000; |
| |
| /** |
| * The default value for positioning nodes randomly. |
| */ |
| public static final boolean DEFAULT_SPRING_RANDOM = true; |
| |
| /** |
| * The default value for ignoring unconnected nodes. |
| */ |
| public static final boolean DEFAULT_SPRING_IGNORE_UNCON = true; |
| |
| /** |
| * The default value for separating connected components. |
| */ |
| public static final boolean DEFAULT_SPRING_SEPARATE_COMPONENTS = true; |
| |
| /** |
| * The default value for the spring layout move-control. |
| */ |
| public static final double DEFAULT_SPRING_MOVE = 1.0f; |
| |
| /** |
| * The default value for the spring layout strain-control. |
| */ |
| public static final double DEFAULT_SPRING_STRAIN = 1.0f; |
| |
| /** |
| * The default value for the spring layout length-control. |
| */ |
| public static final double DEFAULT_SPRING_LENGTH = 1.0f; |
| |
| /** |
| * The default value for the spring layout gravitation-control. |
| */ |
| public static final double DEFAULT_SPRING_GRAVITATION = 1.0f; |
| |
| /** |
| * The variable can be customized to set the number of iterations used. |
| */ |
| private static int sprIterations = DEFAULT_SPRING_ITERATIONS; |
| |
| /** |
| * This variable can be customized to set the max number of MS the algorithm |
| * should run |
| */ |
| private static long maxTimeMS = MAX_SPRING_TIME; |
| |
| /** |
| * The variable can be customized to set whether or not the spring layout |
| * nodes are positioned randomly before beginning iterations. |
| */ |
| private static boolean sprRandom = DEFAULT_SPRING_RANDOM; |
| |
| /** |
| * Minimum distance considered between nodes |
| */ |
| protected static final double MIN_DISTANCE = 0.001d; |
| |
| /** |
| * An arbitrarily small value in mathematics. |
| */ |
| protected static final double EPSILON = 0.001d; |
| |
| /** |
| * The variable can be customerized to set the spring layout move-control. |
| */ |
| private static double sprMove = DEFAULT_SPRING_MOVE; |
| |
| /** |
| * The variable can be customized to set the spring layout strain-control. |
| */ |
| private static double sprStrain = DEFAULT_SPRING_STRAIN; |
| |
| /** |
| * The variable can be customized to set the spring layout length-control. |
| */ |
| private static double sprLength = DEFAULT_SPRING_LENGTH; |
| |
| /** |
| * The variable can be customized to set the spring layout |
| * gravitation-control. |
| */ |
| private static double sprGravitation = DEFAULT_SPRING_GRAVITATION; |
| |
| /** |
| * The largest movement of all vertices that has occured in the most recent |
| * iteration. |
| */ |
| private double largestMovement = 0; |
| |
| /** |
| * Maps a src and dest object to the number of relations between them. Key |
| * is src.toString() + dest.toString(), value is an Integer |
| */ |
| private Map srcDestToNumRelsMap; |
| |
| /** |
| * Maps a src and dest object to the average weight of the relations between |
| * them. Key is src.toString() + dest.toString(), value is a Double |
| */ |
| private Map srcDestToRelsAvgWeightMap; |
| |
| /** |
| * Maps a relationship type to a weight. Key is a string, value is a Double |
| */ |
| private static Map relTypeToWeightMap = new HashMap(); |
| |
| private int iteration; |
| |
| private int[][] srcDestToNumRels; |
| |
| private double[][] srcDestToRelsAvgWeight; |
| |
| private double[] tempLocationsX; |
| |
| private double[] tempLocationsY; |
| |
| private double[] forcesX; |
| |
| private double[] forcesY; |
| |
| private boolean[] anchors; |
| |
| private DisplayIndependentRectangle bounds = null; |
| |
| Date date = null; |
| |
| /** |
| * Constructor. |
| */ |
| public SpringLayoutAlgorithm(int styles) { |
| super(styles); |
| srcDestToNumRelsMap = new HashMap(); |
| srcDestToRelsAvgWeightMap = new HashMap(); |
| date = new Date(); |
| } |
| |
| /** |
| * Creates a sprint layout algoirthm with no style |
| * |
| */ |
| public SpringLayoutAlgorithm() { |
| this(LayoutStyles.NONE); |
| } |
| |
| public void setLayoutArea(double x, double y, double width, double height) { |
| bounds = new DisplayIndependentRectangle(x, y, width, height); |
| } |
| |
| /** |
| * Sets the spring layout move-control. |
| * |
| * @param move |
| * The move-control value. |
| */ |
| public void setSpringMove(double move) { |
| sprMove = move; |
| } |
| |
| /** |
| * Returns the move-control value of this SpringLayoutAlgorithm in |
| * double presion. |
| * |
| * @return The move-control value. |
| */ |
| public double getSpringMove() { |
| return sprMove; |
| } |
| |
| /** |
| * Sets the spring layout strain-control. |
| * |
| * @param strain |
| * The strain-control value. |
| */ |
| public void setSpringStrain(double strain) { |
| sprStrain = strain; |
| } |
| |
| /** |
| * Returns the strain-control value of this SpringLayoutAlgorithm in |
| * double presion. |
| * |
| * @return The strain-control value. |
| */ |
| public double getSpringStrain() { |
| return sprStrain; |
| } |
| |
| /** |
| * Sets the spring layout length-control. |
| * |
| * @param length |
| * The length-control value. |
| */ |
| public void setSpringLength(double length) { |
| sprLength = length; |
| } |
| |
| /** |
| * Gets the max time this algorithm will run for |
| * |
| * @return |
| */ |
| public long getSpringTimeout() { |
| return maxTimeMS; |
| } |
| |
| /** |
| * Sets the spring timeout |
| * |
| * @param timeout |
| */ |
| public void setSpringTimeout(long timeout) { |
| maxTimeMS = timeout; |
| } |
| |
| /** |
| * Returns the length-control value of this SpringLayoutAlgorithm in |
| * double presion. |
| * |
| * @return The length-control value. |
| */ |
| public double getSpringLength() { |
| return sprLength; |
| } |
| |
| /** |
| * Sets the spring layout gravitation-control. |
| * |
| * @param gravitation |
| * The gravitation-control value. |
| */ |
| public void setSpringGravitation(double gravitation) { |
| sprGravitation = gravitation; |
| } |
| |
| /** |
| * Returns the gravitation-control value of this SpringLayoutAlgorithm |
| * in double presion. |
| * |
| * @return The gravitation-control value. |
| */ |
| public double getSpringGravitation() { |
| return sprGravitation; |
| } |
| |
| /** |
| * Sets the number of iterations to be used. |
| * |
| * @param gravitation |
| * The number of iterations. |
| */ |
| public void setIterations(int iterations) { |
| sprIterations = iterations; |
| } |
| |
| /** |
| * Returns the number of iterations to be used. |
| * |
| * @return The number of iterations. |
| */ |
| public int getIterations() { |
| return sprIterations; |
| } |
| |
| /** |
| * Sets whether or not this SpringLayoutAlgorithm will layout the |
| * nodes randomly before beginning iterations. |
| * |
| * @param random |
| * The random placement value. |
| */ |
| public void setRandom(boolean random) { |
| sprRandom = random; |
| } |
| |
| /** |
| * Returns whether or not this SpringLayoutAlgorithm will layout the |
| * nodes randomly before beginning iterations. |
| */ |
| public boolean getRandom() { |
| return sprRandom; |
| } |
| |
| public void setWeight(String relType, double weight) { |
| relTypeToWeightMap.put(relType, new Double(weight)); |
| } |
| |
| public double getWeight(String relType) { |
| Double weight = (Double) relTypeToWeightMap.get(relType); |
| return (weight == null) ? 1 : weight.doubleValue(); |
| } |
| |
| /** |
| * Sets the default conditions. |
| */ |
| public void setDefaultConditions() { |
| // sprMove = DEFAULT_SPRING_MOVE; |
| // sprStrain = DEFAULT_SPRING_STRAIN; |
| // sprLength = DEFAULT_SPRING_LENGTH; |
| // sprGravitation = DEFAULT_SPRING_GRAVITATION; |
| // sprIterations = DEFAULT_SPRING_ITERATIONS; |
| } |
| |
| /** |
| * Clean up after done |
| * |
| * @param entitiesToLayout |
| */ |
| private void reset(InternalNode[] entitiesToLayout) { |
| tempLocationsX = null; |
| tempLocationsY = null; |
| forcesX = null; |
| forcesY = null; |
| anchors = null; |
| setDefaultConditions(); |
| srcDestToNumRelsMap = new HashMap(); |
| srcDestToRelsAvgWeightMap = new HashMap(); |
| relTypeToWeightMap = new HashMap(); |
| } |
| |
| private long startTime = 0; |
| |
| protected void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) { |
| // TODO: Filter out any non-wanted entities and relationships |
| // super.applyLayout(entitiesToLayout, relationshipsToConsider, x, y, |
| // width, height); |
| //InternalNode[] a_entitiesToLayout = (InternalNode[]) entitiesToLayout.toArray(new InternalNode[entitiesToLayout.size()]); |
| bounds = new DisplayIndependentRectangle(x, y, width, height); |
| tempLocationsX = new double[entitiesToLayout.length]; |
| tempLocationsY = new double[entitiesToLayout.length]; |
| forcesX = new double[entitiesToLayout.length]; |
| forcesY = new double[entitiesToLayout.length]; |
| anchors = new boolean[entitiesToLayout.length]; |
| |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| anchors[i] = DEFAULT_ANCHOR; |
| } |
| for (int i = 0; i < relationshipsToConsider.length; i++) { |
| InternalRelationship layoutRelationship = relationshipsToConsider[i]; |
| addRelation(layoutRelationship); |
| } |
| |
| // do the calculations |
| preCompute(entitiesToLayout); |
| startTime = date.getTime(); |
| } |
| |
| protected void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider) { |
| reset(entitiesToLayout); |
| } |
| |
| /** |
| * Adds a simple relation between two nodes to the relation repository. |
| * |
| * @param layoutRelationship |
| * The simple relation to be added |
| * @throws java.lang.NullPointerExcetption |
| * If <code>sr</code> is null |
| * @see SimpleRelation |
| */ |
| private void addRelation(InternalRelationship layoutRelationship) { |
| if (layoutRelationship == null) { |
| throw new IllegalArgumentException("The arguments can not be null!"); |
| } else { |
| double weight = layoutRelationship.getWeight(); |
| weight = (weight <= 0 ? 0.1 : weight); |
| String key1 = layoutRelationship.getSource().toString() + layoutRelationship.getDestination().toString(); |
| String key2 = layoutRelationship.getDestination().toString() + layoutRelationship.getSource().toString(); |
| String[] keys = { key1, key2 }; |
| for (int i = 0; i < keys.length; i++) { |
| String key = keys[i]; |
| Integer count = (Integer) srcDestToNumRelsMap.get(key); |
| Double avgWeight = (Double) srcDestToRelsAvgWeightMap.get(key); |
| if (count == null) { |
| count = new Integer(1); |
| avgWeight = new Double(weight); |
| } else { |
| int newCount = count.intValue() + 1; |
| double newAverage = (avgWeight.doubleValue() * count.doubleValue() + weight) / newCount; |
| avgWeight = new Double(newAverage); |
| count = new Integer(newCount); |
| } |
| srcDestToNumRelsMap.put(key, count); |
| srcDestToRelsAvgWeightMap.put(key, avgWeight); |
| } |
| } |
| } |
| |
| private void preCompute(InternalNode[] entitiesToLayout) { |
| // count number of relationships between all nodes and the average |
| // weight between them |
| srcDestToNumRels = new int[entitiesToLayout.length][entitiesToLayout.length]; |
| srcDestToRelsAvgWeight = new double[entitiesToLayout.length][entitiesToLayout.length]; |
| |
| for (int i = 0; i < entitiesToLayout.length - 1; i++) { |
| InternalNode layoutEntity1 = entitiesToLayout[i]; |
| for (int j = i + 1; j < entitiesToLayout.length; j++) { |
| InternalNode layoutEntity2 = entitiesToLayout[j]; |
| srcDestToNumRels[i][j] = numRelations(layoutEntity1, layoutEntity2); |
| srcDestToNumRels[i][j] += numRelations(layoutEntity2, layoutEntity1); |
| srcDestToRelsAvgWeight[i][j] = avgWeight(layoutEntity1, layoutEntity2); |
| } |
| } |
| |
| if (sprRandom) |
| placeRandomly(entitiesToLayout); // put vertices in random places |
| else |
| convertToUnitCoordinates(entitiesToLayout); |
| |
| iteration = 1; |
| largestMovement = Double.MAX_VALUE; |
| } |
| |
| // TODO: This is a complete Clone! (and not in a good way) |
| protected DisplayIndependentRectangle getLayoutBoundsTemp(InternalNode[] entitiesToLayout, boolean includeNodeSize) { |
| double rightSide = Double.MIN_VALUE; |
| double bottomSide = Double.MIN_VALUE; |
| double leftSide = Double.MAX_VALUE; |
| double topSide = Double.MAX_VALUE; |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| double x = tempLocationsX[i]; |
| double y = tempLocationsY[i]; |
| |
| leftSide = Math.min(x, leftSide); |
| topSide = Math.min(y, topSide); |
| rightSide = Math.max(x, rightSide); |
| bottomSide = Math.max(y, bottomSide); |
| |
| } |
| return new DisplayIndependentRectangle(leftSide, topSide, rightSide - leftSide, bottomSide - topSide); |
| } |
| |
| protected void convertNodePositionsBack(int i, InternalNode entityToConvert, double px, double py, double screenWidth, double screenHeight, DisplayIndependentRectangle layoutBounds) { |
| |
| // If the node selected is outside the screen, map it to the boarder |
| if (px > screenWidth) |
| px = screenWidth; |
| if (py > screenHeight) |
| py = screenHeight; |
| |
| if (px < 0) |
| px = 1; |
| if (py < 0) |
| py = 1; |
| |
| double x = (px / screenWidth) * layoutBounds.width + layoutBounds.x; |
| double y = (py / screenHeight) * layoutBounds.height + layoutBounds.y; |
| |
| tempLocationsX[i] = x; |
| tempLocationsY[i] = y; |
| //setTempLocation(entityToConvert, new DisplayIndependentPoint(x, y)); |
| |
| if (entityToConvert.getInternalX() < 0) { |
| // System.out.println("We have nodes less than 0 here!"); |
| } |
| |
| } |
| |
| private void checkPreferredLocation(InternalNode[] entitiesToLayout, DisplayIndependentRectangle realBounds) { |
| // use 10% for the border - 5% on each side |
| double borderWidth = Math.min(realBounds.width, realBounds.height) / 10.0; |
| DisplayIndependentRectangle screenBounds = new DisplayIndependentRectangle(realBounds.x + borderWidth / 2.0, realBounds.y + borderWidth / 2.0, realBounds.width - borderWidth, realBounds.height - borderWidth); |
| |
| DisplayIndependentRectangle layoutBounds = getLayoutBoundsTemp(entitiesToLayout, false); |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| InternalNode layoutEntity = entitiesToLayout[i]; |
| if (layoutEntity.hasPreferredLocation()) { |
| convertNodePositionsBack(i, layoutEntity, layoutEntity.getPreferredX(), layoutEntity.getPreferredY(), screenBounds.width, screenBounds.height, layoutBounds); |
| } |
| } |
| } |
| |
| /** |
| * Scales the current iteration counter based on how long the algorithm has |
| * been running for. You can set the MaxTime in maxTimeMS! |
| */ |
| private void setSprIterationsBasedOnTime() { |
| if (maxTimeMS <= 0) |
| return; |
| |
| long currentTime = date.getTime(); |
| double fractionComplete = (double) ((double) (currentTime - startTime) / ((double) maxTimeMS)); |
| int currentIteration = (int) (fractionComplete * sprIterations); |
| if (currentIteration > iteration) { |
| iteration = currentIteration; |
| } |
| |
| } |
| |
| protected boolean performAnotherNonContinuousIteration() { |
| setSprIterationsBasedOnTime(); |
| if (iteration <= sprIterations && largestMovement >= sprMove) |
| return true; |
| else |
| return false; |
| } |
| |
| protected int getCurrentLayoutStep() { |
| return iteration; |
| } |
| |
| protected int getTotalNumberOfLayoutSteps() { |
| return sprIterations; |
| } |
| |
| protected void computeOneIteration(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) { |
| if (bounds == null) |
| bounds = new DisplayIndependentRectangle(x, y, width, height); |
| checkPreferredLocation(entitiesToLayout, bounds); |
| computeForces(entitiesToLayout); |
| largestMovement = Double.MAX_VALUE; |
| computePositions(entitiesToLayout); |
| |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| InternalNode layoutEntity = entitiesToLayout[i]; |
| layoutEntity.setInternalLocation(tempLocationsX[i], tempLocationsY[i]); |
| } |
| |
| defaultFitWithinBounds(entitiesToLayout, bounds); |
| |
| iteration++; |
| } |
| |
| /** |
| * Puts vertices in random places, all between (0,0) and (1,1). |
| */ |
| public void placeRandomly(InternalNode[] entitiesToLayout) { |
| // If only one node in the data repository, put it in the middle |
| if (entitiesToLayout.length == 1) { |
| // If only one node in the data repository, put it in the middle |
| tempLocationsX[0] = 0.5; |
| tempLocationsY[0] = 0.5; |
| } else { |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| if (i == 0) { |
| tempLocationsX[i] = 0.0; |
| tempLocationsY[i] = 0.0; |
| } else if (i == 1) { |
| tempLocationsX[i] = 1.0; |
| tempLocationsY[i] = 1.0; |
| } else { |
| tempLocationsX[i] = Math.random(); |
| tempLocationsY[i] = Math.random(); |
| } |
| } |
| } |
| } |
| |
| // ///////////////////////////////////////////////////////////////// |
| // /// Protected Methods ///// |
| // ///////////////////////////////////////////////////////////////// |
| |
| /** |
| * Computes the force for each node in this SpringLayoutAlgorithm. The |
| * computed force will be stored in the data repository |
| */ |
| protected void computeForces(InternalNode[] entitiesToLayout) { |
| |
| // initialize all forces to zero |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| forcesX[i] = 0.0; |
| forcesY[i] = 0.0; |
| } |
| |
| // TODO: Again really really slow! |
| |
| for (int i = 0; i < entitiesToLayout.length - 1; i++) { |
| InternalNode sourceEntity = entitiesToLayout[i]; |
| |
| double srcLocationX = tempLocationsX[i]; |
| double srcLocationY = tempLocationsY[i]; |
| double fx = forcesX[i]; // force in x direction |
| double fy = forcesY[i]; // force in y direction |
| |
| for (int j = i + 1; j < entitiesToLayout.length; j++) { |
| InternalNode destinationEntity = entitiesToLayout[j]; |
| |
| if (!destinationEntity.equals(sourceEntity)) { |
| double destLocationX = tempLocationsX[j]; |
| double destLocationY = tempLocationsY[j]; |
| double dx = srcLocationX - destLocationX; |
| double dy = srcLocationY - destLocationY; |
| double distance = Math.sqrt(dx * dx + dy * dy); |
| double distance_sq = distance * distance; |
| // make sure distance and distance squared not too small |
| distance = Math.max(MIN_DISTANCE, distance); |
| |
| // If there are relationships between srcObj and destObj |
| // then decrease force on srcObj (a pull) in direction of destObj |
| // If no relation between srcObj and destObj then increase |
| // force on srcObj (a push) from direction of destObj. |
| int numRels = srcDestToNumRels[i][j]; |
| double avgWeight = srcDestToRelsAvgWeight[i][j]; |
| if (numRels > 0) { |
| // nodes are pulled towards each other |
| double f = sprStrain * Math.log(distance / sprLength) * numRels * avgWeight; |
| |
| fx = fx - (f * dx / distance); |
| fy = fy - (f * dy / distance); |
| |
| } else { |
| // nodes are repelled from each other |
| //double f = Math.min(100, sprGravitation / (distance*distance)); |
| double f = sprGravitation / (distance_sq); |
| fx = fx + (f * dx / distance); |
| fy = fy + (f * dy / distance); |
| } |
| |
| // According to Newton, "for every action, there is an equal |
| // and opposite reaction." |
| // so give the dest an opposite force |
| forcesX[j] = forcesX[j] - fx; |
| forcesY[j] = forcesY[j] - fy; |
| } |
| } |
| |
| /* |
| * //make sure forces aren't too big if (fx > 0 ) fx = Math.min(fx, |
| * 10*sprMove); else fx = Math.max(fx, -10*sprMove); if (fy > 0) fy = |
| * Math.min(fy, 10*sprMove); else fy = Math.max(fy, -10*sprMove); |
| */ |
| forcesX[i] = fx; |
| forcesY[i] = fy; |
| // Remove the src object from the list of destinations since |
| // we've already calculated the force from it on all other |
| // objects. |
| // dests.remove(srcObj); |
| |
| } |
| } |
| |
| /** |
| * Computes the position for each node in this SpringLayoutAlgorithm. |
| * The computed position will be stored in the data repository. position = |
| * position + sprMove * force |
| */ |
| protected void computePositions(InternalNode[] entitiesToLayout) { |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| if (!anchors[i] || entitiesToLayout[i].hasPreferredLocation()) { |
| double oldX = tempLocationsX[i]; |
| double oldY = tempLocationsY[i]; |
| double deltaX = sprMove * forcesX[i]; |
| double deltaY = sprMove * forcesY[i]; |
| |
| // constrain movement, so that nodes don't shoot way off to the edge |
| double maxMovement = 0.2d * sprMove; |
| if (deltaX >= 0) { |
| deltaX = Math.min(deltaX, maxMovement); |
| } else { |
| deltaX = Math.max(deltaX, -maxMovement); |
| } |
| if (deltaY >= 0) { |
| deltaY = Math.min(deltaY, maxMovement); |
| } else { |
| deltaY = Math.max(deltaY, -maxMovement); |
| } |
| |
| largestMovement = Math.max(largestMovement, Math.abs(deltaX)); |
| largestMovement = Math.max(largestMovement, Math.abs(deltaY)); |
| |
| double newX = oldX + deltaX; |
| double newY = oldY + deltaY; |
| tempLocationsX[i] = newX; |
| tempLocationsY[i] = newY; |
| } |
| |
| } |
| |
| } |
| |
| /** |
| * Converts the position for each node in this SpringLayoutAlgorithm |
| * to unit coordinates in double precision. The computed positions will be |
| * still stored in the data repository. |
| */ |
| protected void convertToUnitCoordinates(InternalNode[] entitiesToLayout) { |
| double minX = Double.MAX_VALUE; |
| double maxX = Double.MIN_VALUE; |
| double minY = Double.MAX_VALUE; |
| double maxY = Double.MIN_VALUE; |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| InternalNode layoutEntity = entitiesToLayout[i]; |
| minX = Math.min(minX, layoutEntity.getInternalX()); |
| minY = Math.min(minY, layoutEntity.getInternalY()); |
| maxX = Math.max(maxX, layoutEntity.getInternalX()); |
| maxY = Math.max(maxY, layoutEntity.getInternalY()); |
| } |
| |
| double spanX = maxX - minX; |
| double spanY = maxY - minY; |
| double maxSpan = Math.max(spanX, spanY); |
| |
| if (maxSpan > EPSILON) { |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| InternalNode layoutEntity = entitiesToLayout[i]; |
| double x = (layoutEntity.getInternalX() - minX) / spanX; |
| double y = (layoutEntity.getInternalY() - minY) / spanY; |
| tempLocationsX[i] = x; |
| tempLocationsY[i] = y; |
| } |
| } else { |
| placeRandomly(entitiesToLayout); |
| } |
| } |
| |
| /** |
| * Examines the number of specified relation between the <code>src</code> |
| * and the <code>dest</code> that exist in this |
| * SpringLayoutAlgorithm's relation repository. |
| * |
| * @param src |
| * The source part of the relaton to be examined. |
| * @param dest |
| * The destination part of the relation to be examined. |
| * @return The number of relations between src and dest. |
| */ |
| private int numRelations(Object src, Object dest) { |
| String key = src.toString() + dest.toString(); |
| Integer count = (Integer) srcDestToNumRelsMap.get(key); |
| int intCount = (count == null) ? 0 : count.intValue(); |
| return intCount; |
| } |
| |
| /** |
| * Returns the average weight between a src and dest object. |
| * |
| * @param src |
| * @param dest |
| * @return The average weight between the given src and dest nodes |
| */ |
| private double avgWeight(Object src, Object dest) { |
| String key = src.toString() + dest.toString(); |
| Double avgWeight = (Double) srcDestToRelsAvgWeightMap.get(key); |
| double doubleWeight = (avgWeight == null) ? 1 : avgWeight.doubleValue(); |
| return doubleWeight; |
| } |
| |
| protected boolean isValidConfiguration(boolean asynchronous, boolean continueous) { |
| if (asynchronous && continueous) |
| return true; |
| else if (asynchronous && !continueous) |
| return true; |
| else if (!asynchronous && continueous) |
| return false; |
| else if (!asynchronous && !continueous) |
| return true; |
| |
| return false; |
| } |
| |
| } |