Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.mxgraph.layout.mxCompactTreeLayout Maven / Gradle / Ivy
Go to download
JGraphX Swing Component - Java Graph Visualization Library
This is a binary & source redistribution of the original, unmodified JGraphX library originating from:
"https://github.com/jgraph/jgraphx/archive/v3.4.1.3.zip".
The purpose of this redistribution is to make the library available to other Maven projects.
package com.mxgraph.layout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxPoint;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;
public class mxCompactTreeLayout extends mxGraphLayout
{
/**
* Specifies the orientation of the layout. Default is true.
*/
protected boolean horizontal;
/**
* Specifies if edge directions should be inverted. Default is false.
*/
protected boolean invert;
/**
* If the parents should be resized to match the width/height of the
* children. Default is true.
*/
protected boolean resizeParent = true;
/**
* Padding added to resized parents
*/
protected int groupPadding = 10;
/**
* A set of the parents that need updating based on children
* process as part of the layout
*/
protected Set parentsChanged = null;
/**
* Specifies if the tree should be moved to the top, left corner
* if it is inside a top-level layer. Default is false.
*/
protected boolean moveTree = false;
/**
* Specifies if all edge points of traversed edges should be removed.
* Default is true.
*/
protected boolean resetEdges = true;
/**
* Holds the levelDistance. Default is 10.
*/
protected int levelDistance = 10;
/**
* Holds the nodeDistance. Default is 20.
*/
protected int nodeDistance = 20;
/**
* The preferred horizontal distance between edges exiting a vertex
*/
protected int prefHozEdgeSep = 5;
/**
* The preferred vertical offset between edges exiting a vertex
*/
protected int prefVertEdgeOff = 2;
/**
* The minimum distance for an edge jetty from a vertex
*/
protected int minEdgeJetty = 12;
/**
* The size of the vertical buffer in the center of inter-rank channels
* where edge control points should not be placed
*/
protected int channelBuffer = 4;
/**
* Whether or not to apply the internal tree edge routing
*/
protected boolean edgeRouting = true;
/**
*
* @param graph
*/
public mxCompactTreeLayout(mxGraph graph)
{
this(graph, true);
}
/**
*
* @param graph
* @param horizontal
*/
public mxCompactTreeLayout(mxGraph graph, boolean horizontal)
{
this(graph, horizontal, false);
}
/**
*
* @param graph
* @param horizontal
* @param invert
*/
public mxCompactTreeLayout(mxGraph graph, boolean horizontal, boolean invert)
{
super(graph);
this.horizontal = horizontal;
this.invert = invert;
}
/**
* Returns a boolean indicating if the given should be ignored as a
* vertex. This returns true if the cell has no connections.
*
* @param vertex Object that represents the vertex to be tested.
* @return Returns true if the vertex should be ignored.
*/
public boolean isVertexIgnored(Object vertex)
{
return super.isVertexIgnored(vertex)
|| graph.getConnections(vertex).length == 0;
}
/**
* @return the horizontal
*/
public boolean isHorizontal()
{
return horizontal;
}
/**
* @param horizontal the horizontal to set
*/
public void setHorizontal(boolean horizontal)
{
this.horizontal = horizontal;
}
/**
* @return the invert
*/
public boolean isInvert()
{
return invert;
}
/**
* @param invert the invert to set
*/
public void setInvert(boolean invert)
{
this.invert = invert;
}
/**
* @return the resizeParent
*/
public boolean isResizeParent()
{
return resizeParent;
}
/**
* @param resizeParent the resizeParent to set
*/
public void setResizeParent(boolean resizeParent)
{
this.resizeParent = resizeParent;
}
/**
* @return the moveTree
*/
public boolean isMoveTree()
{
return moveTree;
}
/**
* @param moveTree the moveTree to set
*/
public void setMoveTree(boolean moveTree)
{
this.moveTree = moveTree;
}
/**
* @return the resetEdges
*/
public boolean isResetEdges()
{
return resetEdges;
}
/**
* @param resetEdges the resetEdges to set
*/
public void setResetEdges(boolean resetEdges)
{
this.resetEdges = resetEdges;
}
public boolean isEdgeRouting()
{
return edgeRouting;
}
public void setEdgeRouting(boolean edgeRouting)
{
this.edgeRouting = edgeRouting;
}
/**
* @return the levelDistance
*/
public int getLevelDistance()
{
return levelDistance;
}
/**
* @param levelDistance the levelDistance to set
*/
public void setLevelDistance(int levelDistance)
{
this.levelDistance = levelDistance;
}
/**
* @return the nodeDistance
*/
public int getNodeDistance()
{
return nodeDistance;
}
/**
* @param nodeDistance the nodeDistance to set
*/
public void setNodeDistance(int nodeDistance)
{
this.nodeDistance = nodeDistance;
}
public double getGroupPadding()
{
return groupPadding;
}
public void setGroupPadding(int groupPadding)
{
this.groupPadding = groupPadding;
}
/*
* (non-Javadoc)
* @see com.mxgraph.layout.mxIGraphLayout#execute(java.lang.Object)
*/
public void execute(Object parent)
{
super.execute(parent);
execute(parent, null);
}
/**
* Implements .
*
* If the parent has any connected edges, then it is used as the root of
* the tree. Else, will be used to find a suitable
* root node within the set of children of the given parent.
*/
public void execute(Object parent, Object root)
{
mxIGraphModel model = graph.getModel();
if (root == null)
{
// Takes the parent as the root if it has outgoing edges
if (graph.getEdges(parent, model.getParent(parent), invert,
!invert, false).length > 0)
{
root = parent;
}
// Tries to find a suitable root in the parent's
// children
else
{
List roots = findTreeRoots(parent, invert);
if (roots.size() > 0)
{
for (int i = 0; i < roots.size(); i++)
{
if (!isVertexIgnored(roots.get(i))
&& graph.getEdges(roots.get(i), null, invert,
!invert, false).length > 0)
{
root = roots.get(i);
break;
}
}
}
}
}
if (root != null)
{
if (resizeParent)
{
parentsChanged = new HashSet();
}
else
{
parentsChanged = null;
}
model.beginUpdate();
try
{
TreeNode node = dfs(root, parent, null);
if (node != null)
{
layout(node);
double x0 = graph.getGridSize();
double y0 = x0;
if (!moveTree)
{
mxRectangle g = getVertexBounds(root);
if (g != null)
{
x0 = g.getX();
y0 = g.getY();
}
}
mxRectangle bounds = null;
if (horizontal)
{
bounds = horizontalLayout(node, x0, y0, null);
}
else
{
bounds = verticalLayout(node, null, x0, y0, null);
}
if (bounds != null)
{
double dx = 0;
double dy = 0;
if (bounds.getX() < 0)
{
dx = Math.abs(x0 - bounds.getX());
}
if (bounds.getY() < 0)
{
dy = Math.abs(y0 - bounds.getY());
}
if (dx != 0 || dy != 0)
{
moveNode(node, dx, dy);
}
if (resizeParent)
{
adjustParents();
}
if (edgeRouting)
{
// Iterate through all edges setting their positions
localEdgeProcessing(node);
}
}
}
}
finally
{
model.endUpdate();
}
}
}
/**
* Returns all visible children in the given parent which do not have
* incoming edges. If the result is empty then the children with the
* maximum difference between incoming and outgoing edges are returned.
* This takes into account edges that are being promoted to the given
* root due to invisible children or collapsed cells.
*
* @param parent Cell whose children should be checked.
* @param invert Specifies if outgoing or incoming edges should be counted
* for a tree root. If false then outgoing edges will be counted.
* @return List of tree roots in parent.
*/
public List findTreeRoots(Object parent, boolean invert)
{
List roots = new ArrayList();
if (parent != null)
{
mxIGraphModel model = graph.getModel();
int childCount = model.getChildCount(parent);
Object best = null;
int maxDiff = 0;
for (int i = 0; i < childCount; i++)
{
Object cell = model.getChildAt(parent, i);
if (model.isVertex(cell) && graph.isCellVisible(cell))
{
Object[] conns = graph.getConnections(cell, parent, true);
int fanOut = 0;
int fanIn = 0;
for (int j = 0; j < conns.length; j++)
{
Object src = graph.getView().getVisibleTerminal(
conns[j], true);
if (src == cell)
{
fanOut++;
}
else
{
fanIn++;
}
}
if ((invert && fanOut == 0 && fanIn > 0)
|| (!invert && fanIn == 0 && fanOut > 0))
{
roots.add(cell);
}
int diff = (invert) ? fanIn - fanOut : fanOut - fanIn;
if (diff > maxDiff)
{
maxDiff = diff;
best = cell;
}
}
}
if (roots.isEmpty() && best != null)
{
roots.add(best);
}
}
return roots;
}
/**
* Moves the specified node and all of its children by the given amount.
*/
protected void moveNode(TreeNode node, double dx, double dy)
{
node.x += dx;
node.y += dy;
apply(node, null);
TreeNode child = node.child;
while (child != null)
{
moveNode(child, dx, dy);
child = child.next;
}
}
/**
* Does a depth first search starting at the specified cell.
* Makes sure the specified parent is never left by the
* algorithm.
*/
protected TreeNode dfs(Object cell, Object parent, Set visited)
{
if (visited == null)
{
visited = new HashSet();
}
TreeNode node = null;
if (cell != null && !visited.contains(cell) && !isVertexIgnored(cell))
{
visited.add(cell);
node = createNode(cell);
mxIGraphModel model = graph.getModel();
TreeNode prev = null;
Object[] out = graph.getEdges(cell, parent, invert, !invert, false,
true);
mxGraphView view = graph.getView();
for (int i = 0; i < out.length; i++)
{
Object edge = out[i];
if (!isEdgeIgnored(edge))
{
// Resets the points on the traversed edge
if (resetEdges)
{
setEdgePoints(edge, null);
}
if (edgeRouting)
{
setEdgeStyleEnabled(edge, false);
setEdgePoints(edge, null);
}
// Checks if terminal in same swimlane
mxCellState state = view.getState(edge);
Object target = (state != null) ? state
.getVisibleTerminal(invert) : view
.getVisibleTerminal(edge, invert);
TreeNode tmp = dfs(target, parent, visited);
if (tmp != null && model.getGeometry(target) != null)
{
if (prev == null)
{
node.child = tmp;
}
else
{
prev.next = tmp;
}
prev = tmp;
}
}
}
}
return node;
}
/**
* Starts the actual compact tree layout algorithm
* at the given node.
*/
protected void layout(TreeNode node)
{
if (node != null)
{
TreeNode child = node.child;
while (child != null)
{
layout(child);
child = child.next;
}
if (node.child != null)
{
attachParent(node, join(node));
}
else
{
layoutLeaf(node);
}
}
}
/**
*
*/
protected mxRectangle horizontalLayout(TreeNode node, double x0, double y0,
mxRectangle bounds)
{
node.x += x0 + node.offsetX;
node.y += y0 + node.offsetY;
bounds = apply(node, bounds);
TreeNode child = node.child;
if (child != null)
{
bounds = horizontalLayout(child, node.x, node.y, bounds);
double siblingOffset = node.y + child.offsetY;
TreeNode s = child.next;
while (s != null)
{
bounds = horizontalLayout(s, node.x + child.offsetX,
siblingOffset, bounds);
siblingOffset += s.offsetY;
s = s.next;
}
}
return bounds;
}
/**
*
*/
protected mxRectangle verticalLayout(TreeNode node, Object parent,
double x0, double y0, mxRectangle bounds)
{
node.x += x0 + node.offsetY;
node.y += y0 + node.offsetX;
bounds = apply(node, bounds);
TreeNode child = node.child;
if (child != null)
{
bounds = verticalLayout(child, node, node.x, node.y, bounds);
double siblingOffset = node.x + child.offsetY;
TreeNode s = child.next;
while (s != null)
{
bounds = verticalLayout(s, node, siblingOffset, node.y
+ child.offsetX, bounds);
siblingOffset += s.offsetY;
s = s.next;
}
}
return bounds;
}
/**
*
*/
protected void attachParent(TreeNode node, double height)
{
double x = nodeDistance + levelDistance;
double y2 = (height - node.width) / 2 - nodeDistance;
double y1 = y2 + node.width + 2 * nodeDistance - height;
node.child.offsetX = x + node.height;
node.child.offsetY = y1;
node.contour.upperHead = createLine(node.height, 0,
createLine(x, y1, node.contour.upperHead));
node.contour.lowerHead = createLine(node.height, 0,
createLine(x, y2, node.contour.lowerHead));
}
/**
*
*/
protected void layoutLeaf(TreeNode node)
{
double dist = 2 * nodeDistance;
node.contour.upperTail = createLine(node.height + dist, 0, null);
node.contour.upperHead = node.contour.upperTail;
node.contour.lowerTail = createLine(0, -node.width - dist, null);
node.contour.lowerHead = createLine(node.height + dist, 0,
node.contour.lowerTail);
}
/**
*
*/
protected double join(TreeNode node)
{
double dist = 2 * nodeDistance;
TreeNode child = node.child;
node.contour = child.contour;
double h = child.width + dist;
double sum = h;
child = child.next;
while (child != null)
{
double d = merge(node.contour, child.contour);
child.offsetY = d + h;
child.offsetX = 0;
h = child.width + dist;
sum += d + h;
child = child.next;
}
return sum;
}
/**
*
*/
protected double merge(Polygon p1, Polygon p2)
{
double x = 0;
double y = 0;
double total = 0;
Polyline upper = p1.lowerHead;
Polyline lower = p2.upperHead;
while (lower != null && upper != null)
{
double d = offset(x, y, lower.dx, lower.dy, upper.dx, upper.dy);
y += d;
total += d;
if (x + lower.dx <= upper.dx)
{
x += lower.dx;
y += lower.dy;
lower = lower.next;
}
else
{
x -= upper.dx;
y -= upper.dy;
upper = upper.next;
}
}
if (lower != null)
{
Polyline b = bridge(p1.upperTail, 0, 0, lower, x, y);
p1.upperTail = (b.next != null) ? p2.upperTail : b;
p1.lowerTail = p2.lowerTail;
}
else
{
Polyline b = bridge(p2.lowerTail, x, y, upper, 0, 0);
if (b.next == null)
{
p1.lowerTail = b;
}
}
p1.lowerHead = p2.lowerHead;
return total;
}
/**
*
*/
protected double offset(double p1, double p2, double a1, double a2,
double b1, double b2)
{
double d = 0;
if (b1 <= p1 || p1 + a1 <= 0)
{
return 0;
}
double t = b1 * a2 - a1 * b2;
if (t > 0)
{
if (p1 < 0)
{
double s = p1 * a2;
d = s / a1 - p2;
}
else if (p1 > 0)
{
double s = p1 * b2;
d = s / b1 - p2;
}
else
{
d = -p2;
}
}
else if (b1 < p1 + a1)
{
double s = (b1 - p1) * a2;
d = b2 - (p2 + s / a1);
}
else if (b1 > p1 + a1)
{
double s = (a1 + p1) * b2;
d = s / b1 - (p2 + a2);
}
else
{
d = b2 - (p2 + a2);
}
if (d > 0)
{
return d;
}
return 0;
}
/**
*
*/
protected Polyline bridge(Polyline line1, double x1, double y1,
Polyline line2, double x2, double y2)
{
double dx = x2 + line2.dx - x1;
double dy = 0;
double s = 0;
if (line2.dx == 0)
{
dy = line2.dy;
}
else
{
s = dx * line2.dy;
dy = s / line2.dx;
}
Polyline r = createLine(dx, dy, line2.next);
line1.next = createLine(0, y2 + line2.dy - dy - y1, r);
return r;
}
/**
*
*/
protected TreeNode createNode(Object cell)
{
TreeNode node = new TreeNode(cell);
mxRectangle geo = getVertexBounds(cell);
if (geo != null)
{
if (horizontal)
{
node.width = geo.getHeight();
node.height = geo.getWidth();
}
else
{
node.width = geo.getWidth();
node.height = geo.getHeight();
}
}
return node;
}
/**
*
* @param node
* @param bounds
* @return
*/
protected mxRectangle apply(TreeNode node, mxRectangle bounds)
{
mxIGraphModel model = graph.getModel();
Object cell = node.cell;
mxRectangle g = model.getGeometry(cell);
if (cell != null && g != null)
{
if (isVertexMovable(cell))
{
g = setVertexLocation(cell, node.x, node.y);
if (resizeParent)
{
parentsChanged.add(model.getParent(cell));
}
}
if (bounds == null)
{
bounds = new mxRectangle(g.getX(), g.getY(), g.getWidth(),
g.getHeight());
}
else
{
bounds = new mxRectangle(Math.min(bounds.getX(), g.getX()),
Math.min(bounds.getY(), g.getY()), Math.max(
bounds.getX() + bounds.getWidth(),
g.getX() + g.getWidth()), Math.max(
bounds.getY() + bounds.getHeight(), g.getY()
+ g.getHeight()));
}
}
return bounds;
}
/**
*
*/
protected Polyline createLine(double dx, double dy, Polyline next)
{
return new Polyline(dx, dy, next);
}
/**
* Adjust parent cells whose child geometries have changed. The default
* implementation adjusts the group to just fit around the children with
* a padding.
*/
protected void adjustParents()
{
arrangeGroups(mxUtils.sortCells(this.parentsChanged, true).toArray(), groupPadding);
}
/**
* Moves the specified node and all of its children by the given amount.
*/
protected void localEdgeProcessing(TreeNode node)
{
processNodeOutgoing(node);
TreeNode child = node.child;
while (child != null)
{
localEdgeProcessing(child);
child = child.next;
}
}
/**
* Separates the x position of edges as they connect to vertices
*
* @param node
* the root node of the tree
*/
protected void processNodeOutgoing(TreeNode node)
{
mxIGraphModel model = graph.getModel();
TreeNode child = node.child;
Object parentCell = node.cell;
int childCount = 0;
List sortedCells = new ArrayList();
while (child != null)
{
childCount++;
double sortingCriterion = child.x;
if (this.horizontal)
{
sortingCriterion = child.y;
}
sortedCells.add(new WeightedCellSorter(child,
(int) sortingCriterion));
child = child.next;
}
WeightedCellSorter[] sortedCellsArray = sortedCells
.toArray(new WeightedCellSorter[sortedCells.size()]);
Arrays.sort(sortedCellsArray);
double availableWidth = node.width;
double requiredWidth = (childCount + 1) * prefHozEdgeSep;
// Add a buffer on the edges of the vertex if the edge count allows
if (availableWidth > requiredWidth + (2 * prefHozEdgeSep))
{
availableWidth -= 2 * prefHozEdgeSep;
}
double edgeSpacing = availableWidth / childCount;
double currentXOffset = edgeSpacing / 2.0;
if (availableWidth > requiredWidth + (2 * prefHozEdgeSep))
{
currentXOffset += prefHozEdgeSep;
}
double currentYOffset = minEdgeJetty - prefVertEdgeOff;
double maxYOffset = 0;
mxRectangle parentBounds = getVertexBounds(parentCell);
child = node.child;
for (int j = 0; j < sortedCellsArray.length; j++)
{
Object childCell = sortedCellsArray[j].cell.cell;
mxRectangle childBounds = getVertexBounds(childCell);
Object[] edges = mxGraphModel.getEdgesBetween(model, parentCell,
childCell);
List newPoints = new ArrayList(3);
double x = 0;
double y = 0;
for (int i = 0; i < edges.length; i++)
{
if (this.horizontal)
{
// Use opposite co-ords, calculation was done for
//
x = parentBounds.getX() + parentBounds.getWidth();
y = parentBounds.getY() + currentXOffset;
newPoints.add(new mxPoint(x, y));
x = parentBounds.getX() + parentBounds.getWidth()
+ currentYOffset;
newPoints.add(new mxPoint(x, y));
y = childBounds.getY() + childBounds.getHeight() / 2.0;
newPoints.add(new mxPoint(x, y));
setEdgePoints(edges[i], newPoints);
}
else
{
x = parentBounds.getX() + currentXOffset;
y = parentBounds.getY() + parentBounds.getHeight();
newPoints.add(new mxPoint(x, y));
y = parentBounds.getY() + parentBounds.getHeight()
+ currentYOffset;
newPoints.add(new mxPoint(x, y));
x = childBounds.getX() + childBounds.getWidth() / 2.0;
newPoints.add(new mxPoint(x, y));
setEdgePoints(edges[i], newPoints);
}
}
if (j < (float) childCount / 2.0f)
{
currentYOffset += prefVertEdgeOff;
}
else if (j > (float) childCount / 2.0f)
{
currentYOffset -= prefVertEdgeOff;
}
// Ignore the case if equals, this means the second of 2
// jettys with the same y (even number of edges)
// pos[k * 2] = currentX;
currentXOffset += edgeSpacing;
// pos[k * 2 + 1] = currentYOffset;
maxYOffset = Math.max(maxYOffset, currentYOffset);
}
}
/**
* A utility class used to track cells whilst sorting occurs on the weighted
* sum of their connected edges. Does not violate (x.compareTo(y)==0) ==
* (x.equals(y))
*/
protected class WeightedCellSorter implements Comparable
{
/**
* The weighted value of the cell stored
*/
public int weightedValue = 0;
/**
* Whether or not to flip equal weight values.
*/
public boolean nudge = false;
/**
* Whether or not this cell has been visited in the current assignment
*/
public boolean visited = false;
/**
* The cell whose median value is being calculated
*/
public TreeNode cell = null;
public WeightedCellSorter()
{
this(null, 0);
}
public WeightedCellSorter(TreeNode cell, int weightedValue)
{
this.cell = cell;
this.weightedValue = weightedValue;
}
/**
* comparator on the medianValue
*
* @param arg0
* the object to be compared to
* @return the standard return you would expect when comparing two
* double
*/
public int compareTo(Object arg0)
{
if (arg0 instanceof WeightedCellSorter)
{
if (weightedValue > ((WeightedCellSorter) arg0).weightedValue)
{
return 1;
}
else if (weightedValue < ((WeightedCellSorter) arg0).weightedValue)
{
return -1;
}
}
return 0;
}
}
/**
*
*/
protected static class TreeNode
{
/**
*
*/
protected Object cell;
/**
*
*/
protected double x, y, width, height, offsetX, offsetY;
/**
*
*/
protected TreeNode child, next; // parent, sibling
/**
*
*/
protected Polygon contour = new Polygon();
/**
*
*/
public TreeNode(Object cell)
{
this.cell = cell;
}
}
/**
*
*/
protected static class Polygon
{
/**
*
*/
protected Polyline lowerHead, lowerTail, upperHead, upperTail;
}
/**
*
*/
protected static class Polyline
{
/**
*
*/
protected double dx, dy;
/**
*
*/
protected Polyline next;
/**
*
*/
protected Polyline(double dx, double dy, Polyline next)
{
this.dx = dx;
this.dy = dy;
this.next = next;
}
}
}