/** * Copyright (c) 2009, iPlant Collaborative, Texas Advanced Computing Center This software is licensed * under the CC-GNU GPL version 2.0 or later. License: http://creativecommons.org/licenses/GPL/2.0/ */ package org.iplantc.phyloviewer.shared.layout; import java.util.Set; import java.util.Vector; import org.iplantc.phyloviewer.shared.math.Box2D; import org.iplantc.phyloviewer.shared.math.Vector2; import org.iplantc.phyloviewer.shared.model.INode; import org.iplantc.phyloviewer.shared.model.ITree; /** * This isn't really a cladogram layout anymore. It's sort of a "tree-space" layout that can be * transformed into layout information for other rendering types (ie Circular). I think this will allow * us to use one layout algorithm for multiple renderers. (at least if the layout is rooted, not sure * about unrooted.) * * @author adamkubach * */ public class LayoutCladogram implements ILayoutData { private double xCanvasSize = 0.8; // Leave room for taxon labels. private double yCanvasSize = 1.0; int maximumLeafDepth = 0; Vector<Double> xPositions = null; double yLeafSpacing = 0; double currentY = 0; boolean useBranchLengths = false; double maximumDistanceToLeaf = 0.0; LayoutStorage storage = new LayoutStorage(); public LayoutCladogram(double xCanvasSize, double yCanvasSize) { this.xCanvasSize = xCanvasSize; this.yCanvasSize = yCanvasSize; } public LayoutCladogram() { } public void layout(ITree tree) { if(tree == null) { return; } INode root = tree.getRootNode(); if(root == null) { return; } // Allocate enough room for our nodes. int numberOfNodes = tree.getNumberOfNodes(); init(numberOfNodes); // Figure out how much space we will need between leaf nodes. int numLeaves = root.getNumberOfLeafNodes(); yLeafSpacing = yCanvasSize / numLeaves; currentY = yCanvasSize - (yLeafSpacing / 2.0); maximumLeafDepth = root.findMaximumDepthToLeaf(); // Calculate the x positions. int numXPositions = maximumLeafDepth + 1; xPositions = new Vector<Double>(numXPositions); for(int i = 0;i < numXPositions;++i) { double ratio = (double)i / maximumLeafDepth; xPositions.add(i, ratio * xCanvasSize); } maximumDistanceToLeaf = root.findMaximumDistanceToLeaf(); this.layoutNode(root, 0, 0.0); } public void init(int numberOfNodes) { storage.init(numberOfNodes); } private int layoutNode(INode node, int depth, double distanceFromRoot) { // Create empty bounding box and vector. Box2D bbox = new Box2D(); Vector2 position = new Vector2(); this.setBoundingBox(node, bbox); this.setPosition(node, position); int numChildren = node.getNumberOfChildren(); int maxChildHeight = -1; if(0 == numChildren) { position.setY(currentY); currentY -= yLeafSpacing; } else { double sumChildrenY = 0.0; for(int childIndex = 0;childIndex < numChildren;++childIndex) { INode childNode = node.getChild(childIndex); // Layout the children. int height = this.layoutNode(childNode, depth + 1, distanceFromRoot + getBranchLength(node)); maxChildHeight = Math.max(maxChildHeight, height); sumChildrenY += getPosition(childNode).getY(); bbox.expandBy(getBoundingBox(childNode)); bbox.expandBy(getPosition(childNode)); } // Set our position. position.setY(sumChildrenY / numChildren); } int myHeight = maxChildHeight + 1; double xPosition = 0.0; if(useBranchLengths && maximumDistanceToLeaf != 0.0) { xPosition = ((distanceFromRoot + getBranchLength(node)) / maximumDistanceToLeaf) * xCanvasSize; } else { xPosition = xPositions.get(maximumLeafDepth - myHeight); } position.setX(xPosition); double halfYLeafSpacing = yLeafSpacing / 2.0; bbox.expandBy(new Vector2(Math.max(position.getX() - halfYLeafSpacing, 0.0), position.getY() - halfYLeafSpacing)); bbox.expandBy(new Vector2(position.getX(), position.getY() + halfYLeafSpacing)); return myHeight; } private double getBranchLength(INode node) { return node.getBranchLength() != null ? node.getBranchLength() : 0.0; } protected void setBoundingBox(INode node, Box2D box2d) { storage.setBoundingBox(node, box2d); } protected void setPosition(INode node, Vector2 vector2) { storage.setPosition(node, vector2); } @Override public Box2D getBoundingBox(INode node) { return this.getBoundingBox(node.getId()); } @Override public Box2D getBoundingBox(int nodeId) { return storage.getBoundingBox(nodeId); } @Override public Vector2 getPosition(INode node) { return storage.getPosition(node); } public Vector2 getPosition(Integer key) { return storage.getPosition(key); } @Override public boolean containsNode(INode node) { return storage.containsNode(node); } /** * Gets the keys present in the HashMap * * @return Set of integers that corresponds to the node id's contained in the layout. */ public Set<Integer> keySet() { return storage.keySet(); } public boolean isUseBranchLengths() { return useBranchLengths; } public void setUseBranchLengths(boolean useBranchLengths) { this.useBranchLengths = useBranchLengths; } }