
edu.uci.ics.jung.algorithms.layout.FRLayout Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jung-algorithms Show documentation
Show all versions of jung-algorithms Show documentation
Algorithms for the JUNG project
/*
* Copyright (c) 2003, The JUNG Authors
* All rights reserved.
*
* This software is open-source under the BSD license; see either "license.txt"
* or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
*/
package edu.uci.ics.jung.algorithms.layout;
import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.util.ConcurrentModificationException;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
import edu.uci.ics.jung.algorithms.util.IterativeContext;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Pair;
/**
* Implements the Fruchterman-Reingold force-directed algorithm for node layout.
*
* Behavior is determined by the following settable parameters:
*
* - attraction multiplier: how much edges try to keep their vertices together
*
- repulsion multiplier: how much vertices try to push each other apart
*
- maximum iterations: how many iterations this algorithm will use before stopping
*
* Each of the first two defaults to 0.75; the maximum number of iterations defaults to 700.
*
* @see "Fruchterman and Reingold, 'Graph Drawing by Force-directed Placement'"
* @see "http://i11www.ilkd.uni-karlsruhe.de/teaching/SS_04/visualisierung/papers/fruchterman91graph.pdf"
* @author Scott White, Yan-Biao Boey, Danyel Fisher
*/
public class FRLayout extends AbstractLayout implements IterativeContext {
private double forceConstant;
private double temperature;
private int currentIteration;
private int mMaxIterations = 700;
protected LoadingCache frVertexData =
CacheBuilder.newBuilder().build(new CacheLoader() {
public FRVertexData load(V vertex) {
return new FRVertexData();
}
});
private double attraction_multiplier = 0.75;
private double attraction_constant;
private double repulsion_multiplier = 0.75;
private double repulsion_constant;
private double max_dimension;
public FRLayout(Graph g) {
super(g);
}
public FRLayout(Graph g, Dimension d) {
super(g, new RandomLocationTransformer(d), d);
initialize();
max_dimension = Math.max(d.height, d.width);
}
@Override
public void setSize(Dimension size) {
if(initialized == false) {
setInitializer(new RandomLocationTransformer(size));
}
super.setSize(size);
max_dimension = Math.max(size.height, size.width);
}
public void setAttractionMultiplier(double attraction) {
this.attraction_multiplier = attraction;
}
public void setRepulsionMultiplier(double repulsion) {
this.repulsion_multiplier = repulsion;
}
public void reset() {
doInit();
}
public void initialize() {
doInit();
}
private void doInit() {
Graph graph = getGraph();
Dimension d = getSize();
if(graph != null && d != null) {
currentIteration = 0;
temperature = d.getWidth() / 10;
forceConstant =
Math
.sqrt(d.getHeight()
* d.getWidth()
/ graph.getVertexCount());
attraction_constant = attraction_multiplier * forceConstant;
repulsion_constant = repulsion_multiplier * forceConstant;
}
}
private double EPSILON = 0.000001D;
/**
* Moves the iteration forward one notch, calculation attraction and
* repulsion between vertices and edges and cooling the temperature.
*/
public synchronized void step() {
currentIteration++;
/**
* Calculate repulsion
*/
while(true) {
try {
for(V v1 : getGraph().getVertices()) {
calcRepulsion(v1);
}
break;
} catch(ConcurrentModificationException cme) {}
}
/**
* Calculate attraction
*/
while(true) {
try {
for(E e : getGraph().getEdges()) {
calcAttraction(e);
}
break;
} catch(ConcurrentModificationException cme) {}
}
while(true) {
try {
for(V v : getGraph().getVertices()) {
if (isLocked(v)) continue;
calcPositions(v);
}
break;
} catch(ConcurrentModificationException cme) {}
}
cool();
}
protected synchronized void calcPositions(V v) {
FRVertexData fvd = getFRData(v);
if(fvd == null) return;
Point2D xyd = apply(v);
double deltaLength = Math.max(EPSILON, fvd.norm());
double newXDisp = fvd.getX() / deltaLength
* Math.min(deltaLength, temperature);
if (Double.isNaN(newXDisp)) {
throw new IllegalArgumentException(
"Unexpected mathematical result in FRLayout:calcPositions [xdisp]"); }
double newYDisp = fvd.getY() / deltaLength
* Math.min(deltaLength, temperature);
xyd.setLocation(xyd.getX()+newXDisp, xyd.getY()+newYDisp);
double borderWidth = getSize().getWidth() / 50.0;
double newXPos = xyd.getX();
if (newXPos < borderWidth) {
newXPos = borderWidth + Math.random() * borderWidth * 2.0;
} else if (newXPos > (getSize().getWidth() - borderWidth)) {
newXPos = getSize().getWidth() - borderWidth - Math.random()
* borderWidth * 2.0;
}
double newYPos = xyd.getY();
if (newYPos < borderWidth) {
newYPos = borderWidth + Math.random() * borderWidth * 2.0;
} else if (newYPos > (getSize().getHeight() - borderWidth)) {
newYPos = getSize().getHeight() - borderWidth
- Math.random() * borderWidth * 2.0;
}
xyd.setLocation(newXPos, newYPos);
}
protected void calcAttraction(E e) {
Pair endpoints = getGraph().getEndpoints(e);
V v1 = endpoints.getFirst();
V v2 = endpoints.getSecond();
boolean v1_locked = isLocked(v1);
boolean v2_locked = isLocked(v2);
if(v1_locked && v2_locked) {
// both locked, do nothing
return;
}
Point2D p1 = apply(v1);
Point2D p2 = apply(v2);
if(p1 == null || p2 == null) return;
double xDelta = p1.getX() - p2.getX();
double yDelta = p1.getY() - p2.getY();
double deltaLength = Math.max(EPSILON, Math.sqrt((xDelta * xDelta)
+ (yDelta * yDelta)));
double force = (deltaLength * deltaLength) / attraction_constant;
if (Double.isNaN(force)) { throw new IllegalArgumentException(
"Unexpected mathematical result in FRLayout:calcPositions [force]"); }
double dx = (xDelta / deltaLength) * force;
double dy = (yDelta / deltaLength) * force;
if(v1_locked == false) {
FRVertexData fvd1 = getFRData(v1);
fvd1.offset(-dx, -dy);
}
if(v2_locked == false) {
FRVertexData fvd2 = getFRData(v2);
fvd2.offset(dx, dy);
}
}
protected void calcRepulsion(V v1) {
FRVertexData fvd1 = getFRData(v1);
if(fvd1 == null)
return;
fvd1.setLocation(0, 0);
try {
for(V v2 : getGraph().getVertices()) {
// if (isLocked(v2)) continue;
if (v1 != v2) {
Point2D p1 = apply(v1);
Point2D p2 = apply(v2);
if(p1 == null || p2 == null) continue;
double xDelta = p1.getX() - p2.getX();
double yDelta = p1.getY() - p2.getY();
double deltaLength = Math.max(EPSILON, Math
.sqrt((xDelta * xDelta) + (yDelta * yDelta)));
double force = (repulsion_constant * repulsion_constant) / deltaLength;
if (Double.isNaN(force)) { throw new RuntimeException(
"Unexpected mathematical result in FRLayout:calcPositions [repulsion]"); }
fvd1.offset((xDelta / deltaLength) * force,
(yDelta / deltaLength) * force);
}
}
} catch(ConcurrentModificationException cme) {
calcRepulsion(v1);
}
}
private void cool() {
temperature *= (1.0 - currentIteration / (double) mMaxIterations);
}
public void setMaxIterations(int maxIterations) {
mMaxIterations = maxIterations;
}
protected FRVertexData getFRData(V v) {
return frVertexData.getUnchecked(v);
}
/**
* @return true
*/
public boolean isIncremental() {
return true;
}
/**
* @return true once the current iteration has passed the maximum count.
*/
public boolean done() {
if (currentIteration > mMaxIterations || temperature < 1.0/max_dimension)
{
return true;
}
return false;
}
@SuppressWarnings("serial")
protected static class FRVertexData extends Point2D.Double
{
protected void offset(double x, double y)
{
this.x += x;
this.y += y;
}
protected double norm()
{
return Math.sqrt(x*x + y*y);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy