| /******************************************************************************* |
| * Copyright 2005, CHISEL Group, University of Victoria, Victoria, BC, Canada. |
| * 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: The Chisel Group, University of Victoria |
| *******************************************************************************/ |
| package org.eclipse.zest.layouts.algorithms; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.zest.layouts.LayoutStyles; |
| import org.eclipse.zest.layouts.dataStructures.DisplayIndependentRectangle; |
| import org.eclipse.zest.layouts.dataStructures.InternalNode; |
| import org.eclipse.zest.layouts.dataStructures.InternalRelationship; |
| import org.eclipse.zest.layouts.exampleStructures.SimpleRelationship; |
| |
| /** |
| * The TreeLayoutAlgorithm class implements a simple algorithm to |
| * arrange graph nodes in a layered vertical tree-like layout. |
| * |
| * This is by no means an efficiently coded algorithm. |
| * |
| * @version 2.0 |
| * @author Casey Best and Rob Lintern (version 1.0 by Jingwei Wu) |
| */ |
| public class TreeLayoutAlgorithm extends AbstractLayoutAlgorithm { |
| |
| private final static double DEFAULT_WEIGHT = 0; |
| private final static boolean DEFAULT_MARKED = false; |
| |
| private final static boolean AS_DESTINATION = false; |
| private final static boolean AS_SOURCE = true; |
| |
| private final static int NUM_DESCENDENTS_INDEX = 0; |
| private final static int NUM_LEVELS_INDEX = 1; |
| |
| private ArrayList treeRoots; |
| |
| private double boundsX; |
| private double boundsY; |
| private double boundsWidth; |
| private double boundsHeight; |
| private DisplayIndependentRectangle layoutBounds = null; |
| |
| private List[] parentLists; |
| private List[] childrenLists; |
| private double[] weights; |
| private boolean[] markedArr; |
| |
| ///////////////////////////////////////////////////////////////////////// |
| ///// Constructors ///// |
| ///////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Constructs a new TreeLayoutAlgorithm object. |
| */ |
| public TreeLayoutAlgorithm(int styles) { |
| super(styles); |
| } |
| |
| /** |
| * Tree layout algorithm Constructor with NO Style |
| * |
| */ |
| public TreeLayoutAlgorithm() { |
| this(LayoutStyles.NONE); |
| } |
| |
| ///////////////////////////////////////////////////////////////////////// |
| ///// Public Methods ///// |
| ///////////////////////////////////////////////////////////////////////// |
| |
| public void setLayoutArea(double x, double y, double width, double height) { |
| throw new RuntimeException(); |
| } |
| |
| protected int getCurrentLayoutStep() { |
| // TODO Auto-generated method stub |
| return 0; |
| } |
| |
| protected int getTotalNumberOfLayoutSteps() { |
| return 4; |
| } |
| |
| /** |
| * Executes this TreeLayoutAlgorithm layout algorithm by referencing the |
| * data stored in the repository system. Once done, the result |
| * will be saved to the data repository. |
| * |
| * @param entitiesToLayout Apply the algorithm to these entities |
| * @param relationshipsToConsider Only consider these relationships when applying the algorithm. |
| * @param boundsX The left side of the bounds in which the layout can place the entities. |
| * @param boundsY The top side of the bounds in which the layout can place the entities. |
| * @param boundsWidth The width of the bounds in which the layout can place the entities. |
| * @param boundsHeight The height of the bounds in which the layout can place the entities. |
| * @throws RuntimeException Thrown if entitiesToLayout doesn't contain all of the endpoints for each relationship in relationshipsToConsider |
| */ |
| protected void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) { |
| // Filter unwanted entities and relationships |
| //super.applyLayout (entitiesToLayout, relationshipsToConsider, boundsX, boundsY, boundsWidth, boundsHeight); |
| |
| parentLists = new List[entitiesToLayout.length]; |
| childrenLists = new List[entitiesToLayout.length]; |
| weights = new double[entitiesToLayout.length]; |
| markedArr = new boolean[entitiesToLayout.length]; |
| for (int i = 0; i < entitiesToLayout.length; i++) { |
| parentLists[i] = new ArrayList(); |
| childrenLists[i] = new ArrayList(); |
| weights[i] = DEFAULT_WEIGHT; |
| markedArr[i] = DEFAULT_MARKED; |
| } |
| |
| this.boundsHeight = height; |
| this.boundsWidth = width; |
| this.boundsX = x; |
| this.boundsY = y; |
| layoutBounds = new DisplayIndependentRectangle(boundsX, boundsY, boundsWidth, boundsHeight); |
| |
| } |
| |
| protected void applyLayoutInternal(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double boundsX, double boundsY, double boundsWidth, double boundsHeight) { |
| |
| if (entitiesToLayout.length > 0) { |
| int totalProgress = 4; |
| fireProgressEvent(1, totalProgress); |
| |
| //List roots = new ArrayList(); |
| treeRoots = new ArrayList(); |
| buildForest(treeRoots, entitiesToLayout, relationshipsToConsider); |
| fireProgressEvent(2, totalProgress); |
| computePositions(treeRoots, entitiesToLayout); |
| fireProgressEvent(3, totalProgress); |
| defaultFitWithinBounds(entitiesToLayout, layoutBounds); |
| |
| } |
| } |
| |
| protected void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider) { |
| updateLayoutLocations(entitiesToLayout); |
| fireProgressEvent(4, 4); |
| } |
| |
| /** |
| * Returns the last found roots |
| */ |
| public List getRoots() { |
| return treeRoots; |
| } |
| |
| /** |
| * Finds all the relationships in which the node <code>obj<code> |
| * plays the specified <code>role</code>. |
| * @param entity The node that concerns the relations to be found. |
| * @param role The role played by the <code>obj</code>. Its type |
| * must be of <code>ACTOR_ROLE</code> or <code>ACTEE_ROLE</code>. |
| * @see SimpleRelationship |
| */ |
| private Collection findRelationships(Object entity, boolean objectAsSource, InternalRelationship[] relationshipsToConsider) { |
| Collection foundRels = new ArrayList(); |
| for (int i = 0; i < relationshipsToConsider.length; i++) { |
| InternalRelationship rel = relationshipsToConsider[i]; |
| if (objectAsSource && rel.getSource().equals(entity)) { |
| foundRels.add(rel); |
| } else if (!objectAsSource && rel.getDestination().equals(entity)) { |
| foundRels.add(rel); |
| } |
| } |
| return foundRels; |
| } |
| |
| /** |
| * Finds the relation that has the lowest index in the relation |
| * repository in which the node <code>obj<code> plays the specified |
| * <code>role</code>. |
| * @param obj The node that concerns the relations to be found. |
| * @param role The role played by the <code>obj</code>. Its type must |
| * be of <code>ACTOR_ROLE</code> or <code>ACTEE_ROLE</code>. |
| * @see SimpleRelationship |
| * @see SimpleRelationship#ACTOR_ROLE |
| * @see SimpleRelationship#ACTEE_ROLE |
| */ |
| private InternalRelationship findRelationship(Object entity, boolean objectAsSource, InternalRelationship[] relationshipsToConsider) { |
| InternalRelationship relationship = null; |
| for (int i = 0; i < relationshipsToConsider.length && relationship == null; i++) { |
| InternalRelationship possibleRel = relationshipsToConsider[i]; |
| if (objectAsSource && possibleRel.getSource().equals(entity)) { |
| relationship = possibleRel; |
| } else if (!objectAsSource && possibleRel.getDestination().equals(entity)) { |
| relationship = possibleRel; |
| } |
| } |
| return relationship; |
| } |
| |
| ///////////////////////////////////////////////////////////////////////// |
| ///// Private Methods ///// |
| ///////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Builds the tree forest that is used to calculate positions |
| * for each node in this TreeLayoutAlgorithm. |
| */ |
| private void buildForest(List roots, InternalNode[] entities, InternalRelationship[] relationships) { |
| List unplacedEntities = new ArrayList(Arrays.asList(entities)); |
| buildForestRecursively(roots, unplacedEntities, entities, relationships); |
| } |
| |
| /** |
| * Builds the forest recursively. All entities |
| * will be placed somewhere in the forest. |
| */ |
| private void buildForestRecursively(List roots, List unplacedEntities, InternalNode[] entities, InternalRelationship[] relationships) { |
| if (unplacedEntities.size() == 0) { |
| return; // no more entities to place |
| } |
| |
| // get the first entity in the list of unplaced entities, find its root, and build this root's tree |
| InternalNode layoutEntity = (InternalNode) unplacedEntities.get(0); |
| InternalNode rootEntity = findRootObjectRecursive(layoutEntity, new HashSet(), relationships); |
| int rootEntityIndex = indexOfInternalNode(entities, rootEntity); |
| buildTreeRecursively(rootEntity, rootEntityIndex, 0, entities, relationships); |
| roots.add(rootEntity); |
| |
| // now see which nodes are left to be placed in a tree somewhere |
| List unmarkedCopy = new ArrayList(unplacedEntities); |
| for (Iterator iter = unmarkedCopy.iterator(); iter.hasNext();) { |
| InternalNode tmpEntity = (InternalNode) iter.next(); |
| int tmpEntityIndex = indexOfInternalNode(entities, tmpEntity); |
| boolean isMarked = markedArr[tmpEntityIndex]; |
| if (isMarked) { |
| unplacedEntities.remove(tmpEntity); |
| } |
| } |
| buildForestRecursively(roots, unplacedEntities, entities, relationships); |
| } |
| |
| /** |
| * Finds the root node that can be treated as the root of a tree. |
| * The found root node should be one of the unmarked nodes. |
| */ |
| private InternalNode findRootObjectRecursive(InternalNode currentEntity, Set seenAlready, InternalRelationship[] relationshipsToConsider) { |
| InternalNode rootEntity = null; |
| InternalRelationship rel = findRelationship(currentEntity, AS_DESTINATION, relationshipsToConsider); |
| if (rel == null) { |
| rootEntity = currentEntity; |
| } else { |
| InternalNode parentEntity = rel.getSource(); |
| if (!seenAlready.contains(parentEntity)) { |
| seenAlready.add(parentEntity); |
| rootEntity = findRootObjectRecursive(parentEntity, seenAlready, relationshipsToConsider); |
| } else { |
| rootEntity = currentEntity; |
| } |
| } |
| return rootEntity; |
| } |
| |
| /** |
| * Builds a tree of the passed in entity. |
| * The entity will pass a weight value to all of its children recursively. |
| */ |
| private void buildTreeRecursively(InternalNode layoutEntity, int i, double weight, InternalNode[] entities, final InternalRelationship[] relationships) { |
| // No need to do further computation! |
| if (layoutEntity == null) { |
| return; |
| } |
| |
| // A marked entity means that it has been added to the |
| // forest, and its weight value needs to be modified. |
| if (markedArr[i]) { |
| modifyWeightRecursively(layoutEntity, i, weight, new HashSet(), entities, relationships); |
| return; //No need to do further computation. |
| } |
| |
| // Mark this entity, set its weight value and create a new tree node. |
| markedArr[i] = true; |
| weights[i] = weight; |
| |
| // collect the children of this entity and put them in order |
| Collection rels = findRelationships(layoutEntity, AS_SOURCE, relationships); |
| List children = new ArrayList(); |
| for (Iterator iter = rels.iterator(); iter.hasNext();) { |
| InternalRelationship layoutRel = (InternalRelationship) iter.next(); |
| InternalNode childEntity = layoutRel.getDestination(); |
| children.add(childEntity); |
| } |
| |
| if (comparator != null) { |
| Collections.sort(children, comparator); |
| } else { |
| // sort the children by level, then by number of descendents, then by number of children |
| // TODO: SLOW |
| Collections.sort(children, new Comparator() { |
| public int compare(Object o1, Object o2) { |
| InternalNode node1 = (InternalNode) o1; |
| InternalNode node2 = (InternalNode) o2; |
| int[] numDescendentsAndLevel1 = new int[2]; |
| int[] numDescendentsAndLevel2 = new int[2]; |
| int level1 = numDescendentsAndLevel1[NUM_LEVELS_INDEX]; |
| int level2 = numDescendentsAndLevel2[NUM_LEVELS_INDEX]; |
| if (level1 == level2) { |
| getNumDescendentsAndLevel(node1, relationships, numDescendentsAndLevel1); |
| getNumDescendentsAndLevel(node2, relationships, numDescendentsAndLevel2); |
| int numDescendents1 = numDescendentsAndLevel1[NUM_DESCENDENTS_INDEX]; |
| int numDescendents2 = numDescendentsAndLevel2[NUM_DESCENDENTS_INDEX]; |
| if (numDescendents1 == numDescendents2) { |
| int numChildren1 = getNumChildren(node1, relationships); |
| int numChildren2 = getNumChildren(node1, relationships); |
| return numChildren2 - numChildren1; |
| } else { |
| return numDescendents2 - numDescendents1; |
| } |
| } else { |
| return level2 - level1; |
| } |
| //return getNumChildren(node2, relationships) - getNumChildren(node1, relationships); |
| } |
| }); |
| } |
| |
| // map children to this parent, and vice versa |
| for (Iterator iter = children.iterator(); iter.hasNext();) { |
| InternalNode childEntity = (InternalNode) iter.next(); |
| int childEntityIndex = indexOfInternalNode(entities, childEntity); |
| if (!childrenLists[i].contains(childEntity)) { |
| childrenLists[i].add(childEntity); |
| } |
| if (!parentLists[childEntityIndex].contains(layoutEntity)) { |
| parentLists[childEntityIndex].add(layoutEntity); |
| } |
| } |
| |
| for (Iterator iter = children.iterator(); iter.hasNext();) { |
| InternalNode childEntity = (InternalNode) iter.next(); |
| int childEntityIndex = indexOfInternalNode(entities, childEntity); |
| buildTreeRecursively(childEntity, childEntityIndex, weight + 1, entities, relationships); |
| } |
| } |
| |
| private int getNumChildren(InternalNode layoutEntity, InternalRelationship[] relationships) { |
| return findRelationships(layoutEntity, AS_SOURCE, relationships).size(); |
| } |
| |
| private void getNumDescendentsAndLevel(InternalNode layoutEntity, InternalRelationship[] relationships, int[] numDescendentsAndLevel) { |
| getNumDescendentsAndLevelRecursive(layoutEntity, relationships, new HashSet(), numDescendentsAndLevel, 0); |
| } |
| |
| private void getNumDescendentsAndLevelRecursive(InternalNode layoutEntity, InternalRelationship[] relationships, Set seenAlready, int[] numDescendentsAndLevel, int currentLevel) { |
| if (seenAlready.contains(layoutEntity)) { |
| return; |
| } |
| seenAlready.add(layoutEntity); |
| numDescendentsAndLevel[NUM_LEVELS_INDEX] = Math.max(numDescendentsAndLevel[NUM_LEVELS_INDEX], currentLevel); |
| Collection rels = findRelationships(layoutEntity, AS_SOURCE, relationships); |
| for (Iterator iter = rels.iterator(); iter.hasNext();) { |
| InternalRelationship layoutRel = (InternalRelationship) iter.next(); |
| InternalNode childEntity = layoutRel.getDestination(); |
| numDescendentsAndLevel[NUM_DESCENDENTS_INDEX]++; |
| getNumDescendentsAndLevelRecursive(childEntity, relationships, seenAlready, numDescendentsAndLevel, currentLevel + 1); |
| |
| } |
| } |
| |
| /** |
| * Modifies the weight value of the marked node recursively. |
| */ |
| private void modifyWeightRecursively(InternalNode layoutEntity, int i, double weight, Set descendentsSeenSoFar, InternalNode[] entities, InternalRelationship[] relationships) { |
| // No need to do further computation! |
| if (layoutEntity == null) { |
| return; |
| } |
| |
| if (descendentsSeenSoFar.contains(layoutEntity)) { |
| return; //No need to do further computation. |
| } |
| |
| descendentsSeenSoFar.add(layoutEntity); |
| // No need to do further computation! |
| if (weight < weights[i]) { |
| return; |
| } |
| |
| weights[i] = weight; |
| Collection rels = findRelationships(layoutEntity, AS_SOURCE, relationships); |
| |
| for (Iterator iter = rels.iterator(); iter.hasNext();) { |
| InternalRelationship tmpRel = (InternalRelationship) iter.next(); |
| InternalNode tmpEntity = tmpRel.getDestination(); |
| int tmpEntityIndex = indexOfInternalNode(entities, tmpEntity); |
| modifyWeightRecursively(tmpEntity, tmpEntityIndex, weight + 1, descendentsSeenSoFar, entities, relationships); |
| } |
| } |
| |
| /** |
| * Gets the maxium weight of a tree in the forest of this TreeLayoutAlgorithm. |
| */ |
| private double getMaxiumWeightRecursive(InternalNode layoutEntity, int i, Set seenAlready, InternalNode[] entities) { |
| double result = 0; |
| if (seenAlready.contains(layoutEntity)) { |
| return result; |
| } |
| seenAlready.add(layoutEntity); |
| List children = childrenLists[i]; |
| if (children.isEmpty()) { |
| result = weights[i]; |
| } else { |
| //TODO: SLOW |
| for (Iterator iter = children.iterator(); iter.hasNext();) { |
| InternalNode childEntity = (InternalNode) iter.next(); |
| int childEntityIndex = indexOfInternalNode(entities, childEntity); |
| result = Math.max(result, getMaxiumWeightRecursive(childEntity, childEntityIndex, seenAlready, entities)); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Computes positions for each node in this TreeLayoutAlgorithm by |
| * referencing the forest that holds those nodes. |
| */ |
| private void computePositions(List roots, InternalNode[] entities) { |
| // No need to do further computation! |
| if (roots.size() == 0) { |
| return; |
| } |
| |
| int totalLeafCount = 0; |
| double maxWeight = 0; |
| for (int i = 0; i < roots.size(); i++) { |
| InternalNode rootEntity = (InternalNode) roots.get(i); |
| int rootEntityIndex = indexOfInternalNode(entities, rootEntity); |
| totalLeafCount = totalLeafCount + getNumberOfLeaves(rootEntity, rootEntityIndex, entities); |
| maxWeight = Math.max(maxWeight, getMaxiumWeightRecursive(rootEntity, rootEntityIndex, new HashSet(), entities) + 1.0); |
| } |
| |
| double width = 1.0 / totalLeafCount; |
| double height = 1.0 / maxWeight; |
| |
| int leafCountSoFar = 0; |
| |
| //TODO: SLOW! |
| for (int i = 0; i < roots.size(); i++) { |
| InternalNode rootEntity = (InternalNode) roots.get(i); |
| int rootEntityIndex = indexOfInternalNode(entities, rootEntity); |
| computePositionRecursively(rootEntity, rootEntityIndex, leafCountSoFar, width, height, new HashSet(), entities); |
| leafCountSoFar = leafCountSoFar + getNumberOfLeaves(rootEntity, rootEntityIndex, entities); |
| } |
| } |
| |
| /** |
| * Computes positions recursively until the leaf nodes are reached. |
| */ |
| private void computePositionRecursively(InternalNode layoutEntity, int i, int relativePosition, double width, double height, Set seenAlready, InternalNode[] entities) { |
| if (seenAlready.contains(layoutEntity)) { |
| return; |
| } |
| seenAlready.add(layoutEntity); |
| double level = getLevel(layoutEntity, i, entities); |
| int breadth = getNumberOfLeaves(layoutEntity, i, entities); |
| double absHPosition = relativePosition + breadth / 2.0; |
| double absVPosition = (level + 0.5); |
| |
| double posx = absHPosition * width; |
| double posy = absVPosition * height; |
| double weight = weights[i]; |
| posy = posy + height * (weight - level); |
| layoutEntity.setInternalLocation(posx, posy); |
| |
| int relativeCount = 0; |
| List children = childrenLists[i]; |
| //TODO: Slow |
| for (Iterator iter = children.iterator(); iter.hasNext();) { |
| InternalNode childEntity = (InternalNode) iter.next(); |
| int childEntityIndex = indexOfInternalNode(entities, childEntity); |
| computePositionRecursively(childEntity, childEntityIndex, relativePosition + relativeCount, width, height, seenAlready, entities); |
| relativeCount = relativeCount + getNumberOfLeaves(childEntity, childEntityIndex, entities); |
| } |
| } |
| |
| private int getNumberOfLeaves(InternalNode layoutEntity, int i, InternalNode[] entities) { |
| return getNumberOfLeavesRecursive(layoutEntity, i, new HashSet(), entities); |
| } |
| |
| private int getNumberOfLeavesRecursive(InternalNode layoutEntity, int i, Set seen, InternalNode[] entities) { |
| int numLeaves = 0; |
| List children = childrenLists[i]; |
| if (children.size() == 0) { |
| numLeaves = 1; |
| } else { |
| //TODO: SLOW! |
| for (Iterator iter = children.iterator(); iter.hasNext();) { |
| InternalNode childEntity = (InternalNode) iter.next(); |
| if (!seen.contains(childEntity)) { |
| seen.add(childEntity); |
| int childEntityIndex = indexOfInternalNode(entities, childEntity); |
| numLeaves += getNumberOfLeavesRecursive(childEntity, childEntityIndex, seen, entities); |
| } else { |
| numLeaves = 1; |
| } |
| } |
| } |
| return numLeaves; |
| } |
| |
| private int getLevel(InternalNode layoutEntity, int i, InternalNode[] entities) { |
| return getLevelRecursive(layoutEntity, i, new HashSet(), entities); |
| } |
| |
| private int getLevelRecursive(InternalNode layoutEntity, int i, Set seen, InternalNode[] entities) { |
| if (seen.contains(layoutEntity)) { |
| return 0; |
| } |
| seen.add(layoutEntity); |
| List parents = parentLists[i]; |
| int maxParentLevel = 0; |
| for (Iterator iter = parents.iterator(); iter.hasNext();) { |
| InternalNode parentEntity = (InternalNode) iter.next(); |
| int parentEntityIndex = indexOfInternalNode(entities, parentEntity); |
| int parentLevel = getLevelRecursive(parentEntity, parentEntityIndex, seen, entities) + 1; |
| maxParentLevel = Math.max(maxParentLevel, parentLevel); |
| } |
| return maxParentLevel; |
| } |
| |
| /** |
| * Note: Use this as little as possible! |
| * TODO limit the use of this method |
| * @param nodes |
| * @param nodeToFind |
| * @return |
| */ |
| private int indexOfInternalNode(InternalNode[] nodes, InternalNode nodeToFind) { |
| for (int i = 0; i < nodes.length; i++) { |
| InternalNode node = nodes[i]; |
| if (node.equals(nodeToFind)) { |
| return i; |
| } |
| } |
| throw new RuntimeException("Couldn't find index of internal node: " + nodeToFind); |
| } |
| |
| protected boolean isValidConfiguration(boolean asynchronous, boolean continueous) { |
| if (asynchronous && continueous) { |
| return false; |
| } else if (asynchronous && !continueous) { |
| return true; |
| } else if (!asynchronous && continueous) { |
| return false; |
| } else if (!asynchronous && !continueous) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| } |