/*
* RadialTreeLayout.java
*
* Copyright (C) 2006-2014 Andrew Rambaut
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package figtree.treeviewer.treelayouts;
import jebl.evolution.graphs.Graph;
import jebl.evolution.graphs.Node;
import jebl.evolution.trees.RootedTree;
import java.awt.*;
import java.awt.geom.*;
import java.util.List;
/**
* @author Andrew Rambaut
* @version $Id$
*
* $HeadURL$
*
* $LastChangedBy$
* $LastChangedDate$
* $LastChangedRevision$
*/
public class RadialTreeLayout extends AbstractTreeLayout {
private double spread = 0.0;
public AxisType getXAxisType() {
return AxisType.CONTINUOUS;
}
public AxisType getYAxisType() {
return AxisType.CONTINUOUS;
}
public boolean isShowingRootBranch() {
return false;
}
public double getTotalRootLength() {
return 0.0;
}
public void setFishEye(double fishEye) {
// do nothing
}
public void setPointOfInterest(double x, double y) {
// do nothing
}
public boolean isShowingColouring() {
return (branchColouringAttribute != null);
}
public boolean maintainAspectRatio() {
return true;
}
public double getHeightOfPoint(Point2D point) {
throw new UnsupportedOperationException("Method getHeightOfPoint() is not supported in this TreeLayout");
}
public Line2D getAxisLine(double height) {
throw new UnsupportedOperationException("Method getHeightLine() is not supported in this TreeLayout");
}
public Shape getHeightArea(double height1, double height2) {
throw new UnsupportedOperationException("Method getHeightArea() is not supported in this TreeLayout");
}
public double getSpread() {
return spread;
}
public void setSpread(double spread) {
this.spread = spread;
fireTreeLayoutChanged();
}
public void layout(RootedTree tree, TreeLayoutCache cache) {
cache.clear();
try {
final Node root = tree.getRootNode();
constructNode(tree, root, 0.0, Math.PI * 2, 0.0, 0.0, 0.0, cache);
// Haven't been able to make these look nice....
// constructNodeAreas(tree, root, new Area(), cache);
} catch (Graph.NoEdgeException e) {
e.printStackTrace();
}
}
private Point2D constructNode(RootedTree tree, Node node,
double angleStart, double angleFinish, double xPosition,
double yPosition, double length,
TreeLayoutCache cache) throws Graph.NoEdgeException {
final double branchAngle = (angleStart + angleFinish) / 2.0;
final double directionX = Math.cos(branchAngle);
final double directionY = Math.sin(branchAngle);
Point2D nodePoint = new Point2D.Double(xPosition + (length * directionX), yPosition + (length * directionY));
// System.out.println("Node: " + Utils.DEBUGsubTreeRep(tree, node) + " at " + nodePoint);
if (!tree.isExternal(node)) {
// Not too clear how to do hilighting for radial trees so leave it out...
// if (hilightAttributeName != null && node.getAttribute(hilightAttributeName) != null) {
// constructHilight(tree, node, angleStart, angleFinish, xPosition, yPosition, length, cache);
// }
List<Node> children = tree.getChildren(node);
int[] leafCounts = new int[children.size()];
int sumLeafCount = 0;
int i = 0;
for (Node child : children) {
leafCounts[i] = jebl.evolution.trees.Utils.getExternalNodeCount(tree, child);
sumLeafCount += leafCounts[i];
i++;
}
double span = (angleFinish - angleStart);
if (!tree.isRoot(node)) {
span *= 1.0 + (spread / 10.0);
angleStart = branchAngle - (span / 2.0);
angleFinish = branchAngle + (span / 2.0);
}
double a2 = angleStart;
boolean rotate = false;
if (node.getAttribute("!rotate") != null &&
((Boolean)node.getAttribute("!rotate"))) {
rotate = true;
}
for(i = 0; i < children.size(); ++i) {
int index = i;
if (rotate) {
index = children.size() - i - 1;
}
Node child = children.get(index);
final double childLength = tree.getLength(child);
double a1 = a2;
a2 = a1 + (span * leafCounts[index] / sumLeafCount);
Point2D childPoint = constructNode(tree, child, a1, a2,
nodePoint.getX(), nodePoint.getY(), childLength, cache);
Line2D branchLine = new Line2D.Double(
childPoint.getX(), childPoint.getY(),
nodePoint.getX(), nodePoint.getY()
);
Object[] colouring = null;
if (branchColouringAttribute != null) {
colouring = (Object[])child.getAttribute(branchColouringAttribute);
}
if (colouring != null) {
// If there is a colouring, then we break the path up into
// segments. This should allow use to iterate along the segments
// and colour them as we draw them.
float nodeHeight = (float) tree.getHeight(node);
float childHeight = (float) tree.getHeight(child);
float x1 = (float)childPoint.getX();
float y1 = (float)childPoint.getY();
float x0 = (float)nodePoint.getX();
float y0 = (float)nodePoint.getY();
GeneralPath branchPath = new GeneralPath();
// to help this, we are going to draw the branch backwards
branchPath.moveTo(x1, y1);
float interval = 0.0F;
for (int j = 0; j < colouring.length - 1; j+=2) {
// float height = ((Number)colouring[j+1]).floatValue();
// float p = (height - childHeight) / (nodeHeight - childHeight);
interval += ((Number)colouring[j+1]).floatValue();
float p = interval / (nodeHeight - childHeight);
float x = x1 + ((x0 - x1) * p);
float y = y1 + ((y0 - y1) * p);
branchPath.lineTo(x, y);
}
branchPath.lineTo(x0, y0);
// add the branchPath to the map of branch paths
cache.branchPaths.put(child, branchPath);
} else {
// add the branchLine to the map of branch paths
cache.branchPaths.put(child, branchLine);
}
cache.branchLabelPaths.put(child, (Line2D)branchLine.clone());
}
Point2D nodeLabelPoint = new Point2D.Double(xPosition + ((length + 1.0) * directionX),
yPosition + ((length + 1.0) * directionY));
Line2D nodeLabelPath = new Line2D.Double(nodePoint, nodeLabelPoint);
cache.nodeLabelPaths.put(node, nodeLabelPath);
} else {
Point2D taxonPoint = new Point2D.Double(xPosition + ((length + 1.0) * directionX),
yPosition + ((length + 1.0) * directionY));
Line2D taxonLabelPath = new Line2D.Double(nodePoint, taxonPoint);
cache.tipLabelPaths.put(node, taxonLabelPath);
}
Point2D nodeShapePoint = new Point2D.Double(xPosition + ((length - 1.0) * directionX),
yPosition + ((length - 1.0) * directionY));
Line2D nodeShapePath = new Line2D.Double(nodePoint, nodeShapePoint);
cache.nodeShapePaths.put(node, nodeShapePath);
// add the node point to the map of node points
cache.nodePoints.put(node, nodePoint);
return nodePoint;
}
private void constructNodeAreas(final RootedTree tree, final Node node, final Area parentNodeArea, TreeLayoutCache cache) {
if (!tree.isExternal(node)) {
List<Node> children = tree.getChildren(node);
boolean rotate = false;
if (node.getAttribute("!rotate") != null &&
((Boolean)node.getAttribute("!rotate"))) {
rotate = true;
}
int index = (rotate ? children.size() - 1 : 0);
Node child1 = children.get(index);
Area childArea1 = new Area();
constructNodeAreas(tree, child1, childArea1, cache);
index = (rotate ? 0 : children.size() - 1);
Node child2 = children.get(index);
Area childArea2 = new Area();
constructNodeAreas(tree, child2, childArea2, cache);
GeneralPath nodePath = new GeneralPath();
Line2D line1 = (Line2D)cache.getBranchPath(child1);
Line2D line2 = (Line2D)cache.getBranchPath(child2);
nodePath.moveTo(line2.getX1(), line2.getY1());
nodePath.lineTo(line2.getX2(), line2.getY2());
nodePath.lineTo(line1.getX1(), line1.getY1());
nodePath.closePath();
Area nodeArea = new Area(nodePath);
parentNodeArea.add(nodeArea);
parentNodeArea.add(childArea1);
parentNodeArea.add(childArea2);
nodeArea.subtract(childArea1);
nodeArea.subtract(childArea2);
cache.nodeAreas.put(node, nodeArea);
}
}
private void constructHilight(RootedTree tree, Node node, double angleStart, double angleFinish, double xPosition,
double yPosition, double length, TreeLayoutCache cache) {
Object[] values = (Object[])node.getAttribute(hilightAttributeName);
int tipCount = (Integer)values[0];
double tipHeight = (Double)values[1];
double height = tree.getHeight(node);
GeneralPath hilightShape = new GeneralPath();
final double branchAngle = (angleStart + angleFinish) / 2.0;
float x0 = (float)(xPosition + (0.5 * length * Math.cos(branchAngle)));
float y0 = (float)(yPosition + (0.5 * length * Math.sin(branchAngle)));
float x1 = (float)(x0 + (tipHeight * Math.cos(angleStart)));
float y1 = (float)(y0 + (tipHeight * Math.sin(angleStart)));
float x2 = (float)(x0 + (tipHeight * Math.cos(angleFinish)));
float y2 = (float)(y0 + (tipHeight * Math.sin(angleFinish)));
hilightShape.moveTo(x0, y0);
hilightShape.lineTo(x1, y1);
hilightShape.lineTo(x2, y2);
hilightShape.lineTo(x0, y0);
hilightShape.closePath();
// add the collapsedShape to the map of branch paths
cache.hilightNodes.add(node);
cache.hilightShapes.put(node, hilightShape);
}
}