edu.uci.ics.jung.algorithms.layout3d.SpringLayout Maven / Gradle / Ivy
/*
* Copyright (c) 2003, the JUNG Project and the Regents of the University of
* California All rights reserved.
*
* This software is open-source under the BSD license; see either "license.txt"
* or http://jung.sourceforge.net/license.txt for a description.
*/
package edu.uci.ics.jung.algorithms.layout3d;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Map;
import javax.media.j3d.BoundingSphere;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.Transformer;
import org.apache.commons.collections15.map.LazyMap;
import edu.uci.ics.jung.algorithms.util.IterativeContext;
import edu.uci.ics.jung.graph.Graph;
/**
* The SpringLayout package represents a visualization of a set of nodes. The
* SpringLayout, which is initialized with a Graph, assigns X/Y locations to
* each node. When called relax()
, the SpringLayout moves the
* visualization forward one step.
*
* @author Danyel Fisher
* @author Joshua O'Madadhain
*/
public class SpringLayout extends AbstractLayout implements IterativeContext {
protected double stretch = 0.70;
protected LengthFunction lengthFunction;
protected int repulsion_range = 100;
protected double force_multiplier = 1.0 / 3.0;
int totalSteps = 2000;
int step = 0;
Map> springEdgeData =
LazyMap.decorate(new HashMap>(),
new Transformer>() {
public SpringEdgeData transform(E e) {
return new SpringEdgeData(e);
}});
Map springVertexData =
LazyMap.decorate(new HashMap(),
new Factory() {
public SpringVertexData create() {
return new SpringVertexData();
}});
/**
* Constructor for a SpringLayout for a raw graph with associated
* dimension--the input knows how big the graph is. Defaults to the unit
* length function.
*/
public SpringLayout(Graph g) {
this(g, UNITLENGTHFUNCTION);
}
/**
* Constructor for a SpringLayout for a raw graph with associated component.
*
* @param g
* the input Graph
* @param f
* the length function
*/
public SpringLayout(Graph g, LengthFunction f) {
super(g);
this.lengthFunction = f;
}
/**
* @return the current value for the stretch parameter
* @see #setStretch(double)
*/
public double getStretch() {
return stretch;
}
/* (non-Javadoc)
* @see edu.uci.ics.jung.visualization.layout.AbstractLayout#setSize(java.awt.Dimension)
*/
@Override
public void setSize(BoundingSphere bs) {
Transformer rlt = new RandomLocationTransformer(bs);
setInitializer(rlt);
super.setSize(bs);
}
/**
* Sets the stretch parameter for this instance. This value
* specifies how much the degrees of an edge's incident vertices
* should influence how easily the endpoints of that edge
* can move (that is, that edge's tendency to change its length).
*
* The default value is 0.70. Positive values less than 1 cause
* high-degree vertices to move less than low-degree vertices, and
* values > 1 cause high-degree vertices to move more than
* low-degree vertices. Negative values will have unpredictable
* and inconsistent results.
* @param stretch
*/
public void setStretch(double stretch) {
this.stretch = stretch;
}
/**
* @return the current value for the node repulsion range
* @see #setRepulsionRange(int)
*/
public int getRepulsionRange() {
return repulsion_range;
}
/**
* Sets the node repulsion range (in drawing area units) for this instance.
* Outside this range, nodes do not repel each other. The default value
* is 100. Negative values are treated as their positive equivalents.
* @param range
*/
public void setRepulsionRange(int range) {
this.repulsion_range = range;
}
/**
* @return the current value for the edge length force multiplier
* @see #setForceMultiplier(double)
*/
public double getForceMultiplier() {
return force_multiplier;
}
/**
* Sets the force multiplier for this instance. This value is used to
* specify how strongly an edge "wants" to be its default length
* (higher values indicate a greater attraction for the default length),
* which affects how much its endpoints move at each timestep.
* The default value is 1/3. A value of 0 turns off any attempt by the
* layout to cause edges to conform to the default length. Negative
* values cause long edges to get longer and short edges to get shorter; use
* at your own risk.
*/
public void setForceMultiplier(double force) {
this.force_multiplier = force;
}
public void initialize() {
Graph graph = getGraph();
BoundingSphere d = getSize();
if(graph != null && d != null) {
try {
for(E e : graph.getEdges()) {
SpringEdgeData sed = getSpringData(e);
calcEdgeLength(sed, lengthFunction);
}
} catch(ConcurrentModificationException cme) {
initialize();
}
}
}
/* ------------------------- */
protected void calcEdgeLength(SpringEdgeData sed, LengthFunction f) {
sed.length = f.getLength(sed.e);
}
/* ------------------------- */
/**
* Relaxation step. Moves all nodes a smidge.
*/
public void step() {
step++;
try {
for(V v : getGraph().getVertices()) {
SpringVertexData svd = getSpringData(v);
// System.err.println("svd = "+svd);
if (svd == null) {
continue;
}
svd.dx /= 4;
svd.dy /= 4;
svd.dz /= 4;
svd.edgedx = svd.edgedy = svd.edgedz = 0;
svd.repulsiondx = svd.repulsiondy = svd.repulsiondz = 0;
}
} catch(ConcurrentModificationException cme) {
step();
}
relaxEdges();
calculateRepulsion();
moveNodes();
}
protected V getAVertex(E e) {
return getGraph().getIncidentVertices(e).iterator().next();
}
protected void relaxEdges() {
try {
for(E e : getGraph().getEdges()) {
V v1 = getAVertex(e);
V v2 = getGraph().getOpposite(v1, e);
Point3f p1 = transform(v1);
Point3f p2 = transform(v2);
if(p1 == null || p2 == null) continue;
double vx = p1.getX() - p2.getX();
double vy = p1.getY() - p2.getY();
double vz = p1.getZ() - p2.getZ();
double len = Math.sqrt(vx * vx + vy * vy + vz * vz);
SpringEdgeData sed = getSpringData(e);
if (sed == null) {
continue;
}
double desiredLen = sed.length;
// round from zero, if needed [zero would be Bad.].
len = (len == 0) ? .0001 : len;
double f = force_multiplier * (desiredLen - len) / len;
f = f * Math.pow(stretch, (getGraph().degree(v1) + getGraph().degree(v2) - 2));
// the actual movement distance 'dx' is the force multiplied by the
// distance to go.
double dx = f * vx;
double dy = f * vy;
double dz = f * vz;
SpringVertexData v1D, v2D;
v1D = getSpringData(v1);
v2D = getSpringData(v2);
sed.f = f;
v1D.edgedx += dx;
v1D.edgedy += dy;
v1D.edgedz += dz;
v2D.edgedx += -dx;
v2D.edgedy += -dy;
v2D.edgedz += -dz;
}
} catch(ConcurrentModificationException cme) {
relaxEdges();
}
}
protected void calculateRepulsion() {
try {
for (V v : getGraph().getVertices()) {
if (isLocked(v)) continue;
SpringVertexData svd = getSpringData(v);
if(svd == null) continue;
double dx = 0, dy = 0, dz = 0;
for (V v2 : getGraph().getVertices()) {
if (v == v2) continue;
Point3f p = transform(v);
Point3f p2 = transform(v2);
if(p == null || p2 == null) continue;
double vx = p.getX() - p2.getX();
double vy = p.getY() - p2.getY();
double vz = p.getZ() - p2.getZ();
double distance = vx * vx + vy * vy + vz * vz;
if (distance == 0) {
dx += Math.random();
dy += Math.random();
dz += Math.random();
} else if (distance < repulsion_range * repulsion_range) {
double factor = 1;
dx += factor * vx / Math.pow(distance, 2);
dy += factor * vy / Math.pow(distance, 2);
dz += factor * vz / Math.pow(distance, 2);
}
}
double dlen = dx * dx + dy * dy + dz * dz;
if (dlen > 0) {
dlen = Math.sqrt(dlen) / 2;
svd.repulsiondx += dx / dlen;
svd.repulsiondy += dy / dlen;
svd.repulsiondz += dz / dlen;
}
}
} catch(ConcurrentModificationException cme) {
calculateRepulsion();
}
}
protected void moveNodes() {
synchronized (getSize()) {
try {
for (V v : getGraph().getVertices()) {
if (isLocked(v)) continue;
SpringVertexData vd = getSpringData(v);
if(vd == null) continue;
Point3f xyd = transform(v);
vd.dx += vd.repulsiondx + vd.edgedx;
vd.dy += vd.repulsiondy + vd.edgedy;
vd.dz += vd.repulsiondz + vd.edgedz;
// keeps nodes from moving any faster than 5 per time unit
xyd.set(
(float)(xyd.getX()+Math.max(-5, Math.min(5, vd.dx))),
(float)(xyd.getY()+Math.max(-5, Math.min(5, vd.dy))),
(float)(xyd.getZ()+Math.max(-5, Math.min(5, vd.dz))));
BoundingSphere d = getSize();
float radius = (float) d.getRadius();
if (xyd.getX() < -radius) {
xyd.set(-radius, xyd.getY(), xyd.getZ());// setX(0);
} else if (xyd.getX() > radius) {
xyd.set(radius, xyd.getY(), xyd.getZ()); //setX(width);
}
if (xyd.getY() < -radius) {
xyd.set(xyd.getX(),-radius, xyd.getZ());//setY(0);
} else if (xyd.getY() > radius) {
xyd.set(xyd.getX(), radius, xyd.getZ()); //setY(height);
}
if (xyd.getZ() < -radius) {
xyd.set(xyd.getX(), xyd.getY(), -radius);//setY(0);
} else if (xyd.getZ() > radius) {
xyd.set(xyd.getX(), xyd.getY(), radius); //setY(height);
}
// System.err.println(v+" xyd = "+xyd);
}
} catch(ConcurrentModificationException cme) {
moveNodes();
}
}
}
public SpringVertexData getSpringData(V v) {
return springVertexData.get(v);
}
public SpringEdgeData getSpringData(E e) {
return springEdgeData.get(e);
}
public double getLength(E e) {
return springEdgeData.get(e).length;
}
// public Point3f transform(V v) {
// if(v.equals("A")) return new Point3f();
// if(v.equals("B")) return new Point3f(50,50,50);
// return new Point3f();
// }
/* ---------------Length Function------------------ */
/**
* If the edge is weighted, then override this method to show what the
* visualized length is.
*
* @author Danyel Fisher
*/
public static interface LengthFunction {
public double getLength(E e);
}
/**
* Returns all edges as the same length: the input value
* @author danyelf
*/
public static final class UnitLengthFunction implements LengthFunction {
int length;
public UnitLengthFunction(int length) {
this.length = length;
}
public double getLength(E e) {
return length;
}
}
public static final LengthFunction UNITLENGTHFUNCTION = new UnitLengthFunction(
30);
/* ---------------User Data------------------ */
protected static class SpringVertexData {
public double edgedx;
public double edgedy;
public double edgedz;
public double repulsiondx;
public double repulsiondy;
public double repulsiondz;
public SpringVertexData() {
}
/** movement speed, x */
public double dx;
/** movement speed, y */
public double dy;
public double dz;
public String toString() {
return "SVD["+dx+","+dy+","+dz+"]"+"["+repulsiondx+","+repulsiondy+","+repulsiondz+"]";
}
}
protected static class SpringEdgeData {
public double f;
public SpringEdgeData(E e) {
this.e = e;
}
E e;
double length;
}
/* ---------------Resize handler------------------ */
public class SpringDimensionChecker extends ComponentAdapter {
public void componentResized(ComponentEvent e) {
setSize(new BoundingSphere(new Point3d(), e.getComponent().getWidth()));
}
}
/**
* This one is an incremental visualization
*/
public boolean isIncremental() {
return true;
}
/**
* For now, we pretend it never finishes.
*/
public boolean done() {
// return true;
return step > totalSteps;
}
public void reset() {
step = 0;
// no counter, do nothing.
// locations.clear();
// initialize();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy