com.jgraph.layout.organic.JGraphFastOrganicLayout Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ingeniasjgraphmod Show documentation
Show all versions of ingeniasjgraphmod Show documentation
A modified version of some JGraph files
The newest version!
/*
* $Id: JGraphFastOrganicLayout.java,v 1.1 2009/09/25 15:14:15 david Exp $
* Copyright (c) 2005, Gaudenz Alder
* Copyright (c) 2005, 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.Rectangle2D;
import java.util.Hashtable;
import java.util.Map;
import com.jgraph.layout.JGraphFacade;
import com.jgraph.layout.JGraphLayout;
import com.jgraph.layout.JGraphLayoutProgress;
/**
* This layout is an implementation of "Graph Drawing by Force-Directed
* Placement" by Fruchterman and Reingold (1991). FR layouts are a variation on
* the basic Eades et al Spring Embedded layout. The paper states that
* "distributing vertices evenly, making edge lengths uniform, and reflecting
* symmetry" are its target aims. The variation from the basic embedded is that
* the attractive force is proportional to the square of the spring length, the
* natural spring length being zero and the repulsive force is linear with the
* distance between the nodes. FR layouts are better suited to well connected
* graphs.
*
* The computational effort per iteration is quadratic, O(|N|^2+|E|). This is
* due to the way all nodes calculate repulsion from all others. The three user
* variables are forceConstant
,initialTemp
and
* maxIteration
.forceConstant
is the constant k
* in the paper and affects the radius around each node around which other nodes
* would be in equilibrium. initialTemp
sets the start
* temperature of the layout, lower values limit the displacement of each node
* on each iteration. maxIteration
sets the total number of
* iterations of the layout that occur.
*/
public class JGraphFastOrganicLayout implements JGraphLayout,
JGraphLayout.Stoppable {
/**
* The force constant by which the attractive forces are divided and the
* replusive forces are multiple by the square of. The value equates to the
* average radius there is of free space around each node.
*/
protected double forceConstant = 50;
/**
* Cache of forceConstant
^2 for performance
*/
protected double forceConstantSquared = 0;
/**
* Temperature to limit displacement at later stages of layout
*/
protected double temperature = 0;
/**
* Start value of temperature
*/
protected double initialTemp = 200;
/**
* Current iteration count
*/
protected int iteration = 0;
/**
* Total number of iterations to run the layout though
*/
protected int maxIterations = 0;
/**
* An array of all vertices to be laid out
*/
protected Object vertexArray[];
/**
* An array of locally stored X co-ordinate displacements for the vertices
*/
protected double dispX[];
/**
* An array of locally stored Y co-ordinate displacements for the vertices
*/
protected double dispY[];
/**
* An array of locally stored co-ordinate positions for the vertices
*/
protected double cellLocation[][];
/**
* The approximate radius of each cell, nodes only
*/
protected double radius[];
/**
* The approximate radius squared of each cell, nodes only
*/
protected double radiusSquared[];
/**
* Local copy of isMoveable
*/
protected boolean isMoveable[];
/**
* Local copy of cell neighbours
*/
protected int[] neighbours[];
/**
* An object to monitor and control progress.
*/
protected JGraphLayoutProgress progress = new JGraphLayoutProgress();
/**
* prevents from dividing with zero
*/
protected double minDistanceLimit = 2;
/**
* cached version of minDistanceLimit
squared
*/
protected double minDistanceLimitSquared = 4;
/**
* @return Returns the progress.
*/
public JGraphLayoutProgress getProgress() {
return progress;
}
/**
* Executes the Fruchterman-Reingold layout using the graph description from
* the specified facade
*
* @param graph
* the facade describing the graph to be acted upon
*/
public void run(JGraphFacade 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 = graph.isDirected();
graph.setDirected(false);
// Allocate memory for local cached arrays
vertexArray = graph.getVertices().toArray();
dispX = new double[vertexArray.length];
dispY = new double[vertexArray.length];
cellLocation = graph.getLocations(vertexArray);
isMoveable = new boolean[vertexArray.length];
neighbours = new int[vertexArray.length][];
radius = new double[vertexArray.length];
radiusSquared = new double[vertexArray.length];
// Temporary map used to setup neighbour array of arrays
Map vertexMap = new Hashtable(vertexArray.length);
if (forceConstant < 0.001)
forceConstant = 0.001;
forceConstantSquared = forceConstant * forceConstant;
// Create a map of vertices first. This is required for the array of
// arrays called neighbours which holds, for each vertex, a list of
// ints which represents the neighbours cells to that vertex as
// the indices into vertexArray
for (int i = 0; i < vertexArray.length; i++) {
// Set up the mapping from array indices to cells
vertexMap.put(vertexArray[i], new Integer(i));
Rectangle2D bounds = graph.getBounds(vertexArray[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();
cellLocation[i][0] += width / 2.0;
cellLocation[i][1] += height / 2.0;
radius[i] = Math.min(width, height);
radiusSquared[i] = radius[i] * radius[i];
}
for (int i = 0; i < vertexArray.length; i++) {
dispX[i] = 0;
dispY[i] = 0;
isMoveable[i] = graph.isMoveable(vertexArray[i]);
// Get lists of neighbours to all vertices, translate the cells
// obtained in indices into vertexArray and store as an array
// against the orginial cell index
Object cellNeighbours[] = graph.getNeighbours(vertexArray[i], null,
false).toArray();
neighbours[i] = new int[cellNeighbours.length];
for (int j = 0; j < cellNeighbours.length; j++) {
Integer indexOtherCell = (Integer) vertexMap
.get(cellNeighbours[j]);
// Check the connected cell in part of the vertex list to be
// acted on by this layout
if (indexOtherCell != null) {
int k = indexOtherCell.intValue();
neighbours[i][j] = k;
} else {
// The index of the other cell doesn't correspond to any
// cell listed to be acted upon in this layout. Set the
// index to the value of this vertex (a dummy self-loop)
// so the attraction force of the edge is not calculated
neighbours[i][j] = i;
}
}
}
temperature = initialTemp;
// If max number of iterations has not been set, guess it
if (maxIterations == 0) {
maxIterations = 20 * (int) Math.sqrt(vertexArray.length);
}
progress.reset(maxIterations);
// Main iteration loop
for (iteration = 0; iteration < maxIterations && !progress.isStopped(); iteration++) {
progress.setProgress(iteration);
// Calculate repulsive forces on all vertices
calcRepulsion();
// Calculate attractive forces through edges
calcAttraction();
calcPositions();
reduceTemperature();
}
// Moved cell location back to top-left from center locations used in
// algorithm
for (int i = 0; i < vertexArray.length; i++) {
Rectangle2D bounds = graph.getBounds(vertexArray[i]);
cellLocation[i][0] -= bounds.getWidth() / 2.0;
cellLocation[i][1] -= bounds.getHeight() / 2.0;
}
// Write result back
graph.setLocations(vertexArray, cellLocation);
// Reset the directed state of the facade
graph.setDirected(directed);
}
/**
* Takes the displacements calculated for each cell and applies them to the
* local cache of cell positions. Limits the displacement to the current
* temperature.
*/
public void calcPositions() {
for (int index = 0; index < vertexArray.length; index++) {
if (isMoveable[index]) {
// Get the distance of displacement for this node for this
// iteration
double deltaLength = Math.sqrt(dispX[index] * dispX[index]
+ dispY[index] * dispY[index]);
if (deltaLength < 0.001)
deltaLength = 0.001;
// Scale down by the current temperature if less than the
// displacement distance
double newXDisp = dispX[index] / deltaLength
* Math.min(deltaLength, temperature);
double newYDisp = dispY[index] / deltaLength
* Math.min(deltaLength, temperature);
// reset displacements
dispX[index] = 0;
dispY[index] = 0;
// Update the cached cell locations
cellLocation[index][0] += newXDisp;
cellLocation[index][1] += newYDisp;
}
}
}
/**
* Calculates the attractive forces between all laid out nodes linked by
* edges
*/
public void calcAttraction() {
// Check the neighbours of each vertex and calculate the attractive
// force of the edge connecting them
for (int i = 0; i < vertexArray.length; i++) {
for (int k = 0; k < neighbours[i].length; k++) {
if (progress.isStopped())
return;
// Get the index of the othe cell in the vertex array
int j = neighbours[i][k];
// Do not proceed self-loops
if (i != j) {
double xDelta = cellLocation[i][0] - cellLocation[j][0];
double yDelta = cellLocation[i][1] - cellLocation[j][1];
// The distance between the nodes
double deltaLengthSquared = xDelta * xDelta + yDelta
* yDelta - radiusSquared[i] - radiusSquared[j];
if (deltaLengthSquared < minDistanceLimitSquared) {
deltaLengthSquared = minDistanceLimitSquared;
}
double deltaLength = Math.sqrt(deltaLengthSquared);
double force = (deltaLengthSquared) / forceConstant;
double displacementX = (xDelta / deltaLength) * force;
double displacementY = (yDelta / deltaLength) * force;
if (isMoveable[i]) {
dispX[i] -= displacementX;
dispY[i] -= displacementY;
}
if (isMoveable[j]) {
dispX[j] += displacementX;
dispY[j] += displacementY;
}
}
}
}
}
/**
* Calculates the repulsive forces between all laid out nodes
*/
public void calcRepulsion() {
for (int i = 0; i < vertexArray.length; i++) {
for (int j = i; j < vertexArray.length; j++) {
if (progress.isStopped())
return;
if (j != i) {
double xDelta = cellLocation[i][0] - cellLocation[j][0];
double yDelta = cellLocation[i][1] - cellLocation[j][1];
if (xDelta == 0)
{
xDelta = 0.01 + Math.random();
}
if (yDelta == 0)
{
yDelta = 0.01 + Math.random();
}
// Distance between nodes
double deltaLength = Math.sqrt((xDelta * xDelta)
+ (yDelta * yDelta));
double deltaLengthWithRadius = deltaLength - radius[i]
- radius[j];
if (deltaLengthWithRadius < minDistanceLimit)
deltaLengthWithRadius = minDistanceLimit;
double force = forceConstantSquared / deltaLengthWithRadius;
double displacementX = (xDelta / deltaLength) * force;
double displacementY = (yDelta / deltaLength) * force;
if (isMoveable[i]) {
dispX[i] += displacementX;
dispY[i] += displacementY;
}
if (isMoveable[j]) {
dispX[j] -= displacementX;
dispY[j] -= displacementY;
}
}
}
}
}
/**
* Reduces the temperature of the layout from an initial setting in a linear
* fashion to zero.
*/
private void reduceTemperature() {
temperature = initialTemp * (1.0 - iteration / (double) maxIterations);
}
/**
* @return Returns the forceConstant.
*/
public double getForceConstant() {
return forceConstant;
}
/**
* @param forceConstant
* The forceConstant to set.
*/
public void setForceConstant(double forceConstant) {
this.forceConstant = forceConstant;
}
/**
* @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 initialTemp.
*/
public double getInitialTemp() {
return initialTemp;
}
/**
* @param initialTemp
* The initialTemp to set.
*/
public void setInitialTemp(double initialTemp) {
this.initialTemp = initialTemp;
}
/**
* Returns Fast Organic
, the name of this algorithm.
*/
public String toString() {
return "Fast Organic";
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy