org.jungrapht.visualization.layout.algorithms.SpringLayoutAlgorithm Maven / Gradle / Ivy
The newest version!
/*
* 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/tomnelson/jungrapht-visualization/blob/master/LICENSE for a description.
*/
package org.jungrapht.visualization.layout.algorithms;
import java.util.ConcurrentModificationException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.jgrapht.Graph;
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutSpringRepulsion;
import org.jungrapht.visualization.layout.algorithms.repulsion.StandardSpringRepulsion;
import org.jungrapht.visualization.layout.algorithms.util.IterativeContext;
import org.jungrapht.visualization.layout.model.LayoutModel;
import org.jungrapht.visualization.layout.model.Point;
import org.jungrapht.visualization.layout.util.RandomLocationTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The SpringLayout package represents a visualization of a set of vertices. The SpringLayout, which
* is initialized with a Graph, assigns X/Y locations to each vertex. When called step()
*
, the SpringLayout moves the visualization forward one step.
*
* @author Danyel Fisher
* @author Joshua O'Madadhain
* @author Tom Nelson
*/
public class SpringLayoutAlgorithm extends AbstractIterativeLayoutAlgorithm
implements IterativeContext {
private static final Logger log = LoggerFactory.getLogger(SpringLayoutAlgorithm.class);
protected double stretch = 0.70;
protected Function lengthFunction;
protected int repulsion_range_sq = 100 * 100;
protected double force_multiplier = 1.0 / 3.0;
boolean done = false;
final Object lock = new Object();
protected Map springVertexData = new ConcurrentHashMap<>();
protected StandardSpringRepulsion.Builder repulsionContractBuilder;
protected StandardSpringRepulsion repulsionContract;
public static class Builder<
V, E, T extends SpringLayoutAlgorithm, B extends Builder>
extends AbstractIterativeLayoutAlgorithm.Builder
implements LayoutAlgorithm.Builder {
private StandardSpringRepulsion.Builder repulsionContractBuilder =
BarnesHutSpringRepulsion.builder();
private Function lengthFunction = n -> 30;
public B repulsionContractBuilder(StandardSpringRepulsion.Builder repulsionContractBuilder) {
this.repulsionContractBuilder = repulsionContractBuilder;
return self();
}
public B withLengthFunction(Function lengthFunction) {
this.lengthFunction = lengthFunction;
return self();
}
public T build() {
return (T) new SpringLayoutAlgorithm<>(this);
}
}
public static Builder builder() {
return new Builder<>();
}
public SpringLayoutAlgorithm() {
this(SpringLayoutAlgorithm.builder());
}
protected SpringLayoutAlgorithm(Builder builder) {
super(builder);
this.lengthFunction = builder.lengthFunction;
this.repulsionContractBuilder = builder.repulsionContractBuilder;
}
/**
* Override to create the BarnesHutOctTree
*
* @param layoutModel
*/
@Override
public void visit(LayoutModel layoutModel) {
super.visit(layoutModel);
Graph graph = layoutModel.getGraph();
if (graph == null || graph.vertexSet().isEmpty()) {
return;
}
// setting the layout model will build the BHQT if the builder is the
// Optimized one
repulsionContract =
repulsionContractBuilder
.nodeData(springVertexData)
.layoutModel(layoutModel)
.random(random)
.build();
}
/** @return the current value for the stretch parameter */
public double getStretch() {
return stretch;
}
public void setStretch(double stretch) {
this.stretch = stretch;
}
public int getRepulsionRange() {
return (int) (Math.sqrt(repulsion_range_sq));
}
public void setRepulsionRange(int range) {
this.repulsion_range_sq = range * range;
}
public double getForceMultiplier() {
return force_multiplier;
}
public void setForceMultiplier(double force) {
this.force_multiplier = force;
}
public void initialize() {
Graph graph = layoutModel.getGraph();
// KKLayoutAlgorithm will fail if all vertices start at the same location
layoutModel.setInitializer(
new RandomLocationTransformer<>(
layoutModel.getWidth(), layoutModel.getHeight(), graph.vertexSet().size()));
}
public void step() {
this.repulsionContract.step();
Graph graph = layoutModel.getGraph();
try {
for (V vertex : graph.vertexSet()) {
SpringVertexData svd =
springVertexData.computeIfAbsent(vertex, v -> new SpringVertexData());
svd.dx /= 4;
svd.dy /= 4;
svd.edgedx = svd.edgedy = 0;
svd.repulsiondx = svd.repulsiondy = 0;
}
} catch (ConcurrentModificationException cme) {
step();
}
relaxEdges();
repulsionContract.calculateRepulsion();
moveVertices();
}
protected void relaxEdges() {
Graph graph = layoutModel.getGraph();
try {
for (E edge : graph.edgeSet()) {
V vertex1 = graph.getEdgeSource(edge);
V vertex2 = graph.getEdgeTarget(edge);
Point p1 = this.layoutModel.get(vertex1);
Point p2 = this.layoutModel.get(vertex2);
if (p1 == null || p2 == null) {
continue;
}
double vx = p1.x - p2.x;
double vy = p1.y - p2.y;
double len = Math.sqrt(vx * vx + vy * vy);
double desiredLen = lengthFunction.apply(edge);
// 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, (graph.degreeOf(vertex1) + graph.degreeOf(vertex2) - 2));
// the actual movement distance 'dx' is the force multiplied by the
// distance to go.
double dx = f * vx;
double dy = f * vy;
SpringVertexData v1D, v2D;
v1D = springVertexData.computeIfAbsent(vertex1, v -> new SpringVertexData());
v2D = springVertexData.computeIfAbsent(vertex2, v -> new SpringVertexData());
v1D.edgedx += dx;
v1D.edgedy += dy;
v2D.edgedx += -dx;
v2D.edgedy += -dy;
}
} catch (ConcurrentModificationException cme) {
relaxEdges();
}
}
@Override
public String toString() {
return "SpringLayoutAlgorithm{" + "repulsionContract=" + repulsionContract + '}';
}
protected void moveVertices() {
Graph graph = layoutModel.getGraph();
synchronized (lock) {
try {
for (V vertex : graph.vertexSet()) {
if (layoutModel.isLocked(vertex)) {
continue;
}
if (cancelled) {
return;
}
SpringVertexData vd =
springVertexData.computeIfAbsent(vertex, v -> new SpringVertexData());
if (vd == null) {
continue;
}
Point xyd = layoutModel.apply(vertex);
double posX = xyd.x;
double posY = xyd.y;
vd.dx += vd.repulsiondx + vd.edgedx;
vd.dy += vd.repulsiondy + vd.edgedy;
// keeps vertices from moving any faster than 5 per time unit
posX = posX + Math.max(-5, Math.min(5, vd.dx));
posY = posY + Math.max(-5, Math.min(5, vd.dy));
int width = layoutModel.getWidth();
int height = layoutModel.getHeight();
posX = Math.max(0, Math.min(width, posX));
posY = Math.max(0, Math.min(height, posY));
// after the bounds have been honored above, really set the location
// in the layout model
layoutModel.set(vertex, posX, posY);
}
} catch (ConcurrentModificationException cme) {
moveVertices();
}
}
}
public static class SpringVertexData {
protected double edgedx;
protected double edgedy;
public double repulsiondx;
public double repulsiondy;
/** movement speed, x */
protected double dx;
/** movement speed, y */
protected double dy;
@Override
public String toString() {
return "{"
+ "edge="
+ Point.of(edgedx, edgedy)
+ ", rep="
+ Point.of(repulsiondx, repulsiondy)
+ ", dx="
+ dx
+ ", dy="
+ dy
+ '}';
}
}
/** @return false */
public boolean done() {
if (cancelled) return true;
if (this.done) {
runAfter();
}
return this.done;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy