com.jgraph.layout.organic.JGraphSelfOrganizingOrganicLayout 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: JGraphSelfOrganizingOrganicLayout.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.organic;
import java.awt.geom.Rectangle2D;
import java.util.Hashtable;
import java.util.Map;
import java.util.Stack;
import com.jgraph.layout.JGraphFacade;
import com.jgraph.layout.JGraphLayout;
/**
* This layout is an implementation of inverted self-organising maps as
* described by Bernd Meyer in his 1998 paper "Self-Organizing Graphs - A Neural
* Network Perspective of Graph Layout". Self-organizing maps have some
* similarities with force-directed layouts, linked nodes tends to cluster.
* However, a difference with the maps is that there is a uniform space filling
* distrubtion of nodes. This makes the bounds within which the layout takes
* place important to calculate correctly at the start. The implementation
* assumes an average density by default. ISOM layouts are better suited to well
* connected graphs.
*
* The computational effort per iteration is linear, O(|N|). This comes from the
* effort of finding the closest node to the random point. When JGraph
* implements the spatial index structure this will improve to O(log|N|). Only a
* selection of nodes are moved per iteration and so a greater number of
* iterations are required for larger graphs. Generally, the number of
* iterations required is proportional to the number of vertices and so the
* computational effort including the number of iterations will always be
* O(|N|). The paper describes 500 iterations as being enough for 25 nodes, thus
* maxIterationsMultiple
, which defines the vertices to number
* of iterations factor, defaults to 20.
*
* This implementation attempt to calculate sensible values for certain
* configuration parameters, based on the input graph. The number of iterations,
* the start radius used, the bounds of the end graph and the narrowing interval
* are calculated for the user, if the user does not set their own values. If a
* layout is used repeatedly, the values calculated may become less suitable as
* the graph changes. To make the layout re-calculate it's own suggested values,
* set the appropriate value to zero. The parameters that can be reset like this
* are: maxIterationsMultiple
,startRadius
and
* narrowingInterval
.
*/
public class JGraphSelfOrganizingOrganicLayout implements JGraphLayout {
/**
* The bounds of the graph prior to the layout
*/
protected Rectangle2D bounds = null;
/**
* The layout sets this variable to the number of vertices multipled by
* maxIterationsMultiple
since the number of iterations
* required in linear with the number of nodes
*/
protected int totalIterations = 0;
/**
* The multiple of the number of vertices to find the total number of
* iterations of this layout applied. Defaults to 20. If the user changes it
* to any positive integer, that value is used instead.
*/
protected int maxIterationsMultiple = 20;
/**
* The current iteration of the layout
*/
protected int iteration = 1;
/**
* The current radius of the layout. The radius actually means the number of
* times neighbours are found from the winning node. For example, if the
* radius is 2, all of the neighbours of winning node are processed for
* moving, as well as all the meighbours of those first neighbours. No node
* is processed twice. The idea of the later stages of the layout is for
* only linked cells to be drawn into clusters, as the radius reduces down
* to minRadius
as the layout progresses.
*/
protected int radius = 0;
/**
* The radius value at on the first iteration. The radius should reflect
* both the number of vertices and the ratio of vertices to edges. Larger
* numbers of vertices requires a larger radius ( the relationship is
* roughly logarithmic ) and higher edge-to-vertex ratio should require a
* lower radius. The value defaults to 3, unless the user sets it to any
* positive integer.
*/
protected int startRadius = 0;
/**
* The lowest radius value allowed. A value of 1 is generally recommended.
* Only use a value of 0 if the adaption is under 0.15 at this point in
* the layout process, otherwise the symmetry of the layout may be destroyed.
*/
protected int minRadius = 1;
/**
* The factor by which the suggest area of the graph bound is multipled by.
* The suggested value is determined from the number of nodes. This value is
* only used if set to a value other than zero
*/
protected double densityFactor = 0.0;
/**
* The number of iterations after which the radius is decremented. This
* value should reflect the total number of iterations, the start radius and
* minimum radius so that some part of the layout is spent at the minimum
* radius.
*/
protected int narrowingInterval = 0;
/**
* The current adaption value
*/
protected double adaption = 0;
/**
* The start adaption value
*/
protected double maxAdaption = 0.8;
/**
* The minimum adaption value
*/
protected double minAdaption = 0.1;
/**
* The rate at which the rate of the change of the graph decreases
*/
protected double coolingFactor = 1.0;
/**
* A stack of nodes to be visited in the adjustment phase
*/
protected Stack stack = null;
/**
* Local copy of cell neighbours
*/
protected int[] neighbours[];
/**
* An array of all vertices to be laid out
*/
protected Object vertexArray[];
/**
* An array of which vertices have been visited during the current
* iteration. Avoid the same vertex being processed twice.
*/
protected boolean vertexVisited[];
/**
* An array of the number of edges any particular node is from the winning
* node. If a node is not in stack
then its corresponding
* value in this array will not be valid.
*/
protected int vertexDistance[];
/**
* An array of locally stored X co-ordinate positions for the vertices
*/
protected double cellLocation[][];
/**
* The X-coordinate of the random point (termed the random vector in the
* paper)
*/
protected double randomX;
/**
* The Y-coordinate of the random point (termed the random vector in the
* paper)
*/
protected double randomY;
/**
* Runs the ISOM layout using the graph information specified in the facade.
*
* @param graph
* the facade describing the input graph
*/
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);
vertexArray = graph.getVertices().toArray();
vertexVisited = new boolean[vertexArray.length];
vertexDistance = new int[vertexArray.length];
cellLocation = graph.getLocations(vertexArray);
neighbours = new int[vertexArray.length][];
Map vertexMap = new Hashtable(vertexArray.length);
bounds = graph.getGraphBounds();
if (densityFactor != 0.0 && bounds != null) {
// Use the density factor to determine the rough end bounds of the
// laid out graph
double currentArea = bounds.getWidth() * bounds.getHeight();
double preferedArea = densityFactor * vertexArray.length;
double dimensionRatio = Math.sqrt(preferedArea/currentArea);
bounds.setFrame(bounds.getX(), bounds.getY(),
bounds.getWidth() * dimensionRatio,
bounds.getHeight() * dimensionRatio);
}
// 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++) {
// Set up the mapping from array indices to cells
vertexMap.put(vertexArray[i], new Integer(i));
// Makes a local copy of all cell locations for performance
Object cellNeighbours[] = graph.getNeighbours(vertexArray[i], null,
true).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 edge is ignored
neighbours[i][j] = i;
}
}
}
adaption = maxAdaption;
// Set the current radius to the start radius value, setting start to
// default value if not set by user
if (startRadius == 0)
startRadius = 3;
radius = startRadius;
// The total number of iterations should be proportional to the number
// of vertices for a good layout
totalIterations = vertexArray.length * maxIterationsMultiple;
if (totalIterations < 100)
totalIterations = 100;
// determine the narrowing iterval so each radius value gets an equal
// number of iterations
if (narrowingInterval == 0) {
int numRadiusSteps = startRadius - minRadius + 1;
if (numRadiusSteps < 1)
numRadiusSteps = 1;
narrowingInterval = totalIterations / numRadiusSteps;
}
// Main layout loop
for (iteration = 1; iteration <= totalIterations; iteration++) {
updateToRandomNode();
updateRadius();
}
// Set locations at final values
graph.setLocations(vertexArray, cellLocation);
// Reset the directed state of the facade
graph.setDirected(directed);
}
/**
* Picks a random point and detemines to the closest nodes to that point
*/
protected void updateToRandomNode() {
double temp = Math.exp(-coolingFactor
* (1.0 * iteration / totalIterations));
adaption = Math.max(minAdaption, temp * maxAdaption);
// Get a random point in the current graph bounds
randomX = Math.random() * bounds.getWidth();
randomY = Math.random() * bounds.getHeight();
// find the vertex closest to this point
int indexClosestVertex = -1;
double smallestDelta = Double.MAX_VALUE;
for (int i = 0; i < vertexArray.length; i++) {
// This is the performance dominating loop and is proportional
// to the number of vertices
// Set up arrays
vertexDistance[i] = 0;
vertexVisited[i] = false;
double distanceSquared = (randomX - cellLocation[i][0]) *
(randomX - cellLocation[i][0]) + (randomY - cellLocation[i][1])
* (randomY - cellLocation[i][1]);
if (distanceSquared < smallestDelta) {
// This is the closest vertex to the random point so far
indexClosestVertex = i;
smallestDelta = distanceSquared;
}
}
// reposition the choosen vertex
if (indexClosestVertex > -1) {
moveVertex(indexClosestVertex);
}
}
/**
* Check whether or not a number of iterations equal to the narrowing
* interval have elapse. If so, decrement the radius, unless already equal
* to the minimum radius.
*/
private void updateRadius() {
if ((radius > minRadius) && (iteration % narrowingInterval == 0)) {
radius--;
}
}
/**
* Moved the specified vertex by a factor proportional to
* adaption
. Also move any neighbours who have a path to the
* specified node of less than radius
edges by the factor
* suitably reduce according to the number of edges between it and the
* winning node
*
* @param vertexIndex
* the winning node
*/
private void moveVertex(int vertexIndex) {
if (stack == null) {
stack = new Stack();
}
vertexVisited[vertexIndex] = true;
stack.push(new Integer(vertexIndex));
int current;
while (!stack.isEmpty()) {
current = ((Integer) (stack.pop())).intValue();
double dx = randomX - cellLocation[current][0];
double dy = randomY - cellLocation[current][1];
double factor = adaption / ( 1 << vertexDistance[current]);
cellLocation[current][0] += factor * dx;
cellLocation[current][1] += factor * dy;
if (vertexDistance[current] < radius) {
for (int i = 0; i < neighbours[vertexIndex].length; i++) {
// Get the index of the othe cell in the vertex array
int j = neighbours[vertexIndex][i];
// Do not proceed self-loops
if (vertexIndex != j) {
if (!vertexVisited[j]) {
vertexVisited[j] = true;
vertexDistance[j] = vertexDistance[current] + 1;
stack.push(new Integer(j));
}
}
}
}
}
}
/**
* @return Returns the coolingFactor.
*/
public double getCoolingFactor() {
return coolingFactor;
}
/**
* @param coolingFactor
* The coolingFactor to set.
*/
public void setCoolingFactor(double coolingFactor) {
this.coolingFactor = coolingFactor;
}
/**
* @return Returns the maxIterationsMultiple.
*/
public int getMaxIterationsMultiple() {
return maxIterationsMultiple;
}
/**
* @param maxIterationsMultiple
* The maxIterationsMultiple to set.
*/
public void setMaxIterationsMultiple(int maxIterationsMultiple) {
this.maxIterationsMultiple = maxIterationsMultiple;
}
/**
* @return Returns the minAdaption.
*/
public double getMinAdaption() {
return minAdaption;
}
/**
* @param minAdaption
* The minAdaption to set.
*/
public void setMinAdaption(double minAdaption) {
this.minAdaption = minAdaption;
}
/**
* @return Returns the startRadius.
*/
public int getStartRadius() {
return startRadius;
}
/**
* @param startRadius
* The startRadius to set.
*/
public void setStartRadius(int startRadius) {
this.startRadius = startRadius;
}
/**
* @return Returns the maxAdaption.
*/
public double getMaxAdaption() {
return maxAdaption;
}
/**
* @param maxAdaption
* The maxAdaption to set.
*/
public void setMaxAdaption(double maxAdaption) {
this.maxAdaption = maxAdaption;
}
/**
* @return Returns the minRadius.
*/
public int getMinRadius() {
return minRadius;
}
/**
* @param minRadius
* The minRadius to set.
*/
public void setMinRadius(int minRadius) {
this.minRadius = minRadius;
}
/**
* @return Returns the densityFactor.
*/
public double getDensityFactor() {
return densityFactor;
}
/**
* @param densityFactor
* The densityFactor to set.
*/
public void setDensityFactor(double densityFactor) {
this.densityFactor = densityFactor;
}
/**
* Returns Self Organizing
, the name of this algorithm.
*/
public String toString() {
return "Self Organizing";
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy