1
package figtree.treeviewer;
3
import jebl.evolution.graphs.Node;
4
import jebl.evolution.graphs.Graph;
5
import jebl.evolution.taxa.Taxon;
6
import jebl.evolution.trees.*;
7
import figtree.treeviewer.decorators.*;
8
import figtree.treeviewer.painters.*;
9
import figtree.treeviewer.treelayouts.*;
13
import java.awt.geom.*;
14
import java.awt.print.*;
16
import java.util.List;
20
* @author Andrew Rambaut
21
* @version $Id: TreePane.java 823 2007-10-28 20:28:47Z stevensh $
23
public class TreePane extends JComponent implements PainterListener, Printable {
24
public enum RootingType {
25
USER_ROOTING("User Selection"),
26
MID_POINT("Midpoint");
27
// LEAST_SQUARES("least squares");
29
RootingType(String name) {
33
public String toString() { return name; }
39
public final static boolean DEBUG_OUTLINE = false;
41
public final String CARTOON_ATTRIBUTE_NAME = "!cartoon";
42
public final String COLLAPSE_ATTRIBUTE_NAME = "!collapse";
43
public final String HILIGHT_ATTRIBUTE_NAME = "!hilight";
48
public RootedTree getTree() {
52
public void setTree(RootedTree tree) {
54
this.originalTree = tree;
55
if (!originalTree.hasLengths()) {
56
transformBranchesOn = true;
67
private void setupTree() {
68
tree = constructTransformedTree(originalTree);
70
recalculateCollapsedNodes();
77
public RootedTree constructTransformedTree(RootedTree sourceTree) {
78
RootedTree newTree = sourceTree;
81
if (rootingType == RootingType.MID_POINT) {
82
newTree = ReRootedTree.rootTreeAtCenter(newTree);
83
} else if (rootingType == RootingType.USER_ROOTING && rootingNode != null) {
84
Node left = newTree.getParent(rootingNode);
86
// rooting length should be [0, 1]
87
double length = newTree.getLength(rootingNode) * rootingLength;
89
newTree = new ReRootedTree(newTree, left, rootingNode, length);
90
} catch (Graph.NoEdgeException e) {
97
if (orderBranchesOn) {
98
newTree = new SortedRootedTree(newTree, branchOrdering);
101
if (transformBranchesOn || !sourceTree.hasLengths()) {
102
newTree = new TransformedRootedTree(newTree, branchTransform);
108
public TreeLayout getTreeLayout() {
112
public TreeLayoutCache getTreeLayoutCache() {
113
return treeLayoutCache;
116
public void setTreeLayout(TreeLayout treeLayout) {
118
this.treeLayout = treeLayout;
120
treeLayout.setCartoonAttributeName(CARTOON_ATTRIBUTE_NAME);
121
treeLayout.setCollapsedAttributeName(COLLAPSE_ATTRIBUTE_NAME);
122
treeLayout.setHilightAttributeName(HILIGHT_ATTRIBUTE_NAME);
123
treeLayout.setBranchColouringAttributeName(branchColouringAttribute);
125
treeLayout.addTreeLayoutListener(new TreeLayoutListener() {
126
public void treeLayoutChanged() {
136
public TimeScale getTimeScale() {
140
public void setTimeScale(TimeScale timeScale) {
141
this.timeScale = timeScale;
146
public boolean isCrosshairShown() {
147
return isCrosshairShown;
150
public void setCrosshairShown(boolean crosshairShown) {
151
isCrosshairShown = crosshairShown;
154
public void setCursorPosition(Point point) {
155
cursorPosition = point;
156
if (cursorPosition != null) {
157
double xPos = (point.getX() - treeBounds.getX()) / treeBounds.getWidth();
158
xPos = (xPos < 0.0 ? 0.0 : xPos > 1.0 ? 1.0 : xPos);
159
double yPos = (point.getY() - treeBounds.getY()) / treeBounds.getHeight();
160
yPos = (yPos < 0.0 ? 0.0 : yPos > 1.0 ? 1.0 : yPos);
161
treeLayout.setPointOfInterest(xPos, yPos);
165
public void midpointRoot() {
167
rootingType = RootingType.MID_POINT;
171
fireSettingsChanged();
174
public void setRootLocation(Node node, double length) {
176
rootingNode = ((ReRootedTree)tree).getSourceNode(node);
180
rootingLength = length;
183
rootingType = RootingType.USER_ROOTING;
187
fireSettingsChanged();
192
public void rotateNode(Node node) {
194
Boolean rotate = (Boolean)node.getAttribute("!rotate");
195
if (rotate != null) {
200
node.setAttribute("!rotate", rotate);
209
public void clearRotation(Node node) {
211
Boolean rotate = (Boolean)node.getAttribute("!rotate");
212
if (rotate != null) {
213
node.removeAttribute("!rotate");
223
public void setBranchDecorator(Decorator branchDecorator) {
224
this.branchDecorator = branchDecorator;
228
public void setBranchColouringDecorator(String branchColouringAttribute, Decorator branchColouringDecorator) {
229
this.branchColouringAttribute = branchColouringAttribute;
230
treeLayout.setBranchColouringAttributeName(branchColouringAttribute);
231
this.branchColouringDecorator = branchColouringDecorator;
235
public void setNodeBackgroundDecorator(Decorator nodeBackgroundDecorator) {
236
this.nodeBackgroundDecorator = nodeBackgroundDecorator;
240
public Rectangle2D getTreeBounds() {
245
* This returns the scaling factor between the graphical image and the branch
246
* lengths of the tree
248
* @return the tree scale
250
public double getTreeScale() {
251
return treeScale / timeScale.getScaleFactor(tree);
255
* Transform a chart co-ordinates into a drawing co-ordinates
257
public double scaleOnAxis(double value) {
258
double height = timeScale.getHeight(value, tree);
259
if (treeLayout.isAxisReversed()) {
260
return treeBounds.getX() + treeBounds.getWidth() - (height * treeScale);
262
return treeBounds.getX() + (height * treeScale);
266
public Shape getAxisLine(double value) {
267
double height = timeScale.getHeight(value, tree);
268
Shape line = treeLayout.getAxisLine(height);
270
return transform.createTransformedShape(line);
276
public ScaleAxis getScaleAxis() {
280
public double getAxisOrigin() {
284
public void setAxisOrigin(double axisOrigin) {
285
this.axisOrigin = axisOrigin;
290
public void setAxisReversed(final boolean isAxisReversed) {
291
treeLayout.setAxisReversed(isAxisReversed);
296
private void setupScaleAxis() {
297
double treeHeight = tree.getHeight(tree.getRootNode()) + treeLayout.getRootLength();
298
double minValue = timeScale.getAge(0.0, tree);
299
double maxValue = timeScale.getAge(treeHeight, tree);
301
if (minValue < maxValue) {
302
if (axisOrigin < minValue) {
303
minValue = axisOrigin;
305
scaleAxis.setRange(minValue, maxValue);
307
if (axisOrigin > minValue) {
308
minValue = axisOrigin;
310
scaleAxis.setRange(maxValue, minValue);
314
public void setRootAge(double rootAge) {
315
double rootLength = timeScale.getHeight(rootAge, tree) - tree.getHeight(tree.getRootNode());
316
treeLayout.setRootLength(rootLength);
321
public double getRootAge() {
322
double treeHeight = tree.getHeight(tree.getRootNode()) + treeLayout.getRootLength();
323
return timeScale.getAge(treeHeight, tree);
326
public double getMajorTickSpacing() {
327
return scaleAxis.getMajorTickSpacing();
330
public double getMinorTickSpacing() {
331
return scaleAxis.getMinorTickSpacing();
334
public void setTickSpacing(double userMajorTickSpacing, double userMinorTickSpacing) {
335
scaleAxis.setManualAxis(userMajorTickSpacing, userMinorTickSpacing);
340
public void setAutomaticScale() {
341
scaleAxis.setAutomatic();
346
public void painterChanged() {
351
public void painterSettingsChanged() {
356
public void attributesChanged() {
361
public BasicStroke getBranchStroke() {
362
return branchLineStroke;
365
public void setBranchStroke(BasicStroke stroke) {
366
branchLineStroke = stroke;
367
float weight = stroke.getLineWidth();
368
selectionStroke = new BasicStroke(Math.max(weight + 4.0F, weight * 1.5F), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
372
public BasicStroke getCalloutStroke() {
373
return calloutStroke;
376
public void setCalloutStroke(BasicStroke calloutStroke) {
377
this.calloutStroke = calloutStroke;
380
public Paint getSelectionPaint() {
381
return selectionPaint;
384
public void setSelectionColor(Color selectionColor) {
385
this.selectionPaint = new Color(
386
selectionColor.getRed(),
387
selectionColor.getGreen(),
388
selectionColor.getBlue(),
392
public boolean isTransformBranchesOn() {
393
return transformBranchesOn;
396
public void setTransformBranchesOn(boolean transformBranchesOn) {
397
this.transformBranchesOn = transformBranchesOn;
401
public TransformedRootedTree.Transform getBranchTransform() {
402
return branchTransform;
405
public void setBranchTransform(TransformedRootedTree.Transform branchTransform) {
406
this.branchTransform = branchTransform;
410
public boolean isOrderBranchesOn() {
411
return orderBranchesOn;
414
public void setOrderBranchesOn(boolean orderBranchesOn) {
415
this.orderBranchesOn = orderBranchesOn;
419
public SortedRootedTree.BranchOrdering getBranchOrdering() {
420
return branchOrdering;
423
public void setBranchOrdering(SortedRootedTree.BranchOrdering branchOrdering) {
424
this.branchOrdering = branchOrdering;
428
public boolean isRootingOn() {
432
public RootingType getRootingType() {
436
public void setRootingOn(boolean rootingOn) {
437
this.isRootingOn = rootingOn;
441
public void setRootingType(RootingType rootingType) {
442
this.rootingType = rootingType;
446
public RootedTree getOriginalTree() {
450
public boolean isShowingTipCallouts() {
451
return showingTipCallouts;
454
public void setShowingTipCallouts(boolean showingTipCallouts) {
455
this.showingTipCallouts = showingTipCallouts;
460
public void setSelectedNode(Node selectedNode) {
461
selectedNodes.clear();
462
selectedTips.clear();
463
addSelectedNode(selectedNode);
466
public void setSelectedTip(Node selectedTip) {
467
selectedNodes.clear();
468
selectedTips.clear();
469
addSelectedTip(selectedTip);
472
public void setSelectedClade(Node selectedNode) {
473
selectedNodes.clear();
474
selectedTips.clear();
475
addSelectedClade(selectedNode);
478
public void setSelectedTips(Node selectedNode) {
479
selectedNodes.clear();
480
selectedTips.clear();
481
addSelectedTips(selectedNode);
484
private boolean canSelectNode(Node selectedNode) {
485
return selectedNode != null;
487
public void addSelectedNode(Node selectedNode) {
488
if ( canSelectNode(selectedNode) ) {
489
selectedNodes.add(selectedNode);
491
fireSelectionChanged();
492
clearSelectionPaths();
496
public void addSelectedTip(Node selectedTip) {
497
if (selectedTip != null) {
501
selectTipsFromSelectedNodes();
502
fireSelectionChanged();
503
clearSelectionPaths();
507
public void addSelectedClade(Node selectedNode) {
508
if ( canSelectNode(selectedNode) ) {
509
addSelectedChildClades(selectedNode);
511
fireSelectionChanged();
512
clearSelectionPaths();
516
private void addSelectedChildClades(Node selectedNode) {
517
selectedNodes.add(selectedNode);
518
for (Node child : tree.getChildren(selectedNode)) {
519
addSelectedChildClades(child);
523
public void addSelectedTips(Node selectedNode) {
524
if (selectedNode != null) {
525
addSelectedChildTips(selectedNode);
527
fireSelectionChanged();
528
clearSelectionPaths();
532
private void addSelectedChildTips(Node selectedNode) {
533
if (tree.isExternal(selectedNode)) {
534
selectedTips.add(selectedNode);
536
for (Node child : tree.getChildren(selectedNode)) {
537
addSelectedChildTips(child);
541
public void selectCladesFromSelectedNodes() {
542
Set<Node> nodes = new HashSet<Node>(selectedNodes);
543
selectedNodes.clear();
544
for (Node node : nodes) {
545
addSelectedClade(node);
547
fireSelectionChanged();
548
clearSelectionPaths();
552
public void selectTipsFromSelectedNodes() {
553
for (Node node : selectedNodes) {
554
addSelectedChildTips(node);
556
selectedNodes.clear();
557
fireSelectionChanged();
558
clearSelectionPaths();
562
public void selectNodesFromSelectedTips() {
563
if (selectedTips.size() > 0) {
564
Node node = RootedTreeUtils.getCommonAncestorNode(tree, selectedTips);
565
addSelectedClade(node);
568
selectedTips.clear();
569
fireSelectionChanged();
570
clearSelectionPaths();
574
public void selectAllTaxa() {
575
selectedTips.addAll(tree.getExternalNodes());
576
fireSelectionChanged();
577
clearSelectionPaths();
581
public void selectAllNodes() {
582
selectedNodes.addAll(tree.getNodes());
583
fireSelectionChanged();
584
clearSelectionPaths();
588
public void clearSelection() {
589
selectedNodes.clear();
590
selectedTips.clear();
591
fireSelectionChanged();
592
clearSelectionPaths();
596
public boolean hasSelection() {
597
return selectedNodes.size() > 0 || selectedTips.size() > 0;
600
public void cartoonSelectedNodes() {
601
cartoonSelectedNodes(tree.getRootNode());
604
private void cartoonSelectedNodes(Node node) {
606
if (!tree.isExternal(node)) {
607
if (selectedNodes.contains(node)) {
608
if (node.getAttribute(CARTOON_ATTRIBUTE_NAME) != null) {
609
node.removeAttribute(CARTOON_ATTRIBUTE_NAME);
611
int tipCount = RootedTreeUtils.getTipCount(tree, node);
612
double height = RootedTreeUtils.getMinTipHeight(tree, node);
613
Object[] values = new Object[] { tipCount, height };
614
node.setAttribute(CARTOON_ATTRIBUTE_NAME, values);
619
for (Node child : tree.getChildren(node)) {
620
cartoonSelectedNodes(child);
626
public void collapseSelectedNodes() {
627
collapseSelectedNodes(tree.getRootNode());
630
private void collapseSelectedNodes(Node node) {
632
if (!tree.isExternal(node)) {
633
if (selectedNodes.contains(node)) {
634
if (node.getAttribute(COLLAPSE_ATTRIBUTE_NAME) != null) {
635
node.removeAttribute(COLLAPSE_ATTRIBUTE_NAME);
637
String tipName = "collapsed";
638
double height = RootedTreeUtils.getMinTipHeight(tree, node);
639
Object[] values = new Object[] { tipName, height };
640
node.setAttribute(COLLAPSE_ATTRIBUTE_NAME, values);
645
for (Node child : tree.getChildren(node)) {
646
collapseSelectedNodes(child);
652
public void hilightSelectedNodes(Color color) {
653
hilightSelectedNodes(tree.getRootNode(), color);
656
private void hilightSelectedNodes(Node node, Color color) {
658
if (!tree.isExternal(node)) {
659
if (selectedNodes.contains(node)) {
660
int tipCount = RootedTreeUtils.getTipCount(tree, node);
661
double height = RootedTreeUtils.getMinTipHeight(tree, node);
662
Object[] values = new Object[] { tipCount, height, color };
663
node.setAttribute(HILIGHT_ATTRIBUTE_NAME, values);
668
for (Node child : tree.getChildren(node)) {
669
hilightSelectedNodes(child, color);
675
public void recalculateCollapsedNodes() {
676
recalculateCollapsedNodes(tree.getRootNode());
679
private void recalculateCollapsedNodes(Node node) {
681
if (!tree.isExternal(node)) {
682
if (selectedNodes.contains(node)) {
683
if (node.getAttribute(CARTOON_ATTRIBUTE_NAME) != null) {
684
int tipCount = RootedTreeUtils.getTipCount(tree, node);
685
double height = RootedTreeUtils.getMinTipHeight(tree, node);
686
Object[] values = new Object[] { tipCount, height };
687
node.setAttribute(CARTOON_ATTRIBUTE_NAME, values);
689
if (node.getAttribute(COLLAPSE_ATTRIBUTE_NAME) != null) {
690
String tipName = "collapsed";
691
double height = RootedTreeUtils.getMinTipHeight(tree, node);
692
Object[] values = new Object[] { tipName, height };
693
node.setAttribute(COLLAPSE_ATTRIBUTE_NAME, values);
695
Object[] oldValues = (Object[])node.getAttribute(HILIGHT_ATTRIBUTE_NAME);
696
if (oldValues != null) {
697
int tipCount = RootedTreeUtils.getTipCount(tree, node);
698
double height = RootedTreeUtils.getMinTipHeight(tree, node);
699
Object[] values = new Object[] { tipCount, height, oldValues[2] };
700
node.setAttribute(HILIGHT_ATTRIBUTE_NAME, values);
705
for (Node child : tree.getChildren(node)) {
706
recalculateCollapsedNodes(child);
712
public void clearCollapsedNodes() {
713
clearSelectedCollapsedNodes(tree.getRootNode());
716
private void clearSelectedCollapsedNodes(Node node) {
718
if (!tree.isExternal(node)) {
719
// Although collapsed nodes could be nested, we don't go
720
// deeper. So one 'clear collapsed' will reveal any nested
722
if (selectedNodes.contains(node)) {
723
if (node.getAttribute(COLLAPSE_ATTRIBUTE_NAME) != null) {
724
node.removeAttribute(COLLAPSE_ATTRIBUTE_NAME);
726
if (node.getAttribute(CARTOON_ATTRIBUTE_NAME) != null) {
727
node.removeAttribute(CARTOON_ATTRIBUTE_NAME);
732
for (Node child : tree.getChildren(node)) {
733
clearSelectedCollapsedNodes(child);
739
public void clearHilightedNodes() {
740
clearSelectedHilightedNodes(tree.getRootNode());
743
private void clearSelectedHilightedNodes(Node node) {
745
if (!tree.isExternal(node)) {
746
if (selectedNodes.contains(node)) {
747
if (node.getAttribute(HILIGHT_ATTRIBUTE_NAME) != null) {
748
node.removeAttribute(HILIGHT_ATTRIBUTE_NAME);
753
for (Node child : tree.getChildren(node)) {
754
clearSelectedHilightedNodes(child);
760
public void rerootOnSelectedBranch() {
762
for (Node selectedNode : selectedNodes) {
763
setRootLocation(selectedNode, 0.5);
764
// root on the first selected branch...
765
// Check for multiple selected branch elsewhere
771
public void clearRooting() {
776
fireSettingsChanged();
779
public void rotateSelectedNode() {
780
for (Node selectedNode : selectedNodes) {
781
rotateNode(selectedNode);
786
public void clearSelectedNodeRotations() {
787
for (Node selectedNode : selectedNodes) {
788
clearRotation(selectedNode);
793
public void annotateSelectedNodes(String name, Object value) {
794
for (Node selectedNode : selectedNodes) {
795
selectedNode.setAttribute(name, value);
800
public void annotateSelectedTips(String name, Object value) {
801
for (Node selectedTip : selectedTips) {
802
Taxon selectedTaxon = tree.getTaxon(selectedTip);
803
// if (selectedTaxon == null) {
804
// throw new IllegalArgumentException("missing taxon?");
806
selectedTaxon.setAttribute(name, value);
811
public void clearSelectedNodeAnnotation(String name) {
812
for (Node selectedNode : selectedNodes) {
813
selectedNode.removeAttribute(name);
818
public void clearSelectedTipAnnotation(String name) {
819
for (Node selectedTip : selectedTips) {
820
Taxon selectedTaxon = tree.getTaxon(selectedTip);
821
selectedTaxon.removeAttribute(name);
827
* Return whether the two axis scales should be maintained
828
* relative to each other
832
public boolean maintainAspectRatio() {
833
return treeLayout.maintainAspectRatio();
836
public void setTipLabelPainter(LabelPainter<Node> tipLabelPainter) {
837
tipLabelPainter.setTreePane(this);
838
if (this.tipLabelPainter != null) {
839
this.tipLabelPainter.removePainterListener(this);
841
this.tipLabelPainter = tipLabelPainter;
842
if (this.tipLabelPainter != null) {
843
this.tipLabelPainter.addPainterListener(this);
849
public LabelPainter<Node> getTipLabelPainter() {
850
return tipLabelPainter;
853
public void setNodeLabelPainter(LabelPainter<Node> nodeLabelPainter) {
854
nodeLabelPainter.setTreePane(this);
855
if (this.nodeLabelPainter != null) {
856
this.nodeLabelPainter.removePainterListener(this);
858
this.nodeLabelPainter = nodeLabelPainter;
859
if (this.nodeLabelPainter != null) {
860
this.nodeLabelPainter.addPainterListener(this);
866
public LabelPainter<Node> getNodeLabelPainter() {
867
return nodeLabelPainter;
870
public void setBranchLabelPainter(LabelPainter<Node> branchLabelPainter) {
871
branchLabelPainter.setTreePane(this);
872
if (this.branchLabelPainter != null) {
873
this.branchLabelPainter.removePainterListener(this);
875
this.branchLabelPainter = branchLabelPainter;
876
if (this.branchLabelPainter != null) {
877
this.branchLabelPainter.addPainterListener(this);
883
public LabelPainter<Node> getBranchLabelPainter() {
884
return branchLabelPainter;
887
public void setNodeBarPainter(NodeBarPainter nodeBarPainter) {
888
nodeBarPainter.setTreePane(this);
889
if (this.nodeBarPainter != null) {
890
this.nodeBarPainter.removePainterListener(this);
892
this.nodeBarPainter = nodeBarPainter;
893
if (this.nodeBarPainter != null) {
894
this.nodeBarPainter.addPainterListener(this);
900
public NodeBarPainter getNodeBarPainter() {
901
return nodeBarPainter;
904
public void addScalePainter(ScalePainter scalePainter) {
905
assert scalePainter != null;
907
scalePainter.setTreePane(this);
908
scalePainter.addPainterListener(this);
910
scalePainters.add(scalePainter);
916
public void removeScalePainter(ScalePainter scalePainter) {
917
assert scalePainter != null;
919
scalePainter.removePainterListener(this);
921
scalePainters.remove(scalePainter);
927
public void setScaleGridPainter(ScaleGridPainter scaleGridPainter) {
928
scaleGridPainter.setTreePane(this);
929
if (this.scaleGridPainter != null) {
930
this.scaleGridPainter.removePainterListener(this);
932
this.scaleGridPainter = scaleGridPainter;
933
if (this.scaleGridPainter != null) {
934
this.scaleGridPainter.addPainterListener(this);
940
public void setPreferredSize(Dimension dimension) {
941
if (treeLayout.maintainAspectRatio()) {
942
super.setPreferredSize(new Dimension(dimension.width, dimension.height));
944
super.setPreferredSize(dimension);
950
public double getHeightAt(Graphics2D graphics2D, Point2D point) {
952
point = transform.inverseTransform(point, null);
953
} catch (NoninvertibleTransformException e) {
956
return treeLayout.getHeightOfPoint(point);
959
public Node getNodeAt(Graphics2D g2, Point point) {
960
Rectangle rect = new Rectangle(point.x - 1, point.y - 1, 3, 3);
962
for (Node node : tree.getExternalNodes()) {
963
Shape taxonLabelBound = tipLabelBounds.get(node);
965
if (taxonLabelBound != null && g2.hit(rect, taxonLabelBound, false)) {
970
if (transform == null) return null;
972
for (Node node : tree.getNodes()) {
973
Shape branchPath = transform.createTransformedShape(treeLayoutCache.getBranchPath(node));
974
if (branchPath != null && g2.hit(rect, branchPath, true)) {
977
Shape collapsedShape = transform.createTransformedShape(treeLayoutCache.getCollapsedShape(node));
978
if (collapsedShape != null && g2.hit(rect, collapsedShape, false)) {
986
public Set<Node> getNodesAt(Graphics2D g2, Rectangle rect) {
988
Set<Node> nodes = new HashSet<Node>();
989
for (Node node : tree.getExternalNodes()) {
990
Shape taxonLabelBound = tipLabelBounds.get(node);
991
if (taxonLabelBound != null && g2.hit(rect, taxonLabelBound, false)) {
996
for (Node node : tree.getNodes()) {
997
Shape branchPath = transform.createTransformedShape(treeLayoutCache.getBranchPath(node));
998
if (branchPath != null && g2.hit(rect, branchPath, true)) {
1001
Shape collapsedShape = transform.createTransformedShape(treeLayoutCache.getCollapsedShape(node));
1002
if (collapsedShape != null && g2.hit(rect, collapsedShape, false)) {
1010
public Set<Node> getSelectedNodes() {
1011
return selectedNodes;
1014
public Set<Node> getSelectedTips() {
1015
return selectedTips;
1018
public Rectangle2D getDragRectangle() {
1019
return dragRectangle;
1022
public void setDragRectangle(Rectangle2D dragRectangle) {
1023
this.dragRectangle = dragRectangle;
1027
public void setRuler(double rulerHeight) {
1028
this.rulerHeight = rulerHeight;
1031
public void scrollPointToVisible(Point point) {
1032
scrollRectToVisible(new Rectangle(point.x, point.y, 0, 0));
1036
private final Set<TreeSelectionListener> treeSelectionListeners = new HashSet<TreeSelectionListener>();
1038
public void addTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
1039
treeSelectionListeners.add(treeSelectionListener);
1042
public void removeTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
1043
treeSelectionListeners.remove(treeSelectionListener);
1046
private void fireSelectionChanged() {
1047
for (TreeSelectionListener treeSelectionListener : treeSelectionListeners) {
1048
treeSelectionListener.selectionChanged();
1052
private final Set<TreePaneListener> treePaneListeners = new HashSet<TreePaneListener>();
1054
public void addTreePaneListener(TreePaneListener treePaneListener) {
1055
treePaneListeners.add(treePaneListener);
1058
public void removeTreePaneListener(TreePaneListener treePaneListener) {
1059
treePaneListeners.remove(treePaneListener);
1062
private void fireSettingsChanged() {
1063
for (TreePaneListener treePaneListener : treePaneListeners) {
1064
treePaneListener.treePaneSettingsChanged();
1068
public void paint(Graphics graphics) {
1069
if (tree == null) return;
1071
graphics.setColor(Color.white);
1072
Rectangle r = graphics.getClipBounds();
1074
graphics.fillRect(r.x, r.y, r.width, r.height);
1077
final Graphics2D g2 = (Graphics2D) graphics;
1079
calibrate(g2, getWidth(), getHeight());
1082
drawTree(g2, getWidth(), getHeight());
1084
Paint oldPaint = g2.getPaint();
1085
Stroke oldStroke = g2.getStroke();
1087
// if (isCrosshairShown && cursorPosition != null && dragRectangle == null) {
1088
// g2.setPaint(cursorPaint);
1089
// g2.setStroke(cursorStroke);
1090
// double x = Math.max(treeBounds.getX(),
1091
// Math.min(cursorPosition.getX(), treeBounds.getX() + treeBounds.getWidth()));
1092
// double y = Math.max(treeBounds.getY(),
1093
// Math.min(cursorPosition.getY(), treeBounds.getY() + treeBounds.getHeight()));
1095
// g2.draw(new Line2D.Double(0.0, y, getWidth(), y));
1096
// g2.draw(new Line2D.Double(x, 0.0, x, getHeight()));
1100
if (branchSelection == null) {
1101
branchSelection = new GeneralPath();
1102
for (Node selectedNode : selectedNodes) {
1103
Shape branchPath = treeLayoutCache.getBranchPath(selectedNode);
1104
if (branchPath != null) {
1105
Shape transPath = transform.createTransformedShape(branchPath);
1106
branchSelection.append(transPath, false);
1109
Shape collapsedShape = treeLayoutCache.getCollapsedShape(selectedNode);
1110
if (collapsedShape != null) {
1111
Shape transPath = transform.createTransformedShape(collapsedShape);
1112
branchSelection.append(transPath, false);
1117
if (labelSelection == null) {
1118
labelSelection = new GeneralPath();
1119
for (Node selectedTip : selectedTips) {
1120
Shape labelBounds = tipLabelBounds.get(selectedTip);
1121
if (labelBounds != null) {
1122
labelSelection.append(labelBounds, false);
1127
g2.setPaint(selectionPaint);
1128
g2.setStroke(selectionStroke);
1129
g2.draw(branchSelection);
1130
g2.fill(labelSelection);
1132
g2.setPaint(oldPaint);
1133
g2.setStroke(oldStroke);
1135
if (dragRectangle != null) {
1136
g2.setPaint(new Color(128, 128, 128, 128));
1137
g2.fill(dragRectangle);
1141
private void clearSelectionPaths() {
1142
branchSelection = null;
1143
labelSelection = null;
1146
private GeneralPath branchSelection = null;
1147
private GeneralPath labelSelection = null;
1149
public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
1151
if (tree == null || pageIndex > 0) return NO_SUCH_PAGE;
1153
Graphics2D g2 = (Graphics2D) graphics;
1154
g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
1157
setDoubleBuffered(false);
1159
drawTree(g2, pageFormat.getImageableWidth(), pageFormat.getImageableHeight());
1161
setDoubleBuffered(true);
1167
public void drawTree(Graphics2D g2, double width, double height) {
1169
final RenderingHints rhints = g2.getRenderingHints();
1170
final boolean antialiasOn = rhints.containsValue(RenderingHints.VALUE_ANTIALIAS_ON);
1171
if( ! antialiasOn ) {
1172
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
1175
if (!calibrated) calibrate(g2, width, height);
1177
// save graphics state which draw changes so that upon exit it can be restored
1179
final AffineTransform oldTransform = g2.getTransform();
1180
final Paint oldPaint = g2.getPaint();
1181
final Stroke oldStroke = g2.getStroke();
1182
final Font oldFont = g2.getFont();
1185
for (ScalePainter scalePainter : scalePainters) {
1187
if (scalePainter.isVisible()) {
1188
Rectangle2D scaleBounds = this.scaleBounds.get(scalePainter);
1189
scalePainter.paint(g2, this, Painter.Justification.CENTER, scaleBounds);
1193
// Paint backgrounds
1194
if (nodeBackgroundDecorator != null) {
1195
for (Node node : treeLayoutCache.getNodeAreaMap().keySet() ) {
1196
Shape nodeArea = treeLayoutCache.getNodeArea(node);
1197
if (nodeArea != null) {
1198
nodeBackgroundDecorator.setItem(node);
1199
Shape transNodePath = transform.createTransformedShape(nodeArea);
1200
Paint background = new Color(0,0,0,0);
1201
background = nodeBackgroundDecorator.getPaint(background);
1202
g2.setPaint(background);
1203
g2.fill(transNodePath);
1205
// Experimental outlining - requires order of drawing to be pre-order
1206
// g2.setStroke(new BasicStroke(8, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
1207
// g2.draw(transNodePath);
1212
// Paint hilighted nodes
1213
for (Node node : treeLayoutCache.getHilightNodesList() ) {
1214
Object[] values = (Object[])node.getAttribute(HILIGHT_ATTRIBUTE_NAME);
1216
Shape hilightShape = treeLayoutCache.getHilightShape(node);
1218
Shape transShape = transform.createTransformedShape(hilightShape);
1219
Paint paint = ((Color)values[2]).darker();
1220
Paint fillPaint = (Color)values[2];
1221
Stroke stroke = new BasicStroke(0.5F);
1223
if (fillPaint != null) {
1224
g2.setPaint(fillPaint);
1225
g2.fill(transShape);
1228
if (paint != null) {
1230
g2.setStroke(stroke);
1231
g2.draw(transShape);
1235
if (scaleGridPainter != null && scaleGridPainter.isVisible()) {
1236
Rectangle2D gridBounds = new Rectangle2D.Double(
1237
treeBounds.getX(), 0.0,
1238
treeBounds.getWidth(), totalScaleBounds.getY());
1239
scaleGridPainter.paint(g2, this, null, gridBounds);
1242
if (DEBUG_OUTLINE) {
1243
g2.setPaint(Color.red);
1244
g2.draw(treeBounds);
1247
// Paint collapsed nodes
1248
for (Node node : treeLayoutCache.getCollapsedShapeMap().keySet() ) {
1249
Shape collapsedShape = treeLayoutCache.getCollapsedShape(node);
1251
Shape transShape = transform.createTransformedShape(collapsedShape);
1252
Paint paint = Color.BLACK;
1253
Paint fillPaint = null;
1254
Stroke stroke = branchLineStroke;
1256
if (branchDecorator != null) {
1257
branchDecorator.setItem(node);
1258
paint = branchDecorator.getPaint(paint);
1259
fillPaint = branchDecorator.getFillPaint(fillPaint);
1260
stroke = branchDecorator.getStroke(stroke);
1263
if (fillPaint != null) {
1264
g2.setPaint(fillPaint);
1265
g2.fill(transShape);
1269
g2.setStroke(stroke);
1270
g2.draw(transShape);
1274
for (Node node : treeLayoutCache.getBranchPathMap().keySet() ) {
1275
Stroke stroke = branchLineStroke;
1276
if (branchDecorator != null) {
1277
branchDecorator.setItem(node);
1278
stroke = branchDecorator.getStroke(stroke);
1280
g2.setStroke(stroke);
1282
Object[] branchColouring = null;
1283
if (treeLayout.isShowingColouring() && branchColouringAttribute != null) {
1284
branchColouring = (Object[])node.getAttribute(branchColouringAttribute);
1287
Shape branchPath = treeLayoutCache.getBranchPath(node);
1288
Shape nodePath = treeLayoutCache.getNodeArea(node);
1290
if (branchColouring != null) {
1291
PathIterator iter = branchPath.getPathIterator(transform);
1293
float[] coords1 = new float[2];
1294
iter.currentSegment(coords1);
1296
for (int i = 0; i < branchColouring.length - 1; i+=2) {
1298
float[] coords2 = new float[2];
1299
iter.currentSegment(coords2);
1301
int colour = ((Number)branchColouring[i]).intValue();
1302
branchColouringDecorator.setItem(colour);
1303
g2.setPaint(branchColouringDecorator.getPaint(Color.BLACK));
1304
g2.draw(new Line2D.Float(coords1[0], coords1[1], coords2[0], coords2[1]));
1309
// Draw the remaining branch as a path so it has proper line joins...
1310
int colour = ((Number)branchColouring[branchColouring.length - 1]).intValue();
1311
branchColouringDecorator.setItem(colour);
1312
g2.setPaint(branchColouringDecorator.getPaint(Color.BLACK));
1314
// Append the rest of the PathIterator to this new path...
1315
GeneralPath path = new GeneralPath();
1316
path.moveTo(coords1[0], coords1[1]);
1317
path.append(iter, true);
1323
Shape transPath = transform.createTransformedShape(branchPath);
1324
Paint paint = Color.BLACK;
1325
if (branchDecorator != null) {
1326
if (branchDecorator.isGradient()) {
1327
branchDecorator.setItems(node, tree.getParent(node));
1328
PathIterator iter = transPath.getPathIterator(null);
1329
double[] coords = new double[6];
1330
iter.currentSegment(coords);
1331
Point2D point1 = new Point2D.Double(coords[0], coords[1]);
1333
iter.currentSegment(coords);
1335
} while (!iter.isDone());
1336
Point2D point2 = new Point2D.Double(coords[0], coords[1]);
1338
paint = branchDecorator.getPaint(paint, point1, point2);
1340
branchDecorator.setItem(node);
1341
paint = branchDecorator.getPaint(paint);
1350
if (!isTransformBranchesOn() && nodeBarPainter != null && nodeBarPainter.isVisible()) {
1351
for (Node node : nodeBars.keySet() ) {
1352
Shape nodeBar = nodeBars.get(node);
1353
nodeBar = transform.createTransformedShape(nodeBar);
1354
nodeBarPainter.paint(g2, node, NodePainter.Justification.CENTER, nodeBar);
1359
if (tipLabelPainter != null && tipLabelPainter.isVisible()) {
1361
for (Node node : tipLabelTransforms.keySet()) {
1363
AffineTransform tipLabelTransform = tipLabelTransforms.get(node);
1365
Painter.Justification tipLabelJustification = tipLabelJustifications.get(node);
1366
g2.transform(tipLabelTransform);
1368
tipLabelPainter.paint(g2, node, tipLabelJustification,
1369
new Rectangle2D.Double(0.0, 0.0, tipLabelWidth, tipLabelPainter.getPreferredHeight()));
1371
g2.setTransform(oldTransform);
1373
if (showingTipCallouts) {
1374
Shape calloutPath = transform.createTransformedShape(treeLayoutCache.getCalloutPath(node));
1375
if (calloutPath != null) {
1376
g2.setStroke(calloutStroke);
1377
g2.draw(calloutPath);
1383
// Paint node labels
1384
if (nodeLabelPainter != null && nodeLabelPainter.isVisible()) {
1385
for (Node node : nodeLabelTransforms.keySet() ) {
1387
AffineTransform nodeTransform = nodeLabelTransforms.get(node);
1389
Painter.Justification nodeLabelJustification = nodeLabelJustifications.get(node);
1390
g2.transform(nodeTransform);
1392
nodeLabelPainter.paint(g2, node, nodeLabelJustification,
1393
new Rectangle2D.Double(0.0, 0.0, nodeLabelPainter.getPreferredWidth(), nodeLabelPainter.getPreferredHeight()));
1395
g2.setTransform(oldTransform);
1399
// Paint branch labels
1400
if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
1402
for (Node node : branchLabelTransforms.keySet() ) {
1404
AffineTransform branchTransform = branchLabelTransforms.get(node);
1406
g2.transform(branchTransform);
1408
branchLabelPainter.calibrate(g2, node);
1409
final double preferredWidth = branchLabelPainter.getPreferredWidth();
1410
final double preferredHeight = branchLabelPainter.getPreferredHeight();
1412
//Line2D labelPath = treeLayout.getBranchLabelPath(node);
1414
branchLabelPainter.paint(g2, node, Painter.Justification.CENTER,
1415
//new Rectangle2D.Double(-preferredWidth/2, -preferredHeight, preferredWidth, preferredHeight));
1416
new Rectangle2D.Double(0, 0, preferredWidth, preferredHeight));
1418
g2.setTransform(oldTransform);
1422
g2.setStroke(oldStroke);
1423
g2.setPaint(oldPaint);
1424
g2.setFont(oldFont);
1427
private void calibrate(Graphics2D g2, double width, double height) {
1429
// First layout the tree
1430
treeLayout.layout(tree, treeLayoutCache);
1432
// Now rescale the scale axis
1435
// First of all get the bounds for the unscaled tree
1438
// There are two sets of bounds here. The treeBounds are the bounds of the elements
1439
// that make up the actual tree. These are scaled from branch length space
1441
// The bounds are then the extra stuff that doesn't get scaled with the tree such
1442
// as labels and the like.
1444
// bounds on branches
1445
for (Shape branchPath : treeLayoutCache.getBranchPathMap().values()) {
1446
// Add the bounds of the branch path to the overall bounds
1447
final Rectangle2D branchBounds = branchPath.getBounds2D();
1448
if (treeBounds == null) {
1449
treeBounds = branchBounds;
1451
treeBounds.add(branchBounds);
1455
// Iterate though the callout paths
1456
for (Shape calloutPath : treeLayoutCache.getCalloutPathMap().values()) {
1457
// Get the line that represents the path for the taxon label
1458
// and add the translated bounds to the overall bounds
1459
final Rectangle2D calloutBounds = calloutPath.getBounds2D();
1460
treeBounds.add(calloutBounds);
1464
for (Shape collapsedShape : treeLayoutCache.getCollapsedShapeMap().values()) {
1465
// Add the bounds of the branch path to the overall bounds
1466
final Rectangle2D branchBounds = collapsedShape.getBounds2D();
1467
if (treeBounds == null) {
1468
treeBounds = branchBounds;
1470
treeBounds.add(branchBounds);
1474
for (Shape hilightShape : treeLayoutCache.getHilightShapeMap().values()) {
1475
// Add the bounds of the branch path to the overall bounds
1476
final Rectangle2D branchBounds = hilightShape.getBounds2D();
1477
if (treeBounds == null) {
1478
treeBounds = branchBounds;
1480
treeBounds.add(branchBounds);
1484
// bounds on nodeShapes
1485
if (!isTransformBranchesOn() && nodeBarPainter != null && nodeBarPainter.isVisible()) {
1487
// Iterate though the nodes
1488
for (Node node : tree.getInternalNodes()) {
1490
Rectangle2D shapeBounds = nodeBarPainter.calibrate(g2, node);
1491
treeBounds.add(shapeBounds);
1492
nodeBars.put(node, nodeBarPainter.getNodeBar());
1496
// adjust the bounds so that the origin is at 0,0
1497
//treeBounds = new Rectangle2D.Double(0.0, 0.0, treeBounds.getWidth(), treeBounds.getHeight());
1499
// add the tree bounds
1500
final Rectangle2D totalTreeBounds = treeBounds.getBounds2D(); // (YH) same as (Rectangle2D) treeBounds.clone();
1502
final Set<Node> externalNodes = tree.getExternalNodes();
1504
if (tipLabelPainter != null && tipLabelPainter.isVisible()) {
1506
tipLabelWidth = 0.0;
1508
// Find the longest taxon label
1509
for (Node node : externalNodes) {
1511
tipLabelPainter.calibrate(g2, node);
1512
tipLabelWidth = Math.max(tipLabelWidth, tipLabelPainter.getPreferredWidth());
1515
final double tipLabelHeight = tipLabelPainter.getPreferredHeight();
1517
// Iterate though the nodes with tip labels
1518
for (Node node : treeLayoutCache.getTipLabelPathMap().keySet()) {
1519
Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, tipLabelWidth, tipLabelHeight);
1521
// Get the line that represents the path for the taxon label
1522
Line2D taxonPath = treeLayoutCache.getTipLabelPath(node);
1524
// Work out how it is rotated and create a transform that matches that
1525
AffineTransform taxonTransform = calculateTransform(null, taxonPath, tipLabelWidth, tipLabelHeight, true);
1527
// and add the translated bounds to the overall bounds
1528
totalTreeBounds.add(taxonTransform.createTransformedShape(labelBounds).getBounds2D());
1532
if (nodeLabelPainter != null && nodeLabelPainter.isVisible()) {
1533
// Iterate though the nodes with node labels
1534
for (Node node : treeLayoutCache.getNodeLabelPathMap().keySet()) {
1535
// Get the line that represents the path for the taxon label
1536
final Line2D labelPath = treeLayoutCache.getNodeLabelPath(node);
1538
nodeLabelPainter.calibrate(g2, node);
1539
final double labelHeight = nodeLabelPainter.getPreferredHeight();
1540
final double labelWidth = nodeLabelPainter.getPreferredWidth();
1541
Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
1543
// Work out how it is rotated and create a transform that matches that
1544
AffineTransform labelTransform = calculateTransform(null, labelPath, labelWidth, labelHeight, true);
1546
// and add the translated bounds to the overall bounds
1547
totalTreeBounds.add(labelTransform.createTransformedShape(labelBounds).getBounds2D());
1551
if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
1552
// Iterate though the nodes with branch labels
1553
for (Node node : treeLayoutCache.getBranchLabelPathMap().keySet()) {
1554
// Get the line that represents the path for the branch label
1555
final Line2D labelPath = treeLayoutCache.getBranchLabelPath(node);
1557
branchLabelPainter.calibrate(g2, node);
1558
final double labelHeight = branchLabelPainter.getHeightBound();
1559
final double labelWidth = branchLabelPainter.getPreferredWidth();
1561
Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
1563
// Work out how it is rotated and create a transform that matches that
1564
AffineTransform labelTransform = calculateTransform(null, labelPath, labelWidth, labelHeight, false);
1566
// and add the translated bounds to the overall bounds
1567
totalTreeBounds.add(labelTransform.createTransformedShape(labelBounds).getBounds2D());
1571
totalScaleBounds = new Rectangle2D.Double(0.0, 0.0, 0.0, 0.0);
1572
double y = totalTreeBounds.getY() + totalTreeBounds.getHeight();
1573
for (ScalePainter scalePainter : scalePainters) {
1574
if (scalePainter.isVisible()) {
1575
scalePainter.calibrate(g2, this);
1576
Rectangle2D sb = new Rectangle2D.Double(
1577
treeBounds.getX(), y,
1578
treeBounds.getWidth(), scalePainter.getPreferredHeight());
1579
y += sb.getHeight();
1580
totalScaleBounds.add(sb);
1581
scaleBounds.put(scalePainter, sb);
1584
totalTreeBounds.add(totalScaleBounds);
1586
final double availableW = width - insets.left - insets.right;
1587
final double availableH = height - insets.top - insets.bottom;
1589
// get the difference between the tree's bounds and the overall bounds
1590
boolean maintainAspectRatio = treeLayout.maintainAspectRatio();
1595
if (maintainAspectRatio) {
1596
double topDiff = treeBounds.getY() - totalTreeBounds.getY();
1597
double leftDiff = treeBounds.getX() - totalTreeBounds.getX();
1598
double bottomDiff = (totalTreeBounds.getHeight() + totalTreeBounds.getY()) -
1599
(treeBounds.getHeight() + treeBounds.getY());
1600
double rightDiff = (totalTreeBounds.getWidth() + totalTreeBounds.getX()) -
1601
(treeBounds.getWidth() + treeBounds.getX());
1602
assert topDiff >= 0 && leftDiff >= 0 && bottomDiff >= 0 && rightDiff >= 0;
1604
xDiff = 2.0 * (leftDiff > rightDiff ? leftDiff : rightDiff);
1605
yDiff = 2.0 * (topDiff > bottomDiff ? topDiff : bottomDiff);
1607
xDiff = totalTreeBounds.getWidth() - treeBounds.getWidth();
1608
yDiff = totalTreeBounds.getHeight() - treeBounds.getHeight();
1609
assert xDiff >= 0 && yDiff >= 0;
1613
// small tree, long labels, label bounds may get larger that window, protect against that
1615
if( xDiff >= availableW ) {
1616
xDiff = Math.min(availableW, totalTreeBounds.getWidth()) - treeBounds.getWidth();
1619
if( yDiff >= availableH ) {
1620
yDiff = Math.min(availableH, totalTreeBounds.getHeight()) - treeBounds.getHeight();
1623
// Get the amount of canvas that is going to be taken up by the tree -
1624
// The rest is taken up by taxon labels which don't scale
1626
final double w = availableW - xDiff;
1627
final double h = availableH - yDiff;
1632
double xOffset = 0.0;
1633
double yOffset = 0.0;
1635
if (maintainAspectRatio) {
1636
// If the tree is layed out in both dimensions then we
1637
// need to find out which axis has the least space and scale
1638
// the tree to that (to keep the aspect ratio.
1640
if ((w / treeBounds.getWidth()) < (h / treeBounds.getHeight())) {
1641
xScale = w / treeBounds.getWidth();
1644
yScale = h / treeBounds.getHeight();
1648
treeScale = xScale; assert treeScale > 0;
1650
// and set the origin so that the center of the tree is in
1651
// the center of the canvas
1652
xOffset = ((width - (treeBounds.getWidth() * xScale)) / 2) - (treeBounds.getX() * xScale);
1653
yOffset = ((height - (treeBounds.getHeight() * yScale)) / 2) - (treeBounds.getY() * yScale);
1656
// Otherwise just scale both dimensions
1657
xScale = w / treeBounds.getWidth();
1658
yScale = h / treeBounds.getHeight();
1660
// and set the origin in the top left corner
1661
xOffset = - (treeBounds.getX() * xScale);
1662
yOffset = - totalTreeBounds.getY();
1667
assert treeScale > 0;
1669
// Create the overall transform
1670
transform = new AffineTransform();
1671
transform.translate(xOffset + insets.left, yOffset + insets.top);
1672
transform.scale(xScale, yScale);
1674
// Get the bounds for the newly scaled tree
1677
// bounds on branches
1678
for (Shape branchPath : treeLayoutCache.getBranchPathMap().values()) {
1679
// Add the bounds of the branch path to the overall bounds
1680
final Rectangle2D branchBounds = transform.createTransformedShape(branchPath).getBounds2D();
1681
if (treeBounds == null) {
1682
treeBounds = branchBounds;
1684
treeBounds.add(branchBounds);
1688
for (Shape collapsedShape : treeLayoutCache.getCollapsedShapeMap().values()) {
1689
// Add the bounds of the branch path to the overall bounds
1690
final Rectangle2D branchBounds = transform.createTransformedShape(collapsedShape).getBounds2D();
1691
if (treeBounds == null) {
1692
treeBounds = branchBounds;
1694
treeBounds.add(branchBounds);
1698
// Clear the map of individual taxon label bounds and transforms
1699
tipLabelBounds.clear();
1700
tipLabelTransforms.clear();
1701
tipLabelJustifications.clear();
1703
if (tipLabelPainter != null && tipLabelPainter.isVisible()) {
1704
final double labelHeight = tipLabelPainter.getPreferredHeight();
1705
Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, tipLabelWidth, labelHeight);
1707
// Iterate though the external nodes with tip labels
1708
for (Node node : treeLayoutCache.getTipLabelPathMap().keySet()) {
1709
// Get the line that represents the path for the tip label
1710
Line2D tipPath = treeLayoutCache.getTipLabelPath(node);
1712
// Work out how it is rotated and create a transform that matches that
1713
AffineTransform taxonTransform = calculateTransform(transform, tipPath, tipLabelWidth, labelHeight, true);
1715
// Store the transformed bounds in the map for use when selecting
1716
tipLabelBounds.put(node, taxonTransform.createTransformedShape(labelBounds));
1718
// Store the transform in the map for use when drawing
1719
tipLabelTransforms.put(node, taxonTransform);
1721
// Store the alignment in the map for use when drawing
1722
final Painter.Justification just = (tipPath.getX1() < tipPath.getX2()) ?
1723
Painter.Justification.LEFT : Painter.Justification.RIGHT;
1724
tipLabelJustifications.put(node, just);
1728
// Clear the map of individual node label bounds and transforms
1729
nodeLabelBounds.clear();
1730
nodeLabelTransforms.clear();
1731
nodeLabelJustifications.clear();
1733
if (nodeLabelPainter != null && nodeLabelPainter.isVisible()) {
1734
final double labelHeight = nodeLabelPainter.getPreferredHeight();
1735
final double labelWidth = nodeLabelPainter.getPreferredWidth();
1736
final Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
1738
// Iterate though the external nodes with node labels
1739
for (Node node : treeLayoutCache.getNodeLabelPathMap().keySet()) {
1740
// Get the line that represents the path for the node label
1741
final Line2D labelPath = treeLayoutCache.getNodeLabelPath(node);
1743
// Work out how it is rotated and create a transform that matches that
1744
AffineTransform labelTransform = calculateTransform(transform, labelPath, labelWidth, labelHeight, true);
1746
// Store the transformed bounds in the map for use when selecting
1747
nodeLabelBounds.put(node, labelTransform.createTransformedShape(labelBounds));
1749
// Store the transform in the map for use when drawing
1750
nodeLabelTransforms.put(node, labelTransform);
1752
// Store the alignment in the map for use when drawing
1753
if (labelPath.getX1() < labelPath.getX2()) {
1754
nodeLabelJustifications.put(node, Painter.Justification.LEFT);
1756
nodeLabelJustifications.put(node, Painter.Justification.RIGHT);
1761
branchLabelBounds.clear();
1762
branchLabelTransforms.clear();
1763
branchLabelJustifications.clear();
1765
if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
1767
// Iterate though the external nodes with branch labels
1768
for (Node node : treeLayoutCache.getBranchLabelPathMap().keySet()) {
1770
// Get the line that represents the path for the branch label
1771
Line2D labelPath = treeLayoutCache.getBranchLabelPath(node);
1773
// AR - I don't think we need to recalibrate this here
1774
// branchLabelPainter.calibrate(g2, node);
1775
final double labelHeight = branchLabelPainter.getPreferredHeight();
1776
final double labelWidth = branchLabelPainter.getPreferredWidth();
1777
final Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
1779
final double dx = labelPath.getP2().getX() - labelPath.getP1().getX();
1780
final double dy = labelPath.getP2().getY() - labelPath.getP1().getY();
1781
final double branchLength = Math.sqrt(dx*dx + dy*dy);
1783
final Painter.Justification just = labelPath.getX1() < labelPath.getX2() ? Painter.Justification.LEFT :
1784
Painter.Justification.RIGHT;
1786
// Work out how it is rotated and create a transform that matches that
1787
AffineTransform labelTransform = calculateTransform(transform, labelPath, labelWidth, labelHeight, false);
1788
// move to middle of branch - since the move is before the rotation
1789
final double direction = just == Painter.Justification.RIGHT ? 1 : -1;
1790
labelTransform.translate(-direction * xScale * branchLength /2, 0);
1792
// Store the transformed bounds in the map for use when selecting
1793
branchLabelBounds.put(node, labelTransform.createTransformedShape(labelBounds));
1795
// Store the transform in the map for use when drawing
1796
branchLabelTransforms.put(node, labelTransform);
1798
// Store the alignment in the map for use when drawing
1799
branchLabelJustifications.put(node, just);
1805
for (ScalePainter scalePainter : scalePainters) {
1806
if (scalePainter.isVisible()) {
1807
scalePainter.calibrate(g2, this);
1808
y -= scalePainter.getPreferredHeight();
1812
totalScaleBounds = new Rectangle2D.Double(treeBounds.getX(), y, treeBounds.getWidth(), 0.0);
1813
for (ScalePainter scalePainter : scalePainters) {
1814
if (scalePainter.isVisible()) {
1815
scalePainter.calibrate(g2, this);
1816
final double h1 = scalePainter.getPreferredHeight();
1817
Rectangle2D sb = new Rectangle2D.Double(treeBounds.getX(), y, treeBounds.getWidth(), h1);
1819
totalScaleBounds.add(sb);
1820
scaleBounds.put(scalePainter, sb);
1824
calloutPaths.clear();
1825
clearSelectionPaths();
1830
private AffineTransform calculateTransform(AffineTransform globalTransform, Line2D line,
1831
double width, double height, boolean just) {
1832
final Point2D origin = line.getP1();
1833
if (globalTransform != null) {
1834
globalTransform.transform(origin, origin);
1837
// Work out how it is rotated and create a transform that matches that
1838
AffineTransform lineTransform = new AffineTransform();
1840
final double dy = line.getY2() - line.getY1();
1843
final double dx = line.getX2() - line.getX1();
1844
final double angle = dx != 0.0 ? Math.atan(dy / dx) : 0.0;
1845
lineTransform.rotate(angle, origin.getX(), origin.getY());
1848
// Now add a translate to the transform - if it is on the left then we need
1849
// to shift it by the entire width of the string.
1850
final double ty = origin.getY() - (height / 2.0);
1851
double tx = origin.getX();
1853
if (line.getX2() > line.getX1()) {
1856
tx -= (labelXOffset + width);
1859
lineTransform.translate(tx, ty);
1860
return lineTransform;
1863
// Overridden methods to recalibrate tree when bounds change
1864
public void setBounds(int x, int y, int width, int height) {
1866
super.setBounds(x, y, width, height);
1869
public void setBounds(Rectangle rectangle) {
1871
super.setBounds(rectangle);
1874
public void setSize(Dimension dimension) {
1876
super.setSize(dimension);
1879
public void setSize(int width, int height) {
1881
super.setSize(width, height);
1884
private RootedTree originalTree = null;
1885
private RootedTree tree = null;
1886
private TreeLayout treeLayout = null;
1887
private TreeLayoutCache treeLayoutCache = new TreeLayoutCache();
1889
private boolean orderBranchesOn = false;
1890
private SortedRootedTree.BranchOrdering branchOrdering = SortedRootedTree.BranchOrdering.INCREASING_NODE_DENSITY;
1892
private boolean transformBranchesOn = false;
1893
private TransformedRootedTree.Transform branchTransform = TransformedRootedTree.Transform.CLADOGRAM;
1895
private boolean isRootingOn = false;
1896
private RootingType rootingType = RootingType.USER_ROOTING;
1897
private Node rootingNode = null;
1898
private double rootingLength = 0.01;
1900
private Rectangle2D treeBounds = new Rectangle2D.Double();
1901
private double treeScale;
1903
private ScaleAxis scaleAxis = new ScaleAxis(ScaleAxis.AT_DATA, ScaleAxis.AT_DATA);
1904
private double axisOrigin = 0.0;
1905
private TimeScale timeScale = new TimeScale(1.0, 0.0);
1907
//private Insets insets = new Insets(0, 0, 0, 0);
1908
private Insets insets = new Insets(6, 6, 6, 6);
1910
private Set<Node> selectedNodes = new HashSet<Node>();
1911
private Set<Node> selectedTips = new HashSet<Node>();
1913
private double rulerHeight = -1.0;
1914
private Rectangle2D dragRectangle = null;
1915
private Point2D cursorPosition = null;
1917
private boolean isCrosshairShown = true;
1919
private Decorator branchDecorator = null;
1920
private Decorator branchColouringDecorator = null;
1921
private String branchColouringAttribute = null;
1923
private Decorator nodeBackgroundDecorator = null;
1925
private float labelXOffset = 5.0F;
1926
private LabelPainter<Node> tipLabelPainter = null;
1927
private double tipLabelWidth;
1928
private LabelPainter<Node> nodeLabelPainter = null;
1929
private LabelPainter<Node> branchLabelPainter = null;
1931
private NodeBarPainter nodeBarPainter = null;
1933
private List<ScalePainter> scalePainters = new ArrayList<ScalePainter>();
1934
private Rectangle2D totalScaleBounds = null;
1935
private Map<ScalePainter, Rectangle2D> scaleBounds = new HashMap<ScalePainter, Rectangle2D>();
1937
private ScaleGridPainter scaleGridPainter = null;
1939
private BasicStroke branchLineStroke = new BasicStroke(1.0F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
1940
private BasicStroke calloutStroke = new BasicStroke(0.5F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[]{0.5f, 2.0f}, 0.0f);
1941
private Stroke selectionStroke = new BasicStroke(6.0F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
1942
private Paint selectionPaint;
1943
private Stroke cursorStroke = new BasicStroke(0.5F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
1944
private Paint cursorPaint = Color.DARK_GRAY;
1946
private boolean calibrated = false;
1947
private AffineTransform transform = null;
1949
private boolean showingTipCallouts = true;
1951
private Map<Node, AffineTransform> tipLabelTransforms = new HashMap<Node, AffineTransform>();
1952
private Map<Node, Shape> tipLabelBounds = new HashMap<Node, Shape>();
1953
private Map<Node, Painter.Justification> tipLabelJustifications = new HashMap<Node, Painter.Justification>();
1955
private Map<Node, AffineTransform> nodeLabelTransforms = new HashMap<Node, AffineTransform>();
1956
private Map<Node, Shape> nodeLabelBounds = new HashMap<Node, Shape>();
1957
private Map<Node, Painter.Justification> nodeLabelJustifications = new HashMap<Node, Painter.Justification>();
1959
private Map<Node, AffineTransform> branchLabelTransforms = new HashMap<Node, AffineTransform>();
1960
private Map<Node, Shape> branchLabelBounds = new HashMap<Node, Shape>();
1961
private Map<Node, Painter.Justification> branchLabelJustifications = new HashMap<Node, Painter.Justification>();
1963
private Map<Node, Shape> nodeBars = new HashMap<Node, Shape>();
1965
private Map<Node, Shape> calloutPaths = new HashMap<Node, Shape>();
b'\\ No newline at end of file'