/*******************************************************************************
* Copyright (c) 2007, 2008 Gregory Jordan
*
* This file is part of PhyloWidget.
*
* PhyloWidget 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.
*
* PhyloWidget 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
* PhyloWidget. If not, see <http://www.gnu.org/licenses/>.
*/
package org.phylowidget.render;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.andrewberman.sortedlist.SortedXYRangeList;
import org.andrewberman.ui.FontLoader;
import org.andrewberman.ui.Point;
import org.andrewberman.ui.TextField;
import org.andrewberman.ui.UIUtils;
import org.andrewberman.ui.unsorted.BulgeUtil;
import org.jgrapht.event.GraphEdgeChangeEvent;
import org.jgrapht.event.GraphListener;
import org.jgrapht.event.GraphVertexChangeEvent;
import org.phylowidget.PWContext;
import org.phylowidget.PWPlatform;
import org.phylowidget.PhyloTree;
import org.phylowidget.UsefulConstants;
import org.phylowidget.tree.PhyloNode;
import org.phylowidget.tree.RootedTree;
import org.phylowidget.ui.NodeUncollapser;
import processing.core.PConstants;
import processing.core.PFont;
import processing.core.PGraphics;
import processing.core.PGraphicsJava2D;
/**
* The abstract tree renderer class.
*
* @author Greg Jordan
*/
@SuppressWarnings("unchecked")
public class BasicTreeRenderer extends DoubleBuffer implements GraphListener, UsefulConstants
{
float baseStroke;
protected FontLoader fonts;
protected LayoutBase treeLayout = new LayoutUnrooted();
protected OverlapDetector overlap = new OverlapDetector();
protected PhyloNode widestNode;
protected PGraphics canvas;
/**
* These variables are set in the calculateSizes() method during every round
* of rendering. Very important!
*/
protected float colSize;
/**
* Size of the text, as a multiplier relative to normal size.
*/
protected float dFont;
/**
* Radius of the node ellipses.
*/
protected float dotWidth;
protected double dx;
protected double dy;
// protected ArrayList<NodeRange> ranges = new ArrayList<NodeRange>();
/**
* Width of the node label gutter.
*/
protected float biggestAspectRatio = 0;
public static NodeRenderer decorator;
/**
* Leaf nodes in the associated tree.
*/
// protected ArrayList<PhyloNode> leaves = new ArrayList<PhyloNode>();
// protected ArrayList<PhyloNode> sigLeaves = new ArrayList<PhyloNode>();
protected PhyloNode[] leaves = new PhyloNode[1];
protected PhyloNode[] sigLeaves = new PhyloNode[1];
/**
* A data structure to store the rectangular regions of all nodes. Instead
* of drawing all nodes, we retrieve the nodes whose regions intersect with
* the visible rectangle, and then draw. This can significantly improve
* performance when viewing only a portion of a large tree.
*/
protected SortedXYRangeList list = new SortedXYRangeList();
boolean mainRender;
protected boolean needsLayout;
/**
* All nodes in the associated tree.
*/
protected PhyloNode[] nodes = new PhyloNode[1];
// protected HashMap<PhyloNode, NodeRange> nodesToRanges = new HashMap<PhyloNode, NodeRange>();
/**
* These variables are set in the calculateSizes() method during every round
* of rendering. Very important!
*/
// protected float numCols;
/**
* These variables are set in the calculateSizes() method during every round
* of rendering. Very important!
*/
// protected float numRows;
RenderingHints oldRH;
protected Point ptemp = new Point(0, 0);
protected Point ptemp2 = new Point(0, 0);
/**
* Radius of the node ellipses.
*/
// protected float rad;
/**
* The rectangle that defines the area in which this renderer will draw
* itself.
*/
public Rectangle2D.Float rect, screenRect;
/**
* These variables are set in the calculateSizes() method during every round
* of rendering. Very important!
*/
protected float rowSize;
protected double scaleX;
protected double scaleY;
/**
* Styles for rendering the tree.
*/
protected float textSize;
protected int threshold;
Rectangle2D.Float tRect = new Rectangle2D.Float();
/**
* The tree that will be rendered.
*/
protected RootedTree tree;
private boolean fforwardMe;
private float tsf;
PWContext context;
public BasicTreeRenderer(PWContext context)
{
rect = new Rectangle2D.Float(0, 0, 0, 0);
this.context = context;
fonts = new FontLoader(context.getPW());
if (decorator == null)
decorator = new NodeRenderer();
setOptions();
}
float calcRealX(PhyloNode n)
{
return (float) (n.getLayoutX() * scaleX + dx);
}
float calcRealY(PhyloNode n)
{
return (float) (n.getLayoutY() * scaleY + dy);
}
protected void constrainAspectRatio()
{
}
ArrayList<PhyloNode> foundItems = new ArrayList<PhyloNode>();
private Area a;
protected void draw()
{
float minSize = Math.min(rowSize, colSize);
baseStroke = getNormalLineWidth() * context.config().lineWidth;
canvas.noStroke();
canvas.fill(0);
canvas.textFont(fonts.getPFont());
canvas.textAlign(PConstants.LEFT, PConstants.CENTER);
hint();
screenRect = new Rectangle2D.Float(0, 0, canvas.width, canvas.height);
UIUtils.screenToModel(screenRect);
treeLayout.drawScaleX = (float) scaleX;
treeLayout.drawScaleY = (float) scaleY;
/*
* FIRST LOOP: Updating nodes Update all nodes, regardless of
* "threshold" status.
* Also set each node's drawMe flag to FALSE.
*/
a = new Area();
foundItems.clear();
int nodesDrawn = 0;
PhyloNode[] nodesToDraw = new PhyloNode[nodes.length];
Thread.yield();
for (int i = 0; i < nodes.length; i++)
{
Thread.yield();
PhyloNode n = nodes[i];
if (fforwardMe)
n.fforward();
updateNode(n); // GJ 2009-02-15 commented out. Node updates will happen in recalc() method now.
n.drawMe = false;
n.labelWasDrawn = false;
n.drawLineAndNode = false;
n.drawLabel = false;
// n.occluded = false;
n.isWithinScreen = isNodeWithinScreen(n);
n.bulgeFactor = 1;
if (n.found && n.isWithinScreen)
foundItems.add(n);
// GJ 2008-09-03: Add ALWAYS_SHOW nodes to the foundItems list.
if (n.getAnnotation(UsefulConstants.LABEL_ALWAYSSHOW) != null)
foundItems.add(n);
else if (n.getAnnotation(UsefulConstants.LABEL_ALWAYSSHOW_ALT) != null)
foundItems.add(n);
if (nodesDrawn >= context.config().renderThreshold && !context.config().showAllLabels)
continue;
if (!n.isWithinScreen)
continue;
n.drawMe = true;
nodesToDraw[nodesDrawn] = n;
nodesDrawn++;
}
fforwardMe = false;
/*
* THIRD LOOP: Drawing nodes
* - This loop actually does the drawing.
*/
Thread.yield();
for (int i = nodesDrawn - 1; i >= 0; i--)
{
Thread.yield();
PhyloNode n = nodesToDraw[i];
// canvas.fill(100,100);
NodeRenderer.r = this;
n.drawLineAndNode = true;
n.drawLabel = false;
handleNode(n);
// canvas.rect(n.range.loX,n.range.loY,n.range.hiX-n.range.loX,n.range.hiY-n.range.loY);
}
/*
* If we have a hovered node, always draw it.
*/
if (tree instanceof PhyloTree)
{
PhyloTree pt = (PhyloTree) tree;
PhyloNode h = pt.hoveredNode;
if (h != null && pt.containsVertex(h))
{
Point point = new Point(getX(h), getY(h));
float dist = (float) point.distance(mousePt);
float bulgedSize = BulgeUtil.bulge(dist, .7f, 30);
if (tree.isLeaf(h))
{
if (textSize <= 14)
h.bulgeFactor = bulgedSize;
else
h.bulgeFactor = 1f;
}
insertAndReturnOverlap(h);
h.drawLabel = true;
decorator.render(this, h);
h.labelWasDrawn = true;
}
}
/*
* Sort the found items.
*/
// Collections.sort(foundItems, new ZOrderComparator());
/*
* Also always try to draw nodes that are "found".
*/
Thread.yield();
Collections.reverse(foundItems);
for (PhyloNode n : foundItems)
{
Thread.yield();
NodeRange r = n.range;
// GJ 19-09-2008 change: Found nodes will ALWAYS be drawn, regardless of whether they're overlapping something else.
insertAndReturnOverlap(n);
// GJ 22-09-2008 change: Don't render the found nodes quite yet; we want them to show up over the nodes!
}
/*
* Now, go through the significance-sorted list of leaves, drawing and occluding as we go.
*/
Thread.yield();
for (int i = 0; i < sigLeaves.length; i++)
{
Thread.yield();
PhyloNode n = sigLeaves[i];
if (!n.isWithinScreen || n.labelWasDrawn)
continue;
NodeRange r = n.range;
if (insertAndReturnOverlap(n))
continue;
n.drawLabel = true;
decorator.render(this, n);
}
/*
* Now, we can draw the found nodes on top of everything else.
*/
Thread.yield();
for (PhyloNode n : foundItems)
{
Thread.yield();
NodeRange r = n.range;
n.drawLabel = true;
decorator.render(this, n);
n.labelWasDrawn = true;
}
/*
* Finally, unhint the canvas.
*/
unhint();
}
private Polygon tempP = new Polygon();
private final boolean insertAndReturnOverlap(PhyloNode n)
{
// if (!tree.isLeaf(n)) // Do nothing and pretend no overlap for branch nodes.
// return false;
if (context.config().showAllLabels)
return false;
float angle = n.getAngle();
if (angle == 0 || angle % Math.PI / 2 == 0)
{
if (intersectsRect(n, a))
return true;
a.add(new Area(r2d));
} else
{
fillPolygon(n, tempP);
if (intersectsPoly(n, a, tempP))
return true;
a.add(new Area(tempP));
}
return false;
}
Rectangle2D.Float r2d = new Rectangle2D.Float();
static final float POLYMULT = 1000;
private final boolean intersectsPoly(PhyloNode n, Area a, Polygon scratch)
{
// fillPolygon(n, scratch);
int[] xpoints = scratch.xpoints;
int[] ypoints = scratch.ypoints;
for (int i = 0; i < xpoints.length; i++)
{
float x = (float) xpoints[i] / POLYMULT;
float y = (float) ypoints[i] / POLYMULT;
if (a.contains(x, y))
return true;
}
return false;
}
private final boolean intersectsRect(PhyloNode n, Area a)
{
// r2d.setFrame((float)n.getRealX(),(float)n.getRealY(),0,0);
// for (Point2D pt : n.corners)
// {
// r2d.add(pt);
// }
r2d.setFrame(n.range.loX, n.range.loY, n.range.hiX - n.range.loX, n.range.hiY - n.range.loY);
return a.intersects(r2d);
}
private void fillPolygon(PhyloNode n, Polygon p)
{
p.reset();
// Point2D[] points = n.corners;
// for (Point2D pt : points)
// {
// p.addPoint((int) (pt.getX() * POLYMULT), (int) (pt.getY() * POLYMULT));
// }
}
protected void drawBootstrap(PhyloNode n)
{
if (n.isNHX() && context.config().showBootstrapValues)
{
// String boot = n.getAnnotation(BOOTSTRAP);
String boot = null;
if (boot != null)
{
canvas.pushMatrix();
canvas.translate(getX(n), getY(n));
Double value = Double.parseDouble(boot);
float curTextSize = textSize * 0.5f;
canvas.textFont(fonts.getPFont());
canvas.textSize(curTextSize);
canvas.fill(context.config().getTextColor().brighter(100).getRGB());
canvas.textAlign(canvas.RIGHT, canvas.BOTTOM);
// float s = strokeForNode(n) / 2 + rowSize * RenderConstants.labelSpacing;
float s = 0;
canvas.text(boot, -getNodeRadius(), -s);
canvas.popMatrix();
}
} else
{
return;
}
}
protected boolean useOverlapDetector()
{
return true;
}
double clamp(double a, double lo, double hi)
{
if (a <= lo)
return lo;
else if (a >= hi)
return hi;
else
return a;
}
protected float getNormalLineWidth()
{
float min = rowSize * 0.1f;
return min;
// return min * RenderConstants.labelSpacing;
}
public void edgeAdded(GraphEdgeChangeEvent e)
{
needsLayout = true;
}
public void edgeRemoved(GraphEdgeChangeEvent e)
{
needsLayout = true;
}
public float getNodeRadius()
{
return dotWidth / 2f;
}
public float getNodeOffset(PhyloNode n)
{
float w = decorator.nr.render(canvas, n, false, true)[1];
return w;
}
public float getTextSize()
{
return textSize;
}
public RootedTree getTree()
{
return tree;
}
public void fforward()
{
ArrayList ffMe = new ArrayList();
tree.getAll(tree.getRoot(), null, ffMe);
for (int i = 0; i < ffMe.size(); i++)
{
PhyloNode n = (PhyloNode) ffMe.get(i);
n.fforward();
}
}
float getX(PhyloNode n)
{
return n.getX();
// return (float) (n.getX() * scaleX + dx);
}
float getY(PhyloNode n)
{
return n.getY();
// return (float) (n.getY() * scaleY + dy);
}
protected void handleNode(PhyloNode n)
{
if (tree.isLeaf(n))
{
decorator.render(this, n);
// decorator.lineRender.render(canvas, n, true,false);
// decorator.nr.render(canvas, n,true,false);
} else
{
n.drawLabel = true;
if (insertAndReturnOverlap(n))
n.drawLabel = false;
decorator.render(this, n);
// decorator.lineRender.render(canvas, n, true,false);
// decorator.nr.render(canvas, n,true,false);
/*
* If we're a NHX node, then draw the bootstrap (if the config says so).
*/
drawBootstrap(n);
// drawCladeLabelIfNeeded(n);
/*
* Do some extra stuff to clean up the thresholding artifacts.
*/
List l = tree.getChildrenOf(n);
int sz = l.size();
for (int i = 0; i < sz; i++)
{
PhyloNode child = (PhyloNode) l.get(i);
NodeRange r = child.range;
/*
* If this child is thresholded out, then draw a placemark line to its
* earliest or latest leaf node.
*/
if (!child.drawMe && child.isWithinScreen)
{
PhyloNode leaf = null;
if (i == 0)
leaf = (PhyloNode) tree.getFirstLeaf(child);
else if (i == l.size() - 1)
leaf = (PhyloNode) tree.getLastLeaf(child);
else
/*
* If this child is a "middle child", just do nothing.
*/
continue;
// GJ 19-09-08 change: Loop to render from the first / last leaf all the way to the current node.
while (leaf != n)
{
// decorator.lineRender.render(canvas, leaf, true,false);
// decorator.nr.render(canvas, leaf,true,false);
// drawCladeLabelIfNeeded(leaf);
leaf.drawLineAndNode = true;
leaf.drawLabel = false;
decorator.render(this, leaf);
leaf = (PhyloNode) tree.getParentOf(leaf);
}
}
}
}
}
void drawCladeLabelIfNeeded(PhyloNode n)
{
if (tree.isLeaf(n))
return;
if (context.config().showCladeLabels && tree.isLabelSignificant(tree.getLabel(n)))
{
boolean overlap = insertAndReturnOverlap(n);
if (!overlap)
{
// decorator.lr.render(canvas, n, true,false);
}
}
}
void hint()
{
if (UIUtils.isJava2D(canvas))
{
Graphics2D g2 = ((PGraphicsJava2D) canvas).g2;
oldRH = g2.getRenderingHints();
if (context.config().antialias)
{
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
} else
{
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
}
} else
{
if (context.config().antialias)
{
canvas.smooth();
} else
{
canvas.noSmooth();
}
}
// if (textSize > 100)
// {
// g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
// RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
// g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
// RenderingHints.VALUE_ANTIALIAS_OFF);
// }
}
boolean isAnyParentDrawn(PhyloNode n)
{
PhyloNode cur = (PhyloNode) n.getParent();
while (cur != null)
{
if (cur.drawMe)
return true;
cur = (PhyloNode) cur.getParent();
}
return false;
}
Rectangle2D.Float rect1 = new Rectangle2D.Float();
Rectangle2D.Float rect2 = new Rectangle2D.Float();
Rectangle2D.Float rect3 = new Rectangle2D.Float();
protected boolean isNodeWithinScreen(PhyloNode n)
{
/*
* Get this node range and set the rect.
*/
NodeRange r = n.range;
// Rectangle rect1 = new Rectangle();
float EXPAND = 50;
float EXPAND2 = 100;
/*
* Try to get the parental noderange and set it.
*/
PhyloNode p = (PhyloNode) tree.getParentOf(n);
rect1.x = r.loX - EXPAND;
rect1.y = r.loY - EXPAND;
rect1.width = r.hiX - r.loX + EXPAND2;
rect1.height = r.hiY - r.loY + EXPAND2;
if (p == null)
{
/*
* If we're the root node, just intersect this node's rect with the screen.
*/
return rect1.intersects(screenRect);
} else
{
NodeRange r2 = p.range;
/*
* Find the union of ourselves and our parent, and then intersect with screen.
* (This fixes the problem where a node is off the screen but we want its parent-line drawn.
*/
rect2.x = r2.loX - EXPAND;
rect2.y = r2.loY - EXPAND;
rect2.width = r2.hiX - r2.loX + EXPAND2;
rect2.height = r2.hiY - r2.loY + EXPAND2;
Rectangle.union(rect1, rect2, rect3);
return screenRect.intersects(rect3);
}
}
/**
* Updates this renderer's internal representation of the tree. This should
* only be called when the tree is changed.
*/
protected void layout()
{
if (!needsLayout)
return;
// System.out.println("Layout "+System.currentTimeMillis());
needsLayout = false;
ArrayList<PhyloNode> ls = new ArrayList<PhyloNode>();
ArrayList<PhyloNode> ns = new ArrayList<PhyloNode>();
synchronized (this)
{
tree.getAll(tree.getRoot(), ls, ns);
Thread.yield();
leaves = new PhyloNode[ls.size()];
nodes = new PhyloNode[ns.size()];
leaves = ls.toArray(leaves);
nodes = ns.toArray(nodes);
/*
* Sort these nodes by significance (i.e. num of enclosed nodes).
*/
Arrays.sort(nodes, 0, nodes.length, tree.sorter);
Thread.yield();
/*
* Sort the leaves by "leaf" significance (first leaf = least depth to root)
*/
sigLeaves = new PhyloNode[leaves.length];
for (int i = 0; i < leaves.length; i++)
{
sigLeaves[i] = leaves[i];
}
int dir = 1;
if (context.config().prioritizeDistantLabels)
dir = -1;
Arrays.sort(sigLeaves, 0, sigLeaves.length, tree.new DepthToRootComparator(dir));
Thread.yield();
}
/*
* Crate new nodeRange objects for this layout.
*/
synchronized (list)
{
list.clear();
for (int i = 0; i < nodes.length; i++)
{
PhyloNode n = (PhyloNode) nodes[i];
synchronized (n)
{
n.range.render = this;
}
list.insert(n.range, false);
}
list.sortFull();
}
Thread.yield();
/*
* ASSUMPTION: the leaves ArrayList contains a "sorted" view of the
* tree's leaves, i.e. in the correct ordering from top to bottom.
*/
// FontMetrics fm = canvas.g2.getFontMetrics(font.font);
// FontMetrics fm = UIUtils.getMetrics(canvas, font.font, font.size);
for (int i = 0; i < nodes.length; i++)
{
PhyloNode n = nodes[i];
// GJ 2008-10-15: Add a NodeUncollapser if it doesn't exist.
if (tree.isCollapsed(n) && !NodeUncollapser.containsNode(n))
{
tree.collapseNode(n);
}
/*
* If we have NHX annotations, put our species into the colors map.
* This is done within this loop just to save the effort of looping through all
* nodes again during layout.
*/
if (n.isNHX() && context.config().colorSpecies)
{
String tax = n.getAnnotation(TAXON_ID);
if (tax != null)
{
decorator.taxonColorMap.put(tax, null);
} else
{
String spec = n.getAnnotation(SPECIES_NAME);
if (spec != null)
decorator.taxonColorMap.put(spec, null);
}
}
// Graphics2D g2 = ((PGraphicsJava2D) canvas).g2;
// width = (float) fm.getStringBounds(n.getLabel(), g2).getWidth() / 100f;
float width = UIUtils.getTextWidth(canvas, fonts.getPFont(), 100, n.getLabel(), true) / 100f;
n.unitTextWidth = width;
}
Thread.yield();
if (context.config().colorSpecies)
{
decorator.getColorsForSpeciesMap();
}
treeLayout.layout(tree, leaves, nodes);
}
public void layoutTrigger()
{
needsLayout = true;
}
public void nodesInRange(ArrayList arr, Rectangle2D.Float rect)
{
synchronized (list)
{
list.getInRange(arr, rect);
}
}
public void positionText(PhyloNode n, TextField tf)
{
decorator.lr.positionText(this, n, tf);
}
protected void recalc()
{
rowSize = rect.height / leaves.length;
textSize = rowSize * 0.9f;
dotWidth = getNormalLineWidth() * context.config().nodeSize;
scaleX = rect.width;
scaleY = rect.height;
float scale = (float) Math.min(scaleX, scaleY);
scaleX = scaleY = scale;
// If we have few nodes, don't fill it up so much.
if (leaves.length <= 10)
{
float scaleMult = 0.025f;
float scaleFactor = scaleMult + (leaves.length) * ((1 - scaleMult) / 10);
scaleX *= scaleFactor;
scaleY *= scaleFactor;
rowSize *= scaleFactor;
textSize *= scaleFactor;
dotWidth *= scaleFactor;
}
dx = (rect.width - scaleX) / 2;
dy = (rect.height - scaleY) / 2;
dx += rect.getX();
dy += rect.getY();
PFont font = fonts.getPFont();
dFont = (font.ascent() - font.descent()) * textSize / 2;
}
private void updateNodes()
{
int len = nodes.length;
for (int i=0; i < len; i++)
{
PhyloNode n = nodes[i];
updateNode(n);
}
}
private TreeRenderState lastRenderState;
private long nodePositionHash;
public void render(PGraphics canvas, float x, float y, float w, float h, boolean mainRender)
{
this.mainRender = mainRender;
rect.setRect(x, y, w, h);
if (tree == null)
return;
// GJ 2009-02-15
shouldTriggerRepaint = true;
nodePositionHash = 0;
// synchronized (this)
// {
// this.canvas = canvas;
// layout();
// }
// synchronized (tree)
// {
// recalc();
// updateNodes();
// }
// GJ 2009-02-15 : stop re-rendering when the tree ain't changing!!
// TreeRenderState trs = new TreeRenderState(this);
// if (trs.equals(lastRenderState))
// {
//// System.out.println("Nothing new!!!");
// shouldTriggerRepaint = false;
// } else
// {
// lastRenderState = trs;
// }
if (context.config().useDoubleBuffering)
{
drawDoubleBuffered(canvas);
} else
{
synchronized (tree)
{
this.canvas = canvas;
layout();
recalc();
// updateNodes();
draw();
}
}
}
// this.rect.setFrame(x, y, w, h);
// this.canvas = canvas;
//// canvas.background(0,0);
// if (tree == null)
// return;
//// synchronized (this)
//// {
// layout();
// recalc();
// draw();
//// }
// }
public void drawToBuffer(PGraphics g)
{
super.drawToBuffer(g);
this.canvas = g;
g.background(0, 0);
/*
* All operations requiring integrity of the tree structure should synchronize on the tree object!
*/
synchronized (tree)
{
layout();
recalc();
// updateNodes();
draw();
}
this.canvas = null;
}
// public UIRectangle getVisibleRect()
// {
// float rx = (float) dx;
// float ry = (float) (dy + (overhang < 0 ? overhang * textSize : 0));
// float sx = (float) (scaleX + biggestAspectRatio * textSize + dotWidth * 2);
// float sy = (float) (scaleY + Math.abs(overhang * textSize));
// return new UIRectangle(rx, ry, sx, sy);
// }
Point mousePt = new Point();
private float overhang;
public void setMouseLocation(Point pt)
{
mousePt.setLocation(pt);
}
protected void setOptions()
{
}
public void setTree(RootedTree t)
{
if (t == null)
return;
if (tree != null)
{
synchronized (tree)
{
tree.removeGraphListener(this);
tree.dispose();
tree = null;
}
}
synchronized (t)
{
tree = t;
tree.addGraphListener(this);
needsLayout = true;
if (!context.config().animateNewTree)
fforwardMe = true;
}
}
void unhint()
{
// if (textSize > 100 && UIUtils.isJava2D(canvas))
// {
if (UIUtils.isJava2D(canvas))
{
Graphics2D g2 = ((PGraphicsJava2D) canvas).g2;
g2.setRenderingHints(oldRH);
}
// }
}
private Point2D.Float tempPt = new Point2D.Float();
/*
* This is called once per render.
*/
protected void updateNode(PhyloNode n)
{
/*
* Update the node's Tween.
*/
if (mainRender)
n.update();
/*
* Set the node's cached "real" x and y values.
*/
n.setX(calcRealX(n));
n.setY(calcRealY(n));
/*
* Update the nodeRange.
*/
NodeRange r = n.range;
decorator.setCornerPoints(this, n);
if (n.rect.getWidth() == 0)
{
n.rect.setFrame(n.getX(), n.getY(), 0, 0);
}
r.loX = n.rect.x;
r.hiX = n.rect.x + n.rect.width;
r.loY = n.rect.y;
r.hiY = n.rect.y + n.rect.height;
nodePositionHash += n.getX();
nodePositionHash += n.getY();
}
/**
* Notifies that a vertex has been added to the tree.
*
* @param e
* the vertex event.
*/
public void vertexAdded(GraphVertexChangeEvent e)
{
needsLayout = true;
}
/**
* Notifies that a vertex has been removed from the tree.
*
* @param e
* the vertex event.
*/
public void vertexRemoved(GraphVertexChangeEvent e)
{
needsLayout = true;
}
private LayoutBase oldLayout = null;
public void setLayout(LayoutBase layout)
{
this.oldLayout = this.treeLayout;
this.treeLayout = layout;
layoutTrigger();
}
public LayoutBase getLayout()
{
return treeLayout;
}
public void forceLayout()
{
needsLayout = true;
layoutTrigger();
}
static class ZOrderComparator implements Comparator<PhyloNode>
{
public int compare(PhyloNode o1, PhyloNode o2)
{
String z1 = o1.getAnnotation(UsefulConstants.Z_ORDER);
String z2 = o2.getAnnotation(UsefulConstants.Z_ORDER);
if (z1 == null)
return -1;
else if (z2 == null)
return 1;
else
{
int z1i = Integer.parseInt(z1);
int z2i = Integer.parseInt(z2);
if (z1i > z2i)
return 1;
else if (z1i == z2i)
return 0;
else
return -1;
}
}
}
class TreeRenderState
{
public int treeModCount;
public double x;
public double y;
public double zoom;
public long nodePositionHash;
public TreeRenderState(BasicTreeRenderer r)
{
this.treeModCount = r.tree.getModCount();
this.x = r.rect.x;
this.y = r.rect.y;
this.zoom = r.rect.getWidth();
this.nodePositionHash = r.nodePositionHash;
}
public boolean equals(Object o)
{
if (o == null)
return false;
TreeRenderState b = (TreeRenderState) o;
return (b.x == x && b.y == y && b.treeModCount == treeModCount && b.zoom == zoom && b.nodePositionHash == nodePositionHash);
}
}
public FontLoader getFontLoader()
{
return fonts;
}
}