/*******************************************************************************
* Copyright (c) 2005, 2016 The Chisel Group and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors: Casey Best, Ian Bull, Rob Lintern, Jingwei Wu (The Chisel Group) - initial API and implementation
* Mateusz Matela - "Tree Views for Zest" contribution, Google Summer of Code 2009
* Miles Parker - optional node space configuration
* Matthias Wienand (itemis AG) - refactorings
*
******************************************************************************/
package org.eclipse.gef.layout.algorithms;
import java.util.Iterator;
import org.eclipse.gef.geometry.planar.Dimension;
import org.eclipse.gef.geometry.planar.Point;
import org.eclipse.gef.geometry.planar.Rectangle;
import org.eclipse.gef.graph.Node;
import org.eclipse.gef.layout.ILayoutAlgorithm;
import org.eclipse.gef.layout.LayoutContext;
import org.eclipse.gef.layout.LayoutProperties;
import org.eclipse.gef.layout.algorithms.TreeLayoutHelper.TreeNode;
/**
* The TreeLayoutAlgorithm class implements a simple algorithm to arrange graph
* nodes in a layered tree-like layout.
*
* @author Casey Best
* @author Ian Bull
* @author Rob Lintern
* @author Jingwei Wu
* @author Mateusz Matela
* @author Miles Parker
* @author mwienand
*/
public class TreeLayoutAlgorithm implements ILayoutAlgorithm {
/**
* Tree direction constant for which root is placed at the top and branches
* spread downwards
*/
public final static int TOP_DOWN = 1;
/**
* Tree direction constant for which root is placed at the bottom and
* branches spread upwards
*/
public final static int BOTTOM_UP = 2;
/**
* Tree direction constant for which root is placed at the left and branches
* spread to the right
*/
public final static int LEFT_RIGHT = 3;
/**
* Tree direction constant for which root is placed at the right and
* branches spread to the left
*/
public final static int RIGHT_LEFT = 4;
private int direction = TOP_DOWN;
private boolean resize = false;
private Rectangle bounds;
private double leafSize, layerSize;
private TreeLayoutHelper treeObserver;
private Dimension nodeSpace;
/**
* Create a default Tree Layout.
*/
public TreeLayoutAlgorithm() {
}
/**
* Create a Tree Layout with a specified direction.
*
* @param direction
* The direction, one of {@link TreeLayoutAlgorithm#BOTTOM_UP},
* {@link TreeLayoutAlgorithm#LEFT_RIGHT},
* {@link TreeLayoutAlgorithm#RIGHT_LEFT},
* {@link TreeLayoutAlgorithm#TOP_DOWN}
*/
public TreeLayoutAlgorithm(int direction) {
this(direction, null);
}
/**
* Create a Tree Layout with fixed size spacing around nodes. If nodeSpace
* is not null, the layout will size the container to the ideal space to
* just contain all nodes of fixed size without any overlap. Otherwise, the
* algorithm will size for the container's available space.
*
* @param direction
* The direction, one of {@link TreeLayoutAlgorithm#BOTTOM_UP},
* {@link TreeLayoutAlgorithm#LEFT_RIGHT},
* {@link TreeLayoutAlgorithm#RIGHT_LEFT},
* {@link TreeLayoutAlgorithm#TOP_DOWN}
* @param nodeSpace
* the size to make each node. May be null.
*/
public TreeLayoutAlgorithm(int direction, Dimension nodeSpace) {
setDirection(direction);
this.nodeSpace = nodeSpace;
}
/**
* @param nodeSpace
* the nodeSpace size to set
*/
public void setNodeSpace(Dimension nodeSpace) {
this.nodeSpace = nodeSpace;
}
/**
* Returns the direction of this {@link TreeLayoutAlgorithm}.
*
* @return The direction of this {@link TreeLayoutAlgorithm}.
*/
public int getDirection() {
return direction;
}
/**
* Changes the direction of this {@link TreeLayoutAlgorithm} to the given
* value. The direction may either be {@link #TOP_DOWN}, {@link #BOTTOM_UP},
* {@link #LEFT_RIGHT}, or {@link #RIGHT_LEFT}.
*
* @param direction
* The new direction for this {@link TreeLayoutAlgorithm}.
*/
public void setDirection(int direction) {
if (direction == TOP_DOWN || direction == BOTTOM_UP
|| direction == LEFT_RIGHT || direction == RIGHT_LEFT)
this.direction = direction;
else
throw new IllegalArgumentException(
"Invalid direction: " + direction);
}
/**
*
* @return true if this algorithm is set to resize elements
*/
public boolean isResizing() {
return resize;
}
/**
*
* @param resizing
* true if this algorithm should resize elements (default is
* false)
*/
public void setResizing(boolean resizing) {
resize = resizing;
}
public void applyLayout(LayoutContext layoutContext, boolean clean) {
if (!clean)
return;
internalApplyLayout(layoutContext);
Node[] entities = layoutContext.getNodes();
if (resize) {
AlgorithmHelper.maximizeSizes(entities);
}
scaleEntities(entities);
}
private void scaleEntities(Node[] entities) {
if (nodeSpace == null) {
Rectangle resizedBounds = new Rectangle(bounds);
int insets = 4;
resizedBounds.setX(resizedBounds.getX() + insets);
resizedBounds.setY(resizedBounds.getY() + insets);
resizedBounds.setWidth(resizedBounds.getWidth() - 2 * insets);
resizedBounds.setHeight(resizedBounds.getHeight() - 2 * insets);
AlgorithmHelper.fitWithinBounds(entities, resizedBounds, resize);
}
}
/**
* Performs a layout pass for the tree without scaling the entities to
* maximum size / use the whole bounds.
*
* @param context
* The {@link LayoutContext} to use.
*/
void internalApplyLayout(LayoutContext context) {
treeObserver = new TreeLayoutHelper(null);
treeObserver.computeTree(context.getNodes());
TreeNode superRoot = treeObserver.getSuperRoot();
bounds = LayoutProperties.getBounds(context.getGraph());
updateLeafAndLayerSizes();
int leafCountSoFar = 0;
for (Iterator<TreeNode> iterator = superRoot.getChildren()
.iterator(); iterator.hasNext();) {
TreeNode rootInfo = iterator.next();
computePositionRecursively(rootInfo, leafCountSoFar);
leafCountSoFar = leafCountSoFar + rootInfo.numOfLeaves;
}
}
private void updateLeafAndLayerSizes() {
if (nodeSpace != null) {
if (getDirection() == TOP_DOWN || getDirection() == BOTTOM_UP) {
leafSize = nodeSpace.getWidth();
layerSize = nodeSpace.getHeight();
} else {
leafSize = nodeSpace.getHeight();
layerSize = nodeSpace.getWidth();
}
} else {
TreeNode superRoot = treeObserver.getSuperRoot();
if (direction == TOP_DOWN || direction == BOTTOM_UP) {
leafSize = bounds.getWidth() / superRoot.numOfLeaves;
layerSize = bounds.getHeight() / superRoot.height;
} else {
leafSize = bounds.getHeight() / superRoot.numOfLeaves;
layerSize = bounds.getWidth() / superRoot.height;
}
}
}
/**
* Computes positions recursively until the leaf nodes are reached.
*/
private void computePositionRecursively(TreeNode entityInfo,
int relativePosition) {
double breadthPosition = relativePosition
+ entityInfo.numOfLeaves / 2.0;
double depthPosition = (entityInfo.depth + 0.5);
switch (direction) {
case TOP_DOWN:
LayoutProperties.setLocation(entityInfo.getNode(), new Point(
breadthPosition * leafSize, depthPosition * layerSize));
break;
case BOTTOM_UP:
LayoutProperties.setLocation(entityInfo.getNode(),
new Point(breadthPosition * leafSize,
bounds.getHeight() - depthPosition * layerSize));
break;
case LEFT_RIGHT:
LayoutProperties.setLocation(entityInfo.getNode(), new Point(
depthPosition * layerSize, breadthPosition * leafSize));
break;
case RIGHT_LEFT:
LayoutProperties.setLocation(entityInfo.getNode(),
new Point(bounds.getWidth() - depthPosition * layerSize,
breadthPosition * leafSize));
break;
}
for (Iterator<TreeNode> iterator = entityInfo.children
.iterator(); iterator.hasNext();) {
TreeNode childInfo = iterator.next();
computePositionRecursively(childInfo, relativePosition);
relativePosition += childInfo.numOfLeaves;
}
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("TreeLayout { direction : ");
switch (direction) {
case BOTTOM_UP:
sb.append("bottom -> top");
break;
case LEFT_RIGHT:
sb.append("left -> right");
break;
case RIGHT_LEFT:
sb.append("right -> left");
break;
case TOP_DOWN:
sb.append("top -> down");
break;
}
sb.append(", resize : " + resize);
sb.append(" }");
// TODO: include node space??
return sb.toString();
}
}