/*
* Copyright 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.devtools.depan.eclipse.visualization.layout;
import com.google.devtools.depan.graph.api.DirectedRelationFinder;
import com.google.devtools.depan.model.GraphModel;
import com.google.devtools.depan.model.GraphNode;
import com.google.devtools.depan.view.HierarchicalTreeModel;
import com.google.devtools.depan.view.SuccessorEdges;
import com.google.devtools.depan.view.TreeModel;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* @author <a href='mailto:leeca@google.com'>Lee Carver </a>
*/
public abstract class HierarchicalLayoutTool {
/** Protection from loops */
protected transient Set<GraphNode> allreadyDone = Sets.newHashSet();
/** Computer hierarchical data for the nodes. */
private TreeModel treeData;
/**
* Build the layout tool from a TreeModel.
*
* @param treeData source of hierarchy data.
*/
public HierarchicalLayoutTool(TreeModel treeData) {
this.treeData = treeData;
}
/**
* Build the layout tool from GraphModel and a set of relations that
* define the hierarchy.
*
* @param layouGraph set of nodes to layout
* @param relations set of relations that define the hierarchy
*/
public HierarchicalLayoutTool(
GraphModel layoutGraph, DirectedRelationFinder relations) {
this(createTreeModel(layoutGraph, relations));
}
public static TreeModel createTreeModel(
GraphModel layoutGraph, DirectedRelationFinder relations) {
return new HierarchicalTreeModel(
layoutGraph.computeSuccessorHierarchy(relations));
}
/**
* Assign positions to all nodes in the Tool's graph.
* This method determines the roots of the current tree data,
* and then assigns positions in a depth first traversal of the tree data.
*/
public void layoutTree() {
Collection<GraphNode> roots = treeData.computeRoots();
// If there are multiple roots, make room for an implicit root above them
int level = getRootLevel(roots);
for (GraphNode node : roots) {
assignChildren(node, level);
}
}
/**
* Provide the starting level to use for the given set of roots.
* In radial displays, it is best to start at level 1 if there are more
* then one root.
*
* @param roots collection of nodes that define the roots of the
* current hierarchy.
* @return starting level to use for these roots.
*/
protected abstract int getRootLevel(Collection<GraphNode> roots);
/**
* Recursively assign the position for the given node and all of
* it's descendants. Through the use of an alreadyDone lookup set,
* loops and joins in the tree data are prevented.
*
* @param node GraphNode to position, along with its descendents.
* @param level hierarchical level ("depth") to place node.
*/
private void assignChildren(GraphNode root, int level) {
// Don't try to place an already located node.
if (allreadyDone.contains(root)) {
return;
}
allreadyDone.add(root);
int nextLevel = level + 1;
int childLeft = getCurrOffset(nextLevel);
for (GraphNode node : orderChildren(root)) {
assignChildren(node, nextLevel);
}
// If there were any children, try to center this node above them
int childRight = getCurrOffset(nextLevel);
if (childLeft != childRight) {
assignNode(root, level, (childLeft + childRight) / 2);
}
// With no children, assign to next leaf location, and bump it.
else {
assignNode(root, level, getCurrOffset(level));
incrCurrOffset(level);
}
}
/**
* Provide the current offset to use for a node at the indicated level.
*
* @param level hierarchical level (depth) for the current offset
* @return offset for placement of node.
*/
protected abstract int getCurrOffset(int level);
/**
* Increment the current offset at the provide level, indicating that
* the current offset has been consumed by some node placement.
*
* @param level hierarchical level (depth) for the current offset
*/
protected abstract void incrCurrOffset(int level);
/**
* Assign the node to the provided level and offset
* @param node GraphNode to place
* @param level hierarchical level (depth) for the current offset
* @param offset "horizontal" position of node in graph
*/
protected abstract void assignNode(GraphNode node, int level, int offset);
/**
* Debugging support to display assignments for nodes.
*/
protected void logAssignNode(GraphNode node, int level, int offset) {
System.err.println("[" + level + ", " + offset + "]: " + node);
}
/**
* Provide the current nodes set of children in a principled order.
* <p>
* The organic ordering of TreeModel.getSuccessors() has little meaning,
* and can appear to be pretty random. Various sorting strategies are
* feasible: alphabetic, length of label, etc.
* <p>
* In this implementation, all leaf children are grouped at the front of
* the list, with the remaining nodes at the end. This roughly approximates
* the view that many Filers (e.g. Package Explorer) use, with leaf files
* and directories partitioned from each other.
*
* @param root node with children
* @return Collection of children in desired processing order
*/
private Collection<GraphNode> orderChildren(GraphNode root) {
List<GraphNode> leafs = Lists.newArrayList();
List<GraphNode> inners = Lists.newArrayList();
for (GraphNode node : treeData.getSuccessorNodes(root)) {
// Don't include nodes that are already placed.
if (allreadyDone.contains(node)) {
continue;
}
SuccessorEdges successors = treeData.getSuccessors(node);
if (successors.hasSuccessors()) {
inners.add(node);
}
else {
leafs.add(node);
}
}
leafs.addAll(inners);
return leafs;
}
/**
* Utility function to create a valid Point2D for Jung placement from
* integer level and offset values.
*
* @param level level for node
* @param offset offset for node
* @return Point2D taht corresponds to level and offset
*/
protected static Point2D makePoint2D(int level, int offset) {
Point2D result = new Point2D.Float(level, offset);
System.out.println("Point is " + result);
return result;
}
/**
* Provide a planar implementation of the basic hierarchical layout tool.
* In an early version, that was an alternative that assigned offsets for
* each level separately. While that was abandoned (radial did not need it),
* other strategies for offset generation are feasible.
*/
public static class Planar extends HierarchicalLayoutTool {
/** Each leaf node is assigned a new position. */
private int leafOffset = 0;
/**
* Create a planar layout tool for the given graph and hierarchy.
*
* @param layouGraph set of nodes to layout
* @param relations set of relations that define the hierarchy
*/
public Planar(GraphModel layoutGraph, DirectedRelationFinder relations) {
super(layoutGraph, relations);
}
/**
* Expose the total leaf count.
*
* @return the leafOffset
*/
public int getLeafCount() {
return leafOffset;
}
/**
* {@inheritDoc}
* <p>
* For the planar hierarchical layout, all roots are at level zero.
*/
@Override
protected int getRootLevel(Collection<GraphNode> roots) {
// All planar roots are at the top of the region
return 0;
}
@Override
protected void assignNode(GraphNode node, int level, int offset) {
// TODO Auto-generated method stub
}
/**
* {@inheritDoc}
* <p>
* In this implementation, the offset is always the next leaf position,
* regardless of the level.
*/
@Override
protected int getCurrOffset(int level) {
return getLeafCount();
}
/**
* {@inheritDoc}
* <p>
* In this implementation, simply bump the leaf count by one.
* This indicates that that leaf position is no longer available.
*/
@Override
protected void incrCurrOffset(int level) {
leafOffset++;
}
}
}