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

com.jgraph.layout.graph.JGraphSpringLayout Maven / Gradle / Ivy

The newest version!
/*
 * $Id: JGraphSpringLayout.java,v 1.1 2009/09/25 15:14:15 david Exp $
 * Copyright (c) 2001-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.graph;

import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Map;

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

/**
 * A basic Spring Embedded Layout Algorithm. Edges on the graph represent
 * spring. All the springs have a natural length, measured in the same units
 * as the screen co-ordination system, which they attempt to achieve constantly.
 * If the spring is shorter than its natural length it extends, pushing the
 * nodes are either end of the edge apart. If the spring is longer than its
 * natural length it contracts, pulling the nodes at either end of the edge
 * together. The force exerted by the spring is proportional to different
 * between its current length and its natural length. A force multiple is
 * also applied indicating the "strength" of the spring.
 * 
 * In addition, all nodes repel each other with a force inversely
 * proportional to the distance between each other. The repelling
 * force is mutliplied by a replusive force factor.
 * 
 * The whole affect is to cause nodes to space out fairly evenly but
 * for nodes linked by edges ( springs ) to cluster together
 * 
 * @deprecated use JGraphFastOrganicLayout instead
 */
public class JGraphSpringLayout implements JGraphLayout, JGraphLayout.Stoppable {

	/**
	 * Stores the temporary positions of each cell during the layout
	 */
	protected transient Map displacement = new Hashtable();

	/**
	 * The multiple by which the force replusive each pair of nodes scaled by
	 * Increase to make nodes force further apart
	 */
	protected double replusiveForce = 10000.0;
	
	/**
	 * The multiple of force applied to the attraction of springs
	 */
	protected double springForce = 0.2;
	
	/**
	 * The natural length of the spring (edge) whereby it imparts no force
	 * on either connected node
	 */
	protected double springLength = 50.0;
	
	/**
	 * current iteration number
	 */
	protected int iteration;
	
	/**
	 * total number of iterations to step through when running
	 */
	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 X co-ordinate positions for the vertices
	 */
	protected double cellLocationX[];
	
	/**
	 * An array of locally stored Y co-ordinate positions for the vertices
	 */
	protected double cellLocationY[];
	
	/**
	 * 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();

	/**
	 * Creates a new layout of 50 iterations
	 *
	 */
	public JGraphSpringLayout() {
		this(50);
	}

	/**
	 * Creates a new spring layout to be executed over the specified number
	 * of iterations
	 * @param 
	 * 			iterations the number of layout iterations to execute
	 */
	public JGraphSpringLayout(int iterations) {
		setMaxIterations(iterations);
	}

	/**
	 * @return Returns the progress.
	 */
	public JGraphLayoutProgress getProgress() {
		return progress;
	}
	
	/**
	 * Executes the spring layout of the specified facade data
	 * 
	 * @param graph
	 *            the description of 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(true);

		Collection vertices = graph.getVertices();
		if (vertices.isEmpty()) return;
		
		// Allocate memory for local cached arrays
		vertexArray = vertices.toArray();
		dispX = new double[vertexArray.length];
		dispY = new double[vertexArray.length];
		cellLocationX = new double[vertexArray.length];
		cellLocationY = new double[vertexArray.length];
		isMoveable = new boolean[vertexArray.length];
		neighbours = new int[vertexArray.length][];

		// If max number of iterations has not been set, guess it
		if (maxIterations == 0) {
			maxIterations = 20 * (int)Math.sqrt(vertexArray.length);
		}
		progress.reset(maxIterations);
		
		// Temporary map used to setup neighbour array of arrays
		Map vertexMap = new Hashtable(vertexArray.length);
		
		// 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));
		}

		for (int i=0; i < vertexArray.length; i++) {
			dispX[i] = 0;
			dispY[i] = 0;
			// Makes a local copy of all cell locations for performance
			Point2D pos = graph.getLocation(vertexArray[i]);
			cellLocationX[i] = pos.getX();
			cellLocationY[i] = pos.getY();
			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 springLength) {
						fr = springLength;
					} else if (fr < -springLength) {
						fr = -springLength;
					}

					double deltaNormX = deltaX / nodeDistance;
					double displacementX = deltaNormX * fr;
					double deltaNormY = deltaY / nodeDistance;
					double displacementY = deltaNormY * fr;

					if (isMoveable[i]) {
						dispX[i] += displacementX;
						dispY[i] += displacementY;
					}
					
					if (isMoveable[j]) {
						dispX[j] -= displacementX;
						dispY[j] -= displacementY;
					}
				}
			}
		}
	}

	/**
	 * Calculates an attractive force between the cells connected by the
	 * specified edge
	 */
	protected void attract() {
		// 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 process self-loops
				if (i != j) {
					
					double xDelta = cellLocationX[i] - cellLocationX[j];
					double yDelta = cellLocationY[i] - cellLocationY[j];
					
					// The distance between the nodes
					double edgeLength = Math.sqrt((xDelta * xDelta)
							+ (yDelta * yDelta));
					
					// Calculate the length by which the spring is extended
					double extensionLength = edgeLength - springLength;
					
					// Avoid divide by zero
					if (edgeLength < 1.0)
						edgeLength = 1.0;
					// calculate the attractive forces
					double fa = extensionLength * springForce;
					
					double deltaNormX = xDelta / edgeLength;
					double deltaNormY = yDelta / edgeLength;
					double displacementX = deltaNormX * fa;
					double displacementY = deltaNormY * fa;
					
					if (isMoveable[i]) {
						dispX[i] -= displacementX;
						dispY[i] -= displacementY;
					}
					if (isMoveable[j]) {
						dispX[j] += displacementX;
						dispY[j] += displacementY;
					}
				}
			}
		}
	}

	/**
	 * repositions the specified cells using the positioning
	 * data obtained through repulse and attract phases
	 * @param graph
	 * 				the description of the graph to be laid out
	 */
	protected void reposition(JGraphFacade graph) {
		for (int index=0; index < vertexArray.length; index++) {
			if (isMoveable[index]) {
				// Update the cached cell locations
				cellLocationX[index] += dispX[index];
				cellLocationY[index] += dispY[index];
				
				// reset displacements
				dispX[index] = 0;
				dispY[index] = 0;

				// If this is the last iteration, write the node locations
				// back to the facade
				if (iteration == maxIterations-1) {
					graph.setLocation(vertexArray[index],
							cellLocationX[index],
							cellLocationY[index]);
				}
			}
		}
	}

	/**
	 * @param iterations the value to set maxIterations to
	 */
	public void setMaxIterations(int iterations) {
		if (iterations < 0) {
			throw new IllegalArgumentException(
					"iterations must be a positive integer");
		}
		this.maxIterations = iterations;
	}

	/**
	 * @return Returns the total number of iterations.
	 */
	public int getMaxIterations() {
		return maxIterations;
	}
	/**
	 * @return Returns the springLength.
	 */
	public double getSpringLength() {
		return springLength;
	}
	/**
	 * @param springLength The springLength to set.
	 */
	public void setSpringLength(double springLength) {
		// Must be finite and positive
		if (springLength < 0.001) {
			throw new IllegalArgumentException(
			"spring length must be postive and non-zero");
		}
		this.springLength = springLength;
	}
	/**
	 * @return Returns the springForce.
	 */
	public double getSpringForce() {
		return springForce;
	}
	/**
	 * @param springForce The springForce to set.
	 */
	public void setSpringForce(double springForce) {
		this.springForce = springForce;
	}
	/**
	 * @return Returns the replusiveForce.
	 */
	public double getReplusiveForce() {
		return replusiveForce;
	}
	/**
	 * @param replusiveForce The replusiveForce to set.
	 */
	public void setReplusiveForce(double replusiveForce) {
		this.replusiveForce = replusiveForce;
	}
	
	/**
	 * Returns Spring, the name of this algorithm.
	 */
	public String toString() {
		return "Spring";
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy