/******************************************************************************* * 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; } }