package edu.tufts.vue.layout;
import java.util.*;
import tufts.vue.LWComponent;
import tufts.vue.LWLink;
import tufts.vue.LWMap;
import tufts.vue.LWNode;
import tufts.vue.LWSelection;
public class HierarchicalLayout2 extends HierarchicalLayout {
protected boolean mInverted;
private static final long serialVersionUID = 1L;
protected static final org.apache.log4j.Logger
Log = org.apache.log4j.Logger.getLogger(HierarchicalLayout2.class);
protected static final boolean DEBUG_LOCAL = false;
public HierarchicalLayout2() {
this(false);
}
public HierarchicalLayout2(boolean inverted) {
mInverted = inverted;
}
public void layout(LWSelection selection) throws Exception {
HashMap<String, LWNode> processedNodes = new HashMap<String, LWNode>();
Iterator<LWComponent> iter = selection.iterator();
// Layout the children and parents of each selected node. The selected nodes
// themselves are not moved. If more than one node in a connected graph is selected,
// the first one encountered in the selection iterator will be the one around which the
// graph is layed out.
while (iter.hasNext()) {
LWComponent comp = iter.next();
if (comp instanceof LWNode && !comp.isManagedLocation() && (processedNodes.get(comp.getID()) == null)) {
LWNode node = (LWNode)comp;
if (DEBUG_LOCAL) {
Log.info("Laying out node " + node.getLabel() + ".");
}
Vector<LWNode> nodes = new Vector<LWNode>();
HierarchyLayer nodeLayer = new HierarchyLayer(node);
nodes.add(node);
findChildrenAndParents(nodes, nodeLayer, processedNodes);
if (DEBUG_LOCAL) {
nodeLayer.printLayers();
}
layoutLayers(node, nodeLayer);
nodes.removeAllElements();
nodeLayer.removeAllElements();
}
}
processedNodes.clear();
}
protected void findChildrenAndParents(Vector<LWNode> nodes, HierarchyLayer layer, HashMap<String, LWNode> processedNodes) {
for (LWNode node : nodes) {
List<LWLink> links = node.getLinks();
Iterator<LWLink> linkIter = links.iterator();
Vector<LWNode> children = new Vector<LWNode>(),
parents = new Vector<LWNode>();
processedNodes.put(node.getID(), node);
// Find the node's children (nodes that point to it) and parents (nodes that it points to).
while (linkIter.hasNext()) {
LWLink link = linkIter.next();
LWComponent head = link.getHead(),
tail = link.getTail();
int arrowState = link.getArrowState();
LWNode child = null,
parent = null;
// If the link has one arrow, the arrow points to the child. If the link has zero
// or two arrows, consider the node on the other end of the link to be the child.
if (node.equals(head)) {
if (tail instanceof LWNode) {
if (arrowState == LWLink.ARROW_HEAD) {
parent = (LWNode)tail;
}
else {
child = (LWNode)tail;
}
}
}
else if (node.equals(tail)) {
if (head instanceof LWNode) {
if (arrowState == LWLink.ARROW_TAIL) {
parent = (LWNode)head;
}
else {
child = (LWNode)head;
}
}
}
if (child != null) {
String childID = child.getID();
if (processedNodes.get(childID) == null) {
processedNodes.put(childID, child);
children.add(child);
if (DEBUG_LOCAL) {
Log.info(node.getLabel() + " has child " + child.getLabel() + ".");
}
}
}
else if (parent != null) {
String parentID = parent.getID();
if (processedNodes.get(parentID) == null) {
processedNodes.put(parentID, parent);
parents.add(parent);
if (DEBUG_LOCAL) {
Log.info(node.getLabel() + " has parent " + parent.getLabel() + ".");
}
}
}
}
// Add the children and parents to their respective layers before recursing to the
// higher and lower layers so that siblings are next to each other and aren't
// interspersed with their cousins.
layer.addChildNodes(children);
layer.addParentNodes(parents);
findChildrenAndParents(children, layer.getChildLayer(), processedNodes);
findChildrenAndParents(parents, layer.getParentLayer(), processedNodes);
children.removeAllElements();
parents.removeAllElements();
}
}
protected void layoutLayers(LWNode node, HierarchyLayer layer) {
// Arrange nodes in the layer which includes the selected node.
// The selected node will be left in its current location.
// The other nodes in this layer will be placed to the selected node's right.
float layerCenterX = node.getX() + (layer.getPaddedWidth() / 2),
layerCenterY = node.getY() + (node.getHeight() / 2);
layer.layout(layerCenterX, layerCenterY);
// Arrange nodes in layers below (children);
HierarchyLayer childLayer = layer.getChildLayer();
float childLayerCenterY = layerCenterY,
previousLayerHeight = layer.getHeight(),
invert = (mInverted ? -1 : 1);
while (childLayer != null) {
float childLayerHeight = childLayer.getHeight();
childLayerCenterY += invert * ((previousLayerHeight / 2) + (childLayerHeight / 2 ) +
(2 * Math.min(previousLayerHeight, childLayerHeight)));
childLayer.layout(layerCenterX, childLayerCenterY);
childLayer = childLayer.getChildLayer();
previousLayerHeight = childLayerHeight;
}
// Arrange nodes in layers above (parents).
HierarchyLayer parentLayer = layer.getParentLayer();
float parentLayerCenterY = layerCenterY;
previousLayerHeight = layer.getHeight();
while (parentLayer != null) {
float parentLayerHeight = parentLayer.getHeight();
parentLayerCenterY -= invert * ((previousLayerHeight / 2) + (parentLayerHeight / 2 ) +
(2 * Math.min(previousLayerHeight, parentLayerHeight)));
parentLayer.layout(layerCenterX, parentLayerCenterY);
parentLayer = parentLayer.getParentLayer();
previousLayerHeight = parentLayerHeight;
}
}
class HierarchyLayer {
protected final float HORIZONTAL_PADDING_FRACTION = 2;
HierarchyLayer mChildren = null,
mParents = null;
Vector<LWNode> mNodes = null;
float mWidth = 0,
mHeight = 0;
public HierarchyLayer(LWNode node) {
addNode(node);
}
public void addNode(LWNode node) {
float nodeHeight = node.getHeight();
if (mNodes == null) {
mNodes = new Vector<LWNode>();
}
mNodes.add(node);
mWidth += node.getWidth();
if (nodeHeight > mHeight) {
mHeight = nodeHeight;
}
}
public void addChildNode(LWNode child) {
if (mChildren == null) {
mChildren = new HierarchyLayer(child);
mChildren.mParents = this;
} else {
mChildren.addNode(child);
}
}
public void addParentNode(LWNode parent) {
if (mParents == null) {
mParents = new HierarchyLayer(parent);
mParents.mChildren = this;
} else {
mParents.addNode(parent);
}
}
public void addChildNodes(Vector<LWNode> children) {
for (LWNode child : children) {
addChildNode(child);
}
}
public void addParentNodes(Vector<LWNode> parents) {
for (LWNode parent : parents) {
addParentNode(parent);
}
}
public HierarchyLayer getChildLayer() {
return mChildren;
}
public HierarchyLayer getParentLayer() {
return mParents;
}
public HierarchyLayer getHighestLayer() {
HierarchyLayer result = this;
if (mParents != null) {
result = mParents.getHighestLayer();
}
return result;
}
public float getWidth() {
return mWidth;
}
public float getPaddedWidth() {
int nodeCount = mNodes.size();
float horizontalPadding = (mWidth / HORIZONTAL_PADDING_FRACTION) / nodeCount;
return mWidth + (horizontalPadding * (nodeCount - 1));
}
public float getHeight() {
return mHeight;
}
public void removeAllElements() {
getHighestLayer().removeAllElementsAndChildren();
}
protected void removeAllElementsAndChildren() {
if (mNodes != null) {
mNodes.removeAllElements();
mNodes = null;
}
mParents = null;
if (mChildren != null) {
mChildren.removeAllElementsAndChildren();
mChildren = null;
}
}
public void layout(float centerX, float centerY) {
int nodeCount = mNodes.size();
float x = centerX - (getPaddedWidth() / 2),
horizontalPadding = (mWidth / HORIZONTAL_PADDING_FRACTION) / nodeCount;
for (LWNode node : mNodes) {
float y = centerY - (node.getHeight() / 2);
node.setLocation(x, y);
x += node.getWidth() + horizontalPadding;
}
}
public void printLayers() {
getHighestLayer().printLayerAndChildren(1);
}
protected void printLayerAndChildren(int level) {
System.out.print(level + " (width " + mWidth + ", height " + mHeight + "): ");
for (int index = 0; index < mNodes.size(); index++) {
if (index > 0) {
System.out.print(", ");
}
System.out.print(mNodes.elementAt(index).getLabel());
}
System.out.println();
if (mChildren != null) {
mChildren.printLayerAndChildren(level + 1);
}
}
}
}