public class SpringGraphLayout extends GraphLayout
The main principle behind its operation is that graph nodes are subject to two forces: a spring force and a coulomb (electric charge) force. The edges are considered to be springs subject to Hooke's law where the force is attraction when the string is stretched (i.e., length greater than the equilibrium length desired) or repulsion when the spring is compressed to less than the equilibrium length. The nodes are considered to be electric point charges (all with the same positive charge) that repel each other according to Coulomb's reciprocal-of-the-distance law. There is a mass function which generally returns the same for all nodes, but can be adjusted (e.g., for immovable notes) depending on the kind of node. See the mass() method to override.
In order to handle contexts, the algorithm works in a depth-first recursive way. That is, nested contexts are laid out before their enclosing graph. The context is treated as a point-node, whose mass is equal to the sum of the masses of its constituents. If there is an edge that crosses into the context, a fake "edge" is temporarily created to directly connect the context itself for the purposes of determining the layout. This fake edge is only considered when laying out the outer graph; the context's layout is unaware of any edges that link to the outer context(s).
The displacement is merely the force interpreted as a position translation operation.
For unconnected components (in the graph theory sense), the algorithm will ordinarily cause them to push apart (since there's no spring force to balance), leaving lots of wide open space. Therefore there's a step that identifies unconnected components, and connects each one's center-most node with at least one other center-most node. This ensures that they will have at least one attractive force to help them stay together.Fine tuning of the layout parameters is crucial to the effective use of the algorithm. See comments on each of the layout parameters to better understand how they work.
Future tweaks:
Tell the force calculators to "favor"
x-forces so that layouts will tend toward landscape orientation.
Tell the force calculators to "favor" rectilinear placement.
Modifier and Type | Field and Description |
---|---|
private boolean |
applyUpLeftPressure |
private int |
ATTRACTION_FORCE_SIGN
The arithmetic sign of an attractive force.
|
private double |
borderMargin
How much extra space to provide on the top and left of the final laid out graph.
|
private float |
contextInnerPadding
How much padding to provide between the bounding rectangle of the graph/context and its enclosed contents.
|
private double |
COULOMB_CONSTANT
Coulomb constant (numerator) for the distance-squared calculations.
|
private double |
COULOMB_EXPONENT
Used to further "dampen" the coulomb force.
|
private static double |
DAMPER_FOR_COULOMB
Multiplier for the coulomb (repulsion) force
|
private static double |
DAMPER_FOR_SPRING
Multiplier for the spring force
|
private static double |
DAMPING_FOR_DISPLACEMENTS
Further damping for displacements; currently set to 1.0
|
private ArrayList<GEdge> |
edges |
private double |
ENERGY_THRESHHOLD
The minimum energy level that tells the algorithm it can stop.
|
private double |
ENERGY_THRESHHOLD_PER_NODE
To fine-tune the threshhold
|
private static double |
EPSILON
Double-precision values are considered equal if they are within this value of each other
|
private double |
equilibriumEdgeLength
The "best" edge length.
|
private double |
MAX_DISTANCE
The maximum distance between nodes; if they are farther apart than this,
bring them in to this distance.
|
private Rectangle2D.Double |
maxBoundsAvailable
How much space this graph is allowed to occupy
|
static int |
maxIterations
When to call it quits on iterating through the algorithm.
|
private double |
MIN_DISTANCE
The minimum distance between nodes; if they are actually closer than this,
then assume they are at this distance.
|
private static DecimalFormat |
nformat |
private HashMap<GNode,Point2D.Double> |
nodeDisplacements
Displacement is represented as a "point" where the x value is dx and the
y value is dy.
|
private HashMap<GNode,Point2D.Double> |
nodeForces |
private HashMap<GNode,Point2D.Double> |
nodePositions
Keeps track of each node's displacement (movement) at each iteration.
|
private ArrayList<GNode> |
nodes |
private double |
pressureScale |
private int |
REPULSION_FORCE_SIGN
Set to -1 * the attraction force.
|
private static double |
SPRING_CONSTANT
Used in determining the spring force using Hooke's law.
|
private static Point2D.Double |
zeroPoint |
graph, verbose
Constructor and Description |
---|
SpringGraphLayout(Graph g) |
Modifier and Type | Method and Description |
---|---|
void |
addEdgesFromUnconnectedComponents()
Add custom edges between unconnected graph components.
|
void |
addForceToNodeForces(Point2D.Double force,
GraphObject node)
For the given node, add the force to the current forces on it.
|
void |
addNewCoulombForces()
For each node in the graph, calculate the new coulomb force vectors (as
"points") and add them to the nodeForces list.
|
void |
addNewSpringForces() |
void |
copyToGraph()
Update the actual graph to reflect the positions calculated by the
layout.
|
Point2D.Double |
coulomb_force(GNode thisnode,
GNode other)
Given two nodes, calculates the coulomb force that repels them.
|
double |
getBorderMargin() |
protected Rectangle2D.Double |
getBounds(boolean displayRects)
Find the bounds of all the node positions (not the original graph).
|
double |
getClippedLength(GNode node1,
GNode node2)
Considers the shape (from the original graph) returns the distance
between the clipping points of a center-to-center line between the
objects.
|
protected Rectangle2D.Double |
getDisplayRectBounds()
Find the bounds of all the node display rectangles (using the original
graph).
|
double |
getEquilibriumEdgeLength()
The preferred length of an edge to be sought by the algorithm.
|
Rectangle2D.Double |
getMaxBoundsAvailable()
Find out the maximum size of the area in which nodes can be placed.
|
protected Rectangle2D.Double |
getPositionBounds()
Find the bounds of all the node positions (not the original graph).
|
static Point2D.Double |
getXYForce(double force,
Point2D.Double source,
Point2D.Double dest)
Calculates both the x and y components of a force between two points.
|
private Rectangle2D.Double |
inferCurrentDisplayRect(GNode node)
Uses the height and width of the node's display rectangle, but infers
(from its new proposed center point) the rectangle it would form in its
nodePositions location.
|
void |
loadEdges()
Initializes the edges list.
|
void |
loadNodePositions()
Using the nodes list, get the actual positions from the original objects
and load up nodePositions list.
|
double |
makeNewPositions(boolean upLeftPressure)
From the existing forces vectors, calculate a displacement for each node,
assume a unit time interval and calculate a new position for each node.
|
double |
mass(GNode gn)
Allows the algorithm to adjust for varying "sizes" of nodes.
|
void |
moveToUpperLeft(boolean force)
Makes sure there are no negative-coordinate positions.
|
protected boolean |
nodesInLine()
Using the original graph nodes, determines whether, within EPSILON, the
nodes are in line, either along an x-value or a y-value.
|
boolean |
performLayout()
Perform the layout operation on the graph.
|
boolean |
performLayoutOnContext(Graph g)
Invokes the spring algorithm recursively on any nested graph.
|
double |
performOneRelaxation()
One iteration of the algorithm, starting with zero forces all around.
|
protected void |
randomizePositions()
Not really random; moves each node one pixel right and down from the
previous node.
|
static Point2D.Double |
reverseForce(Point2D.Double force) |
protected void |
scanAndLoadGraph()
Initialize the node and edges lists.
|
void |
setBorderMargin(double borderMargin) |
void |
setEquilibriumEdgeLength(double equilibriumEdgeLength) |
void |
setMaxBoundsAvailable(Rectangle rect)
Set the available rectangle to be the current extent of the graph; i.e., it will not take up any
more room in display.
|
void |
setMaxBoundsAvailable(Rectangle2D.Double maxBoundsAvailable)
Set the maximum space allowed in laying out the graph.
|
private void |
showAllForces(String s) |
private void |
showDisplacements(String s) |
static String |
showForce(Point2D.Double p) |
private String |
showPoint(Point2D.Double point) |
private void |
showPositions(String s) |
Point2D.Double |
spring_force(GEdge edge,
boolean fromTo)
Models the Hooke's law spring force along edges.
|
private void |
zeroOutVectors(HashMap<GNode,Point2D.Double> points)
convenience method for re-setting the hashmaps after each relaxation.
|
isVerbose, setVerbose
private double equilibriumEdgeLength
private double borderMargin
private float contextInnerPadding
private Rectangle2D.Double maxBoundsAvailable
public static int maxIterations
Global.springLayoutMaxIterations
private double MIN_DISTANCE
private double MAX_DISTANCE
private static final double DAMPER_FOR_SPRING
private static final double DAMPER_FOR_COULOMB
private double COULOMB_EXPONENT
private double ENERGY_THRESHHOLD
private double ENERGY_THRESHHOLD_PER_NODE
private static double EPSILON
private static double DAMPING_FOR_DISPLACEMENTS
private static double SPRING_CONSTANT
DAMPER_FOR_SPRING
private double COULOMB_CONSTANT
COULOMB_EXPONENT
private static final Point2D.Double zeroPoint
private int ATTRACTION_FORCE_SIGN
private int REPULSION_FORCE_SIGN
private static DecimalFormat nformat
private boolean applyUpLeftPressure
private double pressureScale
private HashMap<GNode,Point2D.Double> nodeDisplacements
private HashMap<GNode,Point2D.Double> nodePositions
private HashMap<GNode,Point2D.Double> nodeForces
private ArrayList<GNode> nodes
private ArrayList<GEdge> edges
public SpringGraphLayout(Graph g)
protected void scanAndLoadGraph()
protected boolean nodesInLine()
protected void randomizePositions()
private void zeroOutVectors(HashMap<GNode,Point2D.Double> points)
points
- public boolean performLayout()
performLayout
in class GraphLayout
copyToGraph()
public double performOneRelaxation()
public boolean performLayoutOnContext(Graph g)
g
- the context to be laid outpublic double makeNewPositions(boolean upLeftPressure)
upLeftPressure
- if true, then an additional "pressure" is applied to
force nodes upward and to the left. This should help layouts from getting too spread out.public void addNewCoulombForces()
public void addNewSpringForces()
public Point2D.Double coulomb_force(GNode thisnode, GNode other)
thisnode
- The node from which the other is being repelledother
- public Point2D.Double spring_force(GEdge edge, boolean fromTo)
edge
- The edge whose forces are to be determinedfromTo
- Whether to calculate force from the from object to the to
object or vice versapublic double mass(GNode gn)
gn
- public void addForceToNodeForces(Point2D.Double force, GraphObject node)
force
- x and y values to be added to the force, multiplied by the "mass" of the node.node
- The node whose force is to be alteredpublic void moveToUpperLeft(boolean force)
force
- Whether to force the graph into the upper left corner
whether it already fits or not.public static Point2D.Double getXYForce(double force, Point2D.Double source, Point2D.Double dest)
force
- source
- dest
- public void loadNodePositions()
public void loadEdges()
public void addEdgesFromUnconnectedComponents()
public void copyToGraph()
copyToGraph
in class GraphLayout
private void showAllForces(String s)
private void showDisplacements(String s)
private void showPositions(String s)
private String showPoint(Point2D.Double point)
public static String showForce(Point2D.Double p)
public static Point2D.Double reverseForce(Point2D.Double force)
protected Rectangle2D.Double getBounds(boolean displayRects)
displayRects
- whether to consider the displayrects (an
approximation for the shapes)protected Rectangle2D.Double getPositionBounds()
protected Rectangle2D.Double getDisplayRectBounds()
private Rectangle2D.Double inferCurrentDisplayRect(GNode node)
node
- public double getClippedLength(GNode node1, GNode node2)
public Rectangle2D.Double getMaxBoundsAvailable()
public void setMaxBoundsAvailable(Rectangle rect)
rect
- public void setMaxBoundsAvailable(Rectangle2D.Double maxBoundsAvailable)
maxBoundsAvailable
- public double getEquilibriumEdgeLength()
public void setEquilibriumEdgeLength(double equilibriumEdgeLength)
public double getBorderMargin()
public void setBorderMargin(double borderMargin)