~ubuntu-branches/ubuntu/utopic/figtree/utopic

« back to all changes in this revision

Viewing changes to src/figtree/treeviewer/TreePane.java

  • Committer: Bazaar Package Importer
  • Author(s): Andreas Tille
  • Date: 2011-02-21 08:17:38 UTC
  • Revision ID: james.westby@ubuntu.com-20110221081738-80pe2ulo8rg7up10
Tags: upstream-1.3.1
ImportĀ upstreamĀ versionĀ 1.3.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package figtree.treeviewer;
 
2
 
 
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.*;
 
10
 
 
11
import javax.swing.*;
 
12
import java.awt.*;
 
13
import java.awt.geom.*;
 
14
import java.awt.print.*;
 
15
import java.util.*;
 
16
import java.util.List;
 
17
 
 
18
 
 
19
/**
 
20
 * @author Andrew Rambaut
 
21
 * @version $Id: TreePane.java 823 2007-10-28 20:28:47Z stevensh $
 
22
 */
 
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");
 
28
 
 
29
        RootingType(String name) {
 
30
            this.name = name;
 
31
        }
 
32
 
 
33
        public String toString() { return name; }
 
34
 
 
35
        private String name;
 
36
    }
 
37
 
 
38
 
 
39
    public final static boolean DEBUG_OUTLINE = false;
 
40
 
 
41
    public final String CARTOON_ATTRIBUTE_NAME = "!cartoon";
 
42
    public final String COLLAPSE_ATTRIBUTE_NAME = "!collapse";
 
43
    public final String HILIGHT_ATTRIBUTE_NAME = "!hilight";
 
44
 
 
45
    public TreePane() {
 
46
    }
 
47
 
 
48
    public RootedTree getTree() {
 
49
        return tree;
 
50
    }
 
51
 
 
52
    public void setTree(RootedTree tree) {
 
53
        if (tree != null) {
 
54
            this.originalTree = tree;
 
55
            if (!originalTree.hasLengths()) {
 
56
                transformBranchesOn = true;
 
57
            }
 
58
            setupTree();
 
59
        } else {
 
60
            originalTree = null;
 
61
            this.tree = null;
 
62
            invalidate();
 
63
            repaint();
 
64
        }
 
65
    }
 
66
 
 
67
    private void setupTree() {
 
68
        tree = constructTransformedTree(originalTree);
 
69
 
 
70
        recalculateCollapsedNodes();
 
71
 
 
72
        calibrated = false;
 
73
        invalidate();
 
74
        repaint();
 
75
    }
 
76
 
 
77
    public RootedTree constructTransformedTree(RootedTree sourceTree) {
 
78
        RootedTree newTree = sourceTree;
 
79
 
 
80
        if (isRootingOn) {
 
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);
 
85
                if (left != null) {
 
86
                    // rooting length should be [0, 1]
 
87
                    double length = newTree.getLength(rootingNode) * rootingLength;
 
88
                    try {
 
89
                        newTree = new ReRootedTree(newTree, left, rootingNode, length);
 
90
                    } catch (Graph.NoEdgeException e) {
 
91
                        e.printStackTrace();
 
92
                    }
 
93
                }
 
94
            }
 
95
        }
 
96
 
 
97
        if (orderBranchesOn) {
 
98
            newTree = new SortedRootedTree(newTree, branchOrdering);
 
99
        }
 
100
 
 
101
        if (transformBranchesOn || !sourceTree.hasLengths()) {
 
102
            newTree = new TransformedRootedTree(newTree, branchTransform);
 
103
        }
 
104
 
 
105
        return newTree;
 
106
    }
 
107
 
 
108
    public TreeLayout getTreeLayout() {
 
109
        return treeLayout;
 
110
    }
 
111
 
 
112
    public TreeLayoutCache getTreeLayoutCache() {
 
113
        return treeLayoutCache;
 
114
    }
 
115
 
 
116
    public void setTreeLayout(TreeLayout treeLayout) {
 
117
 
 
118
        this.treeLayout = treeLayout;
 
119
 
 
120
        treeLayout.setCartoonAttributeName(CARTOON_ATTRIBUTE_NAME);
 
121
        treeLayout.setCollapsedAttributeName(COLLAPSE_ATTRIBUTE_NAME);
 
122
        treeLayout.setHilightAttributeName(HILIGHT_ATTRIBUTE_NAME);
 
123
        treeLayout.setBranchColouringAttributeName(branchColouringAttribute);
 
124
 
 
125
        treeLayout.addTreeLayoutListener(new TreeLayoutListener() {
 
126
            public void treeLayoutChanged() {
 
127
                calibrated = false;
 
128
                repaint();
 
129
            }
 
130
        });
 
131
        calibrated = false;
 
132
        invalidate();
 
133
        repaint();
 
134
    }
 
135
 
 
136
    public TimeScale getTimeScale() {
 
137
        return timeScale;
 
138
    }
 
139
 
 
140
    public void setTimeScale(TimeScale timeScale) {
 
141
        this.timeScale = timeScale;
 
142
        calibrated = false;
 
143
        repaint();
 
144
    }
 
145
 
 
146
    public boolean isCrosshairShown() {
 
147
        return isCrosshairShown;
 
148
    }
 
149
 
 
150
    public void setCrosshairShown(boolean crosshairShown) {
 
151
        isCrosshairShown = crosshairShown;
 
152
    }
 
153
 
 
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);
 
162
        }
 
163
    }
 
164
 
 
165
    public void midpointRoot() {
 
166
        isRootingOn = true;
 
167
        rootingType = RootingType.MID_POINT;
 
168
 
 
169
        setupTree();
 
170
 
 
171
        fireSettingsChanged();
 
172
    }
 
173
 
 
174
    public void setRootLocation(Node node, double length) {
 
175
        if (isRootingOn()) {
 
176
            rootingNode = ((ReRootedTree)tree).getSourceNode(node);
 
177
        } else {
 
178
            rootingNode = node;
 
179
        }
 
180
        rootingLength = length;
 
181
 
 
182
        isRootingOn = true;
 
183
        rootingType = RootingType.USER_ROOTING;
 
184
 
 
185
        setupTree();
 
186
 
 
187
        fireSettingsChanged();
 
188
    }
 
189
 
 
190
 
 
191
 
 
192
    public void rotateNode(Node node) {
 
193
        if (node != null) {
 
194
            Boolean rotate = (Boolean)node.getAttribute("!rotate");
 
195
            if (rotate != null) {
 
196
                rotate = !rotate;
 
197
            } else {
 
198
                rotate = true;
 
199
            }
 
200
            node.setAttribute("!rotate", rotate);
 
201
 
 
202
            calibrated = false;
 
203
            invalidate();
 
204
            repaint();
 
205
        }
 
206
 
 
207
    }
 
208
 
 
209
    public void clearRotation(Node node) {
 
210
        if (node != null) {
 
211
            Boolean rotate = (Boolean)node.getAttribute("!rotate");
 
212
            if (rotate != null) {
 
213
                node.removeAttribute("!rotate");
 
214
            }
 
215
 
 
216
            calibrated = false;
 
217
            invalidate();
 
218
            repaint();
 
219
        }
 
220
 
 
221
    }
 
222
 
 
223
    public void setBranchDecorator(Decorator branchDecorator) {
 
224
        this.branchDecorator = branchDecorator;
 
225
        repaint();
 
226
    }
 
227
 
 
228
    public void setBranchColouringDecorator(String branchColouringAttribute, Decorator branchColouringDecorator) {
 
229
        this.branchColouringAttribute = branchColouringAttribute;
 
230
        treeLayout.setBranchColouringAttributeName(branchColouringAttribute);
 
231
        this.branchColouringDecorator = branchColouringDecorator;
 
232
        repaint();
 
233
    }
 
234
 
 
235
    public void setNodeBackgroundDecorator(Decorator nodeBackgroundDecorator) {
 
236
        this.nodeBackgroundDecorator = nodeBackgroundDecorator;
 
237
        repaint();
 
238
    }
 
239
 
 
240
    public Rectangle2D getTreeBounds() {
 
241
        return treeBounds;
 
242
    }
 
243
 
 
244
    /**
 
245
     * This returns the scaling factor between the graphical image and the branch
 
246
     * lengths of the tree
 
247
     *
 
248
     * @return the tree scale
 
249
     */
 
250
    public double getTreeScale() {
 
251
        return treeScale / timeScale.getScaleFactor(tree);
 
252
    }
 
253
 
 
254
    /**
 
255
     *  Transform a chart co-ordinates into a drawing co-ordinates
 
256
     */
 
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);
 
261
        } else {
 
262
            return treeBounds.getX() + (height * treeScale);
 
263
        }
 
264
    }
 
265
 
 
266
    public Shape getAxisLine(double value) {
 
267
        double height = timeScale.getHeight(value, tree);
 
268
        Shape line = treeLayout.getAxisLine(height);
 
269
        if (line != null) {
 
270
            return transform.createTransformedShape(line);
 
271
        }
 
272
        return null;
 
273
    }
 
274
 
 
275
 
 
276
    public ScaleAxis getScaleAxis() {
 
277
        return scaleAxis;
 
278
    }
 
279
 
 
280
    public double getAxisOrigin() {
 
281
        return axisOrigin;
 
282
    }
 
283
 
 
284
    public void setAxisOrigin(double axisOrigin) {
 
285
        this.axisOrigin = axisOrigin;
 
286
        calibrated = false;
 
287
        repaint();
 
288
    }
 
289
 
 
290
    public void setAxisReversed(final boolean isAxisReversed) {
 
291
        treeLayout.setAxisReversed(isAxisReversed);
 
292
        calibrated = false;
 
293
        repaint();
 
294
    }
 
295
 
 
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);
 
300
 
 
301
        if (minValue < maxValue) {
 
302
            if (axisOrigin < minValue) {
 
303
                minValue = axisOrigin;
 
304
            }
 
305
            scaleAxis.setRange(minValue, maxValue);
 
306
        } else {
 
307
            if (axisOrigin > minValue) {
 
308
                minValue = axisOrigin;
 
309
            }
 
310
            scaleAxis.setRange(maxValue, minValue);
 
311
        }
 
312
    }
 
313
 
 
314
    public void setRootAge(double rootAge) {
 
315
        double rootLength = timeScale.getHeight(rootAge, tree) - tree.getHeight(tree.getRootNode());
 
316
        treeLayout.setRootLength(rootLength);
 
317
        calibrated = false;
 
318
        repaint();
 
319
    }
 
320
 
 
321
    public double getRootAge() {
 
322
        double treeHeight = tree.getHeight(tree.getRootNode()) + treeLayout.getRootLength();
 
323
        return timeScale.getAge(treeHeight, tree);
 
324
    }
 
325
 
 
326
    public double getMajorTickSpacing() {
 
327
        return scaleAxis.getMajorTickSpacing();
 
328
    }
 
329
 
 
330
    public double getMinorTickSpacing() {
 
331
        return scaleAxis.getMinorTickSpacing();
 
332
    }
 
333
 
 
334
    public void setTickSpacing(double userMajorTickSpacing, double userMinorTickSpacing) {
 
335
        scaleAxis.setManualAxis(userMajorTickSpacing, userMinorTickSpacing);
 
336
        calibrated = false;
 
337
        repaint();
 
338
    }
 
339
 
 
340
    public void setAutomaticScale() {
 
341
        scaleAxis.setAutomatic();
 
342
        calibrated = false;
 
343
        repaint();
 
344
    }
 
345
 
 
346
    public void painterChanged() {
 
347
        calibrated = false;
 
348
        repaint();
 
349
    }
 
350
 
 
351
    public void painterSettingsChanged() {
 
352
        calibrated = false;
 
353
        repaint();
 
354
    }
 
355
 
 
356
    public void attributesChanged() {
 
357
        calibrated = false;
 
358
        repaint();
 
359
    }
 
360
 
 
361
    public BasicStroke getBranchStroke() {
 
362
        return branchLineStroke;
 
363
    }
 
364
 
 
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);
 
369
        repaint();
 
370
    }
 
371
 
 
372
    public BasicStroke getCalloutStroke() {
 
373
        return calloutStroke;
 
374
    }
 
375
 
 
376
    public void setCalloutStroke(BasicStroke calloutStroke) {
 
377
        this.calloutStroke = calloutStroke;
 
378
    }
 
379
 
 
380
    public Paint getSelectionPaint() {
 
381
        return selectionPaint;
 
382
    }
 
383
 
 
384
    public void setSelectionColor(Color selectionColor) {
 
385
        this.selectionPaint = new Color(
 
386
                selectionColor.getRed(),
 
387
                selectionColor.getGreen(),
 
388
                selectionColor.getBlue(),
 
389
                128);
 
390
    }
 
391
 
 
392
    public boolean isTransformBranchesOn() {
 
393
        return transformBranchesOn;
 
394
    }
 
395
 
 
396
    public void setTransformBranchesOn(boolean transformBranchesOn) {
 
397
        this.transformBranchesOn = transformBranchesOn;
 
398
        setupTree();
 
399
    }
 
400
 
 
401
    public TransformedRootedTree.Transform getBranchTransform() {
 
402
        return branchTransform;
 
403
    }
 
404
 
 
405
    public void setBranchTransform(TransformedRootedTree.Transform branchTransform) {
 
406
        this.branchTransform = branchTransform;
 
407
        setupTree();
 
408
    }
 
409
 
 
410
    public boolean isOrderBranchesOn() {
 
411
        return orderBranchesOn;
 
412
    }
 
413
 
 
414
    public void setOrderBranchesOn(boolean orderBranchesOn) {
 
415
        this.orderBranchesOn = orderBranchesOn;
 
416
        setupTree();
 
417
    }
 
418
 
 
419
    public SortedRootedTree.BranchOrdering getBranchOrdering() {
 
420
        return branchOrdering;
 
421
    }
 
422
 
 
423
    public void setBranchOrdering(SortedRootedTree.BranchOrdering branchOrdering) {
 
424
        this.branchOrdering = branchOrdering;
 
425
        setupTree();
 
426
    }
 
427
 
 
428
    public boolean isRootingOn() {
 
429
        return isRootingOn;
 
430
    }
 
431
 
 
432
    public RootingType getRootingType() {
 
433
        return rootingType;
 
434
    }
 
435
 
 
436
    public void setRootingOn(boolean rootingOn) {
 
437
        this.isRootingOn = rootingOn;
 
438
        setupTree();
 
439
    }
 
440
 
 
441
    public void setRootingType(RootingType rootingType) {
 
442
        this.rootingType = rootingType;
 
443
        setupTree();
 
444
    }
 
445
 
 
446
    public RootedTree getOriginalTree() {
 
447
        return originalTree;
 
448
    }
 
449
 
 
450
    public boolean isShowingTipCallouts() {
 
451
        return showingTipCallouts;
 
452
    }
 
453
 
 
454
    public void setShowingTipCallouts(boolean showingTipCallouts) {
 
455
        this.showingTipCallouts = showingTipCallouts;
 
456
        calibrated = false;
 
457
        repaint();
 
458
    }
 
459
 
 
460
    public void setSelectedNode(Node selectedNode) {
 
461
        selectedNodes.clear();
 
462
        selectedTips.clear();
 
463
        addSelectedNode(selectedNode);
 
464
    }
 
465
 
 
466
    public void setSelectedTip(Node selectedTip) {
 
467
        selectedNodes.clear();
 
468
        selectedTips.clear();
 
469
        addSelectedTip(selectedTip);
 
470
    }
 
471
 
 
472
    public void setSelectedClade(Node selectedNode) {
 
473
        selectedNodes.clear();
 
474
        selectedTips.clear();
 
475
        addSelectedClade(selectedNode);
 
476
    }
 
477
 
 
478
    public void setSelectedTips(Node selectedNode) {
 
479
        selectedNodes.clear();
 
480
        selectedTips.clear();
 
481
        addSelectedTips(selectedNode);
 
482
    }
 
483
 
 
484
    private boolean canSelectNode(Node selectedNode) {
 
485
        return selectedNode != null;
 
486
    }
 
487
    public void addSelectedNode(Node selectedNode) {
 
488
        if ( canSelectNode(selectedNode) ) {
 
489
            selectedNodes.add(selectedNode);
 
490
        }
 
491
        fireSelectionChanged();
 
492
        clearSelectionPaths();
 
493
        repaint();
 
494
    }
 
495
 
 
496
    public void addSelectedTip(Node selectedTip) {
 
497
        if (selectedTip != null) {
 
498
            this.selectedNodes
 
499
                    .add(selectedTip);
 
500
        }
 
501
        selectTipsFromSelectedNodes();
 
502
        fireSelectionChanged();
 
503
        clearSelectionPaths();
 
504
        repaint();
 
505
    }
 
506
 
 
507
    public void addSelectedClade(Node selectedNode) {
 
508
        if ( canSelectNode(selectedNode) ) {
 
509
            addSelectedChildClades(selectedNode);
 
510
        }
 
511
        fireSelectionChanged();
 
512
        clearSelectionPaths();
 
513
        repaint();
 
514
    }
 
515
 
 
516
    private void addSelectedChildClades(Node selectedNode) {
 
517
        selectedNodes.add(selectedNode);
 
518
        for (Node child : tree.getChildren(selectedNode)) {
 
519
            addSelectedChildClades(child);
 
520
        }
 
521
    }
 
522
 
 
523
    public void addSelectedTips(Node selectedNode) {
 
524
        if (selectedNode != null) {
 
525
            addSelectedChildTips(selectedNode);
 
526
        }
 
527
        fireSelectionChanged();
 
528
        clearSelectionPaths();
 
529
        repaint();
 
530
    }
 
531
 
 
532
    private void addSelectedChildTips(Node selectedNode) {
 
533
        if (tree.isExternal(selectedNode)) {
 
534
            selectedTips.add(selectedNode);
 
535
        }
 
536
        for (Node child : tree.getChildren(selectedNode)) {
 
537
            addSelectedChildTips(child);
 
538
        }
 
539
    }
 
540
 
 
541
    public void selectCladesFromSelectedNodes() {
 
542
        Set<Node> nodes = new HashSet<Node>(selectedNodes);
 
543
        selectedNodes.clear();
 
544
        for (Node node : nodes) {
 
545
            addSelectedClade(node);
 
546
        }
 
547
        fireSelectionChanged();
 
548
        clearSelectionPaths();
 
549
        repaint();
 
550
    }
 
551
 
 
552
    public void selectTipsFromSelectedNodes() {
 
553
        for (Node node : selectedNodes) {
 
554
            addSelectedChildTips(node);
 
555
        }
 
556
        selectedNodes.clear();
 
557
        fireSelectionChanged();
 
558
        clearSelectionPaths();
 
559
        repaint();
 
560
    }
 
561
 
 
562
    public void selectNodesFromSelectedTips() {
 
563
        if (selectedTips.size() > 0) {
 
564
            Node node = RootedTreeUtils.getCommonAncestorNode(tree, selectedTips);
 
565
            addSelectedClade(node);
 
566
        }
 
567
 
 
568
        selectedTips.clear();
 
569
        fireSelectionChanged();
 
570
        clearSelectionPaths();
 
571
        repaint();
 
572
    }
 
573
 
 
574
    public void selectAllTaxa() {
 
575
        selectedTips.addAll(tree.getExternalNodes());
 
576
        fireSelectionChanged();
 
577
        clearSelectionPaths();
 
578
        repaint();
 
579
    }
 
580
 
 
581
    public void selectAllNodes() {
 
582
        selectedNodes.addAll(tree.getNodes());
 
583
        fireSelectionChanged();
 
584
        clearSelectionPaths();
 
585
        repaint();
 
586
    }
 
587
 
 
588
    public void clearSelection() {
 
589
        selectedNodes.clear();
 
590
        selectedTips.clear();
 
591
        fireSelectionChanged();
 
592
        clearSelectionPaths();
 
593
        repaint();
 
594
    }
 
595
 
 
596
    public boolean hasSelection() {
 
597
        return selectedNodes.size() > 0 || selectedTips.size() > 0;
 
598
    }
 
599
 
 
600
    public void cartoonSelectedNodes() {
 
601
        cartoonSelectedNodes(tree.getRootNode());
 
602
    }
 
603
 
 
604
    private void cartoonSelectedNodes(Node node) {
 
605
 
 
606
        if (!tree.isExternal(node)) {
 
607
            if (selectedNodes.contains(node)) {
 
608
                if (node.getAttribute(CARTOON_ATTRIBUTE_NAME) != null) {
 
609
                    node.removeAttribute(CARTOON_ATTRIBUTE_NAME);
 
610
                } else {
 
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);
 
615
                }
 
616
                calibrated = false;
 
617
                repaint();
 
618
            } else {
 
619
                for (Node child : tree.getChildren(node)) {
 
620
                    cartoonSelectedNodes(child);
 
621
                }
 
622
            }
 
623
        }
 
624
    }
 
625
 
 
626
    public void collapseSelectedNodes() {
 
627
        collapseSelectedNodes(tree.getRootNode());
 
628
    }
 
629
 
 
630
    private void collapseSelectedNodes(Node node) {
 
631
 
 
632
        if (!tree.isExternal(node)) {
 
633
            if (selectedNodes.contains(node)) {
 
634
                if (node.getAttribute(COLLAPSE_ATTRIBUTE_NAME) != null) {
 
635
                    node.removeAttribute(COLLAPSE_ATTRIBUTE_NAME);
 
636
                } else {
 
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);
 
641
                }
 
642
                calibrated = false;
 
643
                repaint();
 
644
            } else {
 
645
                for (Node child : tree.getChildren(node)) {
 
646
                    collapseSelectedNodes(child);
 
647
                }
 
648
            }
 
649
        }
 
650
    }
 
651
 
 
652
    public void hilightSelectedNodes(Color color) {
 
653
        hilightSelectedNodes(tree.getRootNode(), color);
 
654
    }
 
655
 
 
656
    private void hilightSelectedNodes(Node node, Color color) {
 
657
 
 
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);
 
664
 
 
665
                calibrated = false;
 
666
                repaint();
 
667
            } else {
 
668
                for (Node child : tree.getChildren(node)) {
 
669
                    hilightSelectedNodes(child, color);
 
670
                }
 
671
            }
 
672
        }
 
673
    }
 
674
 
 
675
    public void recalculateCollapsedNodes() {
 
676
        recalculateCollapsedNodes(tree.getRootNode());
 
677
    }
 
678
 
 
679
    private void recalculateCollapsedNodes(Node node) {
 
680
 
 
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);
 
688
                }
 
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);
 
694
                }
 
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);
 
701
                }
 
702
                calibrated = false;
 
703
                repaint();
 
704
            } else {
 
705
                for (Node child : tree.getChildren(node)) {
 
706
                    recalculateCollapsedNodes(child);
 
707
                }
 
708
            }
 
709
        }
 
710
    }
 
711
 
 
712
    public void clearCollapsedNodes() {
 
713
        clearSelectedCollapsedNodes(tree.getRootNode());
 
714
    }
 
715
 
 
716
    private void clearSelectedCollapsedNodes(Node node) {
 
717
 
 
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
 
721
            // collapsed nodes.
 
722
            if (selectedNodes.contains(node)) {
 
723
                if (node.getAttribute(COLLAPSE_ATTRIBUTE_NAME) != null) {
 
724
                    node.removeAttribute(COLLAPSE_ATTRIBUTE_NAME);
 
725
                }
 
726
                if (node.getAttribute(CARTOON_ATTRIBUTE_NAME) != null) {
 
727
                    node.removeAttribute(CARTOON_ATTRIBUTE_NAME);
 
728
                }
 
729
                calibrated = false;
 
730
                repaint();
 
731
            } else {
 
732
                for (Node child : tree.getChildren(node)) {
 
733
                    clearSelectedCollapsedNodes(child);
 
734
                }
 
735
            }
 
736
        }
 
737
    }
 
738
 
 
739
    public void clearHilightedNodes() {
 
740
        clearSelectedHilightedNodes(tree.getRootNode());
 
741
    }
 
742
 
 
743
    private void clearSelectedHilightedNodes(Node node) {
 
744
 
 
745
        if (!tree.isExternal(node)) {
 
746
            if (selectedNodes.contains(node)) {
 
747
                if (node.getAttribute(HILIGHT_ATTRIBUTE_NAME) != null) {
 
748
                    node.removeAttribute(HILIGHT_ATTRIBUTE_NAME);
 
749
                    calibrated = false;
 
750
                    repaint();
 
751
                }
 
752
 
 
753
                for (Node child : tree.getChildren(node)) {
 
754
                    clearSelectedHilightedNodes(child);
 
755
                }
 
756
            }
 
757
        }
 
758
    }
 
759
 
 
760
    public void rerootOnSelectedBranch() {
 
761
 
 
762
        for (Node selectedNode : selectedNodes) {
 
763
            setRootLocation(selectedNode, 0.5);
 
764
            // root on the first selected branch...
 
765
            // Check for multiple selected branch elsewhere
 
766
            return;
 
767
        }
 
768
        repaint();
 
769
    }
 
770
 
 
771
    public void clearRooting() {
 
772
        rootingNode = null;
 
773
 
 
774
        setupTree();
 
775
 
 
776
        fireSettingsChanged();
 
777
    }
 
778
 
 
779
    public void rotateSelectedNode() {
 
780
        for (Node selectedNode : selectedNodes) {
 
781
            rotateNode(selectedNode);
 
782
        }
 
783
        repaint();
 
784
    }
 
785
 
 
786
    public void clearSelectedNodeRotations() {
 
787
        for (Node selectedNode : selectedNodes) {
 
788
            clearRotation(selectedNode);
 
789
        }
 
790
        repaint();
 
791
    }
 
792
 
 
793
    public void annotateSelectedNodes(String name, Object value) {
 
794
        for (Node selectedNode : selectedNodes) {
 
795
            selectedNode.setAttribute(name, value);
 
796
        }
 
797
        repaint();
 
798
    }
 
799
 
 
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?");
 
805
//            }
 
806
            selectedTaxon.setAttribute(name, value);
 
807
        }
 
808
        repaint();
 
809
    }
 
810
 
 
811
    public void clearSelectedNodeAnnotation(String name) {
 
812
        for (Node selectedNode : selectedNodes) {
 
813
            selectedNode.removeAttribute(name);
 
814
        }
 
815
        repaint();
 
816
    }
 
817
 
 
818
    public void clearSelectedTipAnnotation(String name) {
 
819
        for (Node selectedTip : selectedTips) {
 
820
            Taxon selectedTaxon = tree.getTaxon(selectedTip);
 
821
            selectedTaxon.removeAttribute(name);
 
822
        }
 
823
        repaint();
 
824
    }
 
825
 
 
826
    /**
 
827
     * Return whether the two axis scales should be maintained
 
828
     * relative to each other
 
829
     *
 
830
     * @return a boolean
 
831
     */
 
832
    public boolean maintainAspectRatio() {
 
833
        return treeLayout.maintainAspectRatio();
 
834
    }
 
835
 
 
836
    public void setTipLabelPainter(LabelPainter<Node> tipLabelPainter) {
 
837
        tipLabelPainter.setTreePane(this);
 
838
        if (this.tipLabelPainter != null) {
 
839
            this.tipLabelPainter.removePainterListener(this);
 
840
        }
 
841
        this.tipLabelPainter = tipLabelPainter;
 
842
        if (this.tipLabelPainter != null) {
 
843
            this.tipLabelPainter.addPainterListener(this);
 
844
        }
 
845
        calibrated = false;
 
846
        repaint();
 
847
    }
 
848
 
 
849
    public LabelPainter<Node> getTipLabelPainter() {
 
850
        return tipLabelPainter;
 
851
    }
 
852
 
 
853
    public void setNodeLabelPainter(LabelPainter<Node> nodeLabelPainter) {
 
854
        nodeLabelPainter.setTreePane(this);
 
855
        if (this.nodeLabelPainter != null) {
 
856
            this.nodeLabelPainter.removePainterListener(this);
 
857
        }
 
858
        this.nodeLabelPainter = nodeLabelPainter;
 
859
        if (this.nodeLabelPainter != null) {
 
860
            this.nodeLabelPainter.addPainterListener(this);
 
861
        }
 
862
        calibrated = false;
 
863
        repaint();
 
864
    }
 
865
 
 
866
    public LabelPainter<Node> getNodeLabelPainter() {
 
867
        return nodeLabelPainter;
 
868
    }
 
869
 
 
870
    public void setBranchLabelPainter(LabelPainter<Node> branchLabelPainter) {
 
871
        branchLabelPainter.setTreePane(this);
 
872
        if (this.branchLabelPainter != null) {
 
873
            this.branchLabelPainter.removePainterListener(this);
 
874
        }
 
875
        this.branchLabelPainter = branchLabelPainter;
 
876
        if (this.branchLabelPainter != null) {
 
877
            this.branchLabelPainter.addPainterListener(this);
 
878
        }
 
879
        calibrated = false;
 
880
        repaint();
 
881
    }
 
882
 
 
883
    public LabelPainter<Node> getBranchLabelPainter() {
 
884
        return branchLabelPainter;
 
885
    }
 
886
 
 
887
    public void setNodeBarPainter(NodeBarPainter nodeBarPainter) {
 
888
        nodeBarPainter.setTreePane(this);
 
889
        if (this.nodeBarPainter != null) {
 
890
            this.nodeBarPainter.removePainterListener(this);
 
891
        }
 
892
        this.nodeBarPainter = nodeBarPainter;
 
893
        if (this.nodeBarPainter != null) {
 
894
            this.nodeBarPainter.addPainterListener(this);
 
895
        }
 
896
        calibrated = false;
 
897
        repaint();
 
898
    }
 
899
 
 
900
    public NodeBarPainter getNodeBarPainter() {
 
901
        return nodeBarPainter;
 
902
    }
 
903
 
 
904
    public void addScalePainter(ScalePainter scalePainter) {
 
905
        assert scalePainter != null;
 
906
 
 
907
        scalePainter.setTreePane(this);
 
908
        scalePainter.addPainterListener(this);
 
909
 
 
910
        scalePainters.add(scalePainter);
 
911
 
 
912
        calibrated = false;
 
913
        repaint();
 
914
    }
 
915
 
 
916
    public void removeScalePainter(ScalePainter scalePainter) {
 
917
        assert scalePainter != null;
 
918
 
 
919
        scalePainter.removePainterListener(this);
 
920
 
 
921
        scalePainters.remove(scalePainter);
 
922
 
 
923
        calibrated = false;
 
924
        repaint();
 
925
    }
 
926
 
 
927
    public void setScaleGridPainter(ScaleGridPainter scaleGridPainter) {
 
928
        scaleGridPainter.setTreePane(this);
 
929
        if (this.scaleGridPainter != null) {
 
930
            this.scaleGridPainter.removePainterListener(this);
 
931
        }
 
932
        this.scaleGridPainter = scaleGridPainter;
 
933
        if (this.scaleGridPainter != null) {
 
934
            this.scaleGridPainter.addPainterListener(this);
 
935
        }
 
936
        calibrated = false;
 
937
        repaint();
 
938
    }
 
939
 
 
940
    public void setPreferredSize(Dimension dimension) {
 
941
        if (treeLayout.maintainAspectRatio()) {
 
942
            super.setPreferredSize(new Dimension(dimension.width, dimension.height));
 
943
        } else {
 
944
            super.setPreferredSize(dimension);
 
945
        }
 
946
 
 
947
        calibrated = false;
 
948
    }
 
949
 
 
950
    public double getHeightAt(Graphics2D graphics2D, Point2D point) {
 
951
        try {
 
952
            point = transform.inverseTransform(point, null);
 
953
        } catch (NoninvertibleTransformException e) {
 
954
            e.printStackTrace();
 
955
        }
 
956
        return treeLayout.getHeightOfPoint(point);
 
957
    }
 
958
 
 
959
    public Node getNodeAt(Graphics2D g2, Point point) {
 
960
        Rectangle rect = new Rectangle(point.x - 1, point.y - 1, 3, 3);
 
961
 
 
962
        for (Node node : tree.getExternalNodes()) {
 
963
            Shape taxonLabelBound = tipLabelBounds.get(node);
 
964
 
 
965
            if (taxonLabelBound != null && g2.hit(rect, taxonLabelBound, false)) {
 
966
                return node;
 
967
            }
 
968
        }
 
969
 
 
970
        if (transform == null) return null;
 
971
 
 
972
        for (Node node : tree.getNodes()) {
 
973
            Shape branchPath = transform.createTransformedShape(treeLayoutCache.getBranchPath(node));
 
974
            if (branchPath != null && g2.hit(rect, branchPath, true)) {
 
975
                return node;
 
976
            }
 
977
            Shape collapsedShape = transform.createTransformedShape(treeLayoutCache.getCollapsedShape(node));
 
978
            if (collapsedShape != null && g2.hit(rect, collapsedShape, false)) {
 
979
                return node;
 
980
            }
 
981
        }
 
982
 
 
983
        return null;
 
984
    }
 
985
 
 
986
    public Set<Node> getNodesAt(Graphics2D g2, Rectangle rect) {
 
987
 
 
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)) {
 
992
                nodes.add(node);
 
993
            }
 
994
        }
 
995
 
 
996
        for (Node node : tree.getNodes()) {
 
997
            Shape branchPath = transform.createTransformedShape(treeLayoutCache.getBranchPath(node));
 
998
            if (branchPath != null && g2.hit(rect, branchPath, true)) {
 
999
                nodes.add(node);
 
1000
            }
 
1001
            Shape collapsedShape = transform.createTransformedShape(treeLayoutCache.getCollapsedShape(node));
 
1002
            if (collapsedShape != null && g2.hit(rect, collapsedShape, false)) {
 
1003
                nodes.add(node);
 
1004
            }
 
1005
        }
 
1006
 
 
1007
        return nodes;
 
1008
    }
 
1009
 
 
1010
    public Set<Node> getSelectedNodes() {
 
1011
        return selectedNodes;
 
1012
    }
 
1013
 
 
1014
    public Set<Node> getSelectedTips() {
 
1015
        return selectedTips;
 
1016
    }
 
1017
 
 
1018
    public Rectangle2D getDragRectangle() {
 
1019
        return dragRectangle;
 
1020
    }
 
1021
 
 
1022
    public void setDragRectangle(Rectangle2D dragRectangle) {
 
1023
        this.dragRectangle = dragRectangle;
 
1024
        repaint();
 
1025
    }
 
1026
 
 
1027
    public void setRuler(double rulerHeight) {
 
1028
        this.rulerHeight = rulerHeight;
 
1029
    }
 
1030
 
 
1031
    public void scrollPointToVisible(Point point) {
 
1032
        scrollRectToVisible(new Rectangle(point.x, point.y, 0, 0));
 
1033
    }
 
1034
 
 
1035
 
 
1036
    private final Set<TreeSelectionListener> treeSelectionListeners = new HashSet<TreeSelectionListener>();
 
1037
 
 
1038
    public void addTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
 
1039
        treeSelectionListeners.add(treeSelectionListener);
 
1040
    }
 
1041
 
 
1042
    public void removeTreeSelectionListener(TreeSelectionListener treeSelectionListener) {
 
1043
        treeSelectionListeners.remove(treeSelectionListener);
 
1044
    }
 
1045
 
 
1046
    private void fireSelectionChanged() {
 
1047
        for (TreeSelectionListener treeSelectionListener : treeSelectionListeners) {
 
1048
            treeSelectionListener.selectionChanged();
 
1049
        }
 
1050
    }
 
1051
 
 
1052
    private final Set<TreePaneListener> treePaneListeners = new HashSet<TreePaneListener>();
 
1053
 
 
1054
    public void addTreePaneListener(TreePaneListener treePaneListener) {
 
1055
        treePaneListeners.add(treePaneListener);
 
1056
    }
 
1057
 
 
1058
    public void removeTreePaneListener(TreePaneListener treePaneListener) {
 
1059
        treePaneListeners.remove(treePaneListener);
 
1060
    }
 
1061
 
 
1062
    private void fireSettingsChanged() {
 
1063
        for (TreePaneListener treePaneListener : treePaneListeners) {
 
1064
            treePaneListener.treePaneSettingsChanged();
 
1065
        }
 
1066
    }
 
1067
 
 
1068
    public void paint(Graphics graphics) {
 
1069
        if (tree == null) return;
 
1070
 
 
1071
        graphics.setColor(Color.white);
 
1072
        Rectangle r = graphics.getClipBounds();
 
1073
        if (r != null) {
 
1074
            graphics.fillRect(r.x,  r.y, r.width, r.height);
 
1075
        }
 
1076
 
 
1077
        final Graphics2D g2 = (Graphics2D) graphics;
 
1078
        if (!calibrated) {
 
1079
            calibrate(g2, getWidth(), getHeight());
 
1080
        }
 
1081
 
 
1082
        drawTree(g2, getWidth(), getHeight());
 
1083
 
 
1084
        Paint oldPaint = g2.getPaint();
 
1085
        Stroke oldStroke = g2.getStroke();
 
1086
 
 
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()));
 
1094
//
 
1095
//                      g2.draw(new Line2D.Double(0.0, y, getWidth(), y));
 
1096
//                      g2.draw(new Line2D.Double(x, 0.0, x, getHeight()));
 
1097
//
 
1098
//              }
 
1099
 
 
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);
 
1107
 
 
1108
                }
 
1109
                Shape collapsedShape = treeLayoutCache.getCollapsedShape(selectedNode);
 
1110
                if (collapsedShape != null) {
 
1111
                    Shape transPath = transform.createTransformedShape(collapsedShape);
 
1112
                    branchSelection.append(transPath, false);
 
1113
                }
 
1114
            }
 
1115
        }
 
1116
 
 
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);
 
1123
                }
 
1124
            }
 
1125
        }
 
1126
 
 
1127
        g2.setPaint(selectionPaint);
 
1128
        g2.setStroke(selectionStroke);
 
1129
        g2.draw(branchSelection);
 
1130
        g2.fill(labelSelection);
 
1131
 
 
1132
        g2.setPaint(oldPaint);
 
1133
        g2.setStroke(oldStroke);
 
1134
 
 
1135
        if (dragRectangle != null) {
 
1136
            g2.setPaint(new Color(128, 128, 128, 128));
 
1137
            g2.fill(dragRectangle);
 
1138
        }
 
1139
    }
 
1140
 
 
1141
    private void clearSelectionPaths() {
 
1142
        branchSelection = null;
 
1143
        labelSelection = null;
 
1144
    }
 
1145
 
 
1146
    private GeneralPath branchSelection = null;
 
1147
    private GeneralPath labelSelection = null;
 
1148
 
 
1149
    public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException {
 
1150
 
 
1151
        if (tree == null || pageIndex > 0) return NO_SUCH_PAGE;
 
1152
 
 
1153
        Graphics2D g2 = (Graphics2D) graphics;
 
1154
        g2.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
 
1155
 
 
1156
        calibrated = false;
 
1157
        setDoubleBuffered(false);
 
1158
 
 
1159
        drawTree(g2, pageFormat.getImageableWidth(), pageFormat.getImageableHeight());
 
1160
 
 
1161
        setDoubleBuffered(true);
 
1162
        calibrated = false;
 
1163
 
 
1164
        return PAGE_EXISTS;
 
1165
    }
 
1166
 
 
1167
    public void drawTree(Graphics2D g2, double width, double height) {
 
1168
 
 
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);
 
1173
        }
 
1174
 
 
1175
        if (!calibrated) calibrate(g2, width, height);
 
1176
 
 
1177
        // save graphics state which draw changes so that upon exit it can be restored
 
1178
 
 
1179
        final AffineTransform oldTransform = g2.getTransform();
 
1180
        final Paint oldPaint = g2.getPaint();
 
1181
        final Stroke oldStroke = g2.getStroke();
 
1182
        final Font oldFont = g2.getFont();
 
1183
 
 
1184
        // Paint scales
 
1185
        for (ScalePainter scalePainter : scalePainters) {
 
1186
 
 
1187
            if (scalePainter.isVisible()) {
 
1188
                Rectangle2D scaleBounds = this.scaleBounds.get(scalePainter);
 
1189
                scalePainter.paint(g2, this, Painter.Justification.CENTER, scaleBounds);
 
1190
            }
 
1191
        }
 
1192
 
 
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);
 
1204
 
 
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);
 
1208
                }
 
1209
            }
 
1210
        }
 
1211
 
 
1212
        // Paint hilighted nodes
 
1213
        for (Node node : treeLayoutCache.getHilightNodesList() ) {
 
1214
            Object[] values = (Object[])node.getAttribute(HILIGHT_ATTRIBUTE_NAME);
 
1215
 
 
1216
            Shape hilightShape = treeLayoutCache.getHilightShape(node);
 
1217
 
 
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);
 
1222
 
 
1223
            if (fillPaint != null) {
 
1224
                g2.setPaint(fillPaint);
 
1225
                g2.fill(transShape);
 
1226
            }
 
1227
 
 
1228
            if (paint != null) {
 
1229
                g2.setPaint(paint);
 
1230
                g2.setStroke(stroke);
 
1231
                g2.draw(transShape);
 
1232
            }
 
1233
        }
 
1234
 
 
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);
 
1240
        }
 
1241
 
 
1242
        if (DEBUG_OUTLINE) {
 
1243
            g2.setPaint(Color.red);
 
1244
            g2.draw(treeBounds);
 
1245
        }
 
1246
 
 
1247
        // Paint collapsed nodes
 
1248
        for (Node node : treeLayoutCache.getCollapsedShapeMap().keySet() ) {
 
1249
            Shape collapsedShape = treeLayoutCache.getCollapsedShape(node);
 
1250
 
 
1251
            Shape transShape = transform.createTransformedShape(collapsedShape);
 
1252
            Paint paint = Color.BLACK;
 
1253
            Paint fillPaint = null;
 
1254
            Stroke stroke = branchLineStroke;
 
1255
 
 
1256
            if (branchDecorator != null) {
 
1257
                branchDecorator.setItem(node);
 
1258
                paint = branchDecorator.getPaint(paint);
 
1259
                fillPaint = branchDecorator.getFillPaint(fillPaint);
 
1260
                stroke = branchDecorator.getStroke(stroke);
 
1261
            }
 
1262
 
 
1263
            if (fillPaint != null) {
 
1264
                g2.setPaint(fillPaint);
 
1265
                g2.fill(transShape);
 
1266
            }
 
1267
 
 
1268
            g2.setPaint(paint);
 
1269
            g2.setStroke(stroke);
 
1270
            g2.draw(transShape);
 
1271
        }
 
1272
 
 
1273
        // Paint branches
 
1274
        for (Node node : treeLayoutCache.getBranchPathMap().keySet() ) {
 
1275
            Stroke stroke = branchLineStroke;
 
1276
            if (branchDecorator != null) {
 
1277
                branchDecorator.setItem(node);
 
1278
                stroke = branchDecorator.getStroke(stroke);
 
1279
            }
 
1280
            g2.setStroke(stroke);
 
1281
 
 
1282
            Object[] branchColouring = null;
 
1283
            if (treeLayout.isShowingColouring() && branchColouringAttribute != null) {
 
1284
                branchColouring = (Object[])node.getAttribute(branchColouringAttribute);
 
1285
            }
 
1286
 
 
1287
            Shape branchPath = treeLayoutCache.getBranchPath(node);
 
1288
            Shape nodePath = treeLayoutCache.getNodeArea(node);
 
1289
 
 
1290
            if (branchColouring != null) {
 
1291
                PathIterator iter = branchPath.getPathIterator(transform);
 
1292
 
 
1293
                float[] coords1 = new float[2];
 
1294
                iter.currentSegment(coords1);
 
1295
 
 
1296
                for (int i = 0; i < branchColouring.length - 1; i+=2) {
 
1297
                    iter.next();
 
1298
                    float[] coords2 = new float[2];
 
1299
                    iter.currentSegment(coords2);
 
1300
 
 
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]));
 
1305
 
 
1306
                    coords1 = coords2;
 
1307
                }
 
1308
 
 
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));
 
1313
 
 
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);
 
1318
 
 
1319
                // and draw it...
 
1320
                g2.draw(path);
 
1321
 
 
1322
            } else {
 
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]);
 
1332
                        do {
 
1333
                            iter.currentSegment(coords);
 
1334
                            iter.next();
 
1335
                        } while (!iter.isDone());
 
1336
                        Point2D point2 = new Point2D.Double(coords[0], coords[1]);
 
1337
 
 
1338
                        paint = branchDecorator.getPaint(paint, point1, point2);
 
1339
                    } else {
 
1340
                        branchDecorator.setItem(node);
 
1341
                        paint = branchDecorator.getPaint(paint);
 
1342
                    }
 
1343
                }
 
1344
                g2.setPaint(paint);
 
1345
                g2.draw(transPath);
 
1346
            }
 
1347
        }
 
1348
 
 
1349
        // Paint node bars
 
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);
 
1355
            }
 
1356
        }
 
1357
 
 
1358
        // Paint tip labels
 
1359
        if (tipLabelPainter != null && tipLabelPainter.isVisible()) {
 
1360
 
 
1361
            for (Node node : tipLabelTransforms.keySet()) {
 
1362
 
 
1363
                AffineTransform tipLabelTransform = tipLabelTransforms.get(node);
 
1364
 
 
1365
                Painter.Justification tipLabelJustification = tipLabelJustifications.get(node);
 
1366
                g2.transform(tipLabelTransform);
 
1367
 
 
1368
                tipLabelPainter.paint(g2, node, tipLabelJustification,
 
1369
                        new Rectangle2D.Double(0.0, 0.0, tipLabelWidth, tipLabelPainter.getPreferredHeight()));
 
1370
 
 
1371
                g2.setTransform(oldTransform);
 
1372
 
 
1373
                if (showingTipCallouts) {
 
1374
                    Shape calloutPath = transform.createTransformedShape(treeLayoutCache.getCalloutPath(node));
 
1375
                    if (calloutPath != null) {
 
1376
                        g2.setStroke(calloutStroke);
 
1377
                        g2.draw(calloutPath);
 
1378
                    }
 
1379
                }
 
1380
            }
 
1381
        }
 
1382
 
 
1383
        // Paint node labels
 
1384
        if (nodeLabelPainter != null && nodeLabelPainter.isVisible()) {
 
1385
            for (Node node : nodeLabelTransforms.keySet() ) {
 
1386
 
 
1387
                AffineTransform nodeTransform = nodeLabelTransforms.get(node);
 
1388
 
 
1389
                Painter.Justification nodeLabelJustification = nodeLabelJustifications.get(node);
 
1390
                g2.transform(nodeTransform);
 
1391
 
 
1392
                nodeLabelPainter.paint(g2, node, nodeLabelJustification,
 
1393
                        new Rectangle2D.Double(0.0, 0.0, nodeLabelPainter.getPreferredWidth(), nodeLabelPainter.getPreferredHeight()));
 
1394
 
 
1395
                g2.setTransform(oldTransform);
 
1396
            }
 
1397
        }
 
1398
 
 
1399
        // Paint branch labels
 
1400
        if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
 
1401
 
 
1402
            for (Node node : branchLabelTransforms.keySet() ) {
 
1403
 
 
1404
                AffineTransform branchTransform = branchLabelTransforms.get(node);
 
1405
 
 
1406
                g2.transform(branchTransform);
 
1407
 
 
1408
                branchLabelPainter.calibrate(g2, node);
 
1409
                final double preferredWidth = branchLabelPainter.getPreferredWidth();
 
1410
                final double preferredHeight = branchLabelPainter.getPreferredHeight();
 
1411
 
 
1412
                //Line2D labelPath = treeLayout.getBranchLabelPath(node);
 
1413
 
 
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));
 
1417
 
 
1418
                g2.setTransform(oldTransform);
 
1419
            }
 
1420
        }
 
1421
 
 
1422
        g2.setStroke(oldStroke);
 
1423
        g2.setPaint(oldPaint);
 
1424
        g2.setFont(oldFont);
 
1425
    }
 
1426
 
 
1427
    private void calibrate(Graphics2D g2, double width, double height) {
 
1428
 
 
1429
        // First layout the tree
 
1430
        treeLayout.layout(tree, treeLayoutCache);
 
1431
 
 
1432
        // Now rescale the scale axis
 
1433
        setupScaleAxis();
 
1434
 
 
1435
        // First of all get the bounds for the unscaled tree
 
1436
        treeBounds = null;
 
1437
 
 
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
 
1440
 
 
1441
        // The bounds are then the extra stuff that doesn't get scaled with the tree such
 
1442
        // as labels and the like.
 
1443
 
 
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;
 
1450
            } else {
 
1451
                treeBounds.add(branchBounds);
 
1452
            }
 
1453
        }
 
1454
 
 
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);
 
1461
        }
 
1462
 
 
1463
 
 
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;
 
1469
            } else {
 
1470
                treeBounds.add(branchBounds);
 
1471
            }
 
1472
        }
 
1473
 
 
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;
 
1479
            } else {
 
1480
                treeBounds.add(branchBounds);
 
1481
            }
 
1482
        }
 
1483
 
 
1484
        // bounds on nodeShapes
 
1485
        if (!isTransformBranchesOn() && nodeBarPainter != null && nodeBarPainter.isVisible()) {
 
1486
            nodeBars.clear();
 
1487
            // Iterate though the nodes
 
1488
            for (Node node : tree.getInternalNodes()) {
 
1489
 
 
1490
                Rectangle2D shapeBounds = nodeBarPainter.calibrate(g2, node);
 
1491
                treeBounds.add(shapeBounds);
 
1492
                nodeBars.put(node, nodeBarPainter.getNodeBar());
 
1493
            }
 
1494
        }
 
1495
 
 
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());
 
1498
 
 
1499
        // add the tree bounds
 
1500
        final Rectangle2D totalTreeBounds = treeBounds.getBounds2D(); // (YH) same as (Rectangle2D) treeBounds.clone();
 
1501
 
 
1502
        final Set<Node> externalNodes = tree.getExternalNodes();
 
1503
 
 
1504
        if (tipLabelPainter != null && tipLabelPainter.isVisible()) {
 
1505
 
 
1506
            tipLabelWidth = 0.0;
 
1507
 
 
1508
            // Find the longest taxon label
 
1509
            for (Node node : externalNodes) {
 
1510
 
 
1511
                tipLabelPainter.calibrate(g2, node);
 
1512
                tipLabelWidth = Math.max(tipLabelWidth, tipLabelPainter.getPreferredWidth());
 
1513
            }
 
1514
 
 
1515
            final double tipLabelHeight = tipLabelPainter.getPreferredHeight();
 
1516
 
 
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);
 
1520
 
 
1521
                // Get the line that represents the path for the taxon label
 
1522
                Line2D taxonPath = treeLayoutCache.getTipLabelPath(node);
 
1523
 
 
1524
                // Work out how it is rotated and create a transform that matches that
 
1525
                AffineTransform taxonTransform = calculateTransform(null, taxonPath, tipLabelWidth, tipLabelHeight, true);
 
1526
 
 
1527
                // and add the translated bounds to the overall bounds
 
1528
                totalTreeBounds.add(taxonTransform.createTransformedShape(labelBounds).getBounds2D());
 
1529
            }
 
1530
        }
 
1531
 
 
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);
 
1537
 
 
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);
 
1542
 
 
1543
                // Work out how it is rotated and create a transform that matches that
 
1544
                AffineTransform labelTransform = calculateTransform(null, labelPath, labelWidth, labelHeight, true);
 
1545
 
 
1546
                // and add the translated bounds to the overall bounds
 
1547
                totalTreeBounds.add(labelTransform.createTransformedShape(labelBounds).getBounds2D());
 
1548
            }
 
1549
        }
 
1550
 
 
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);
 
1556
 
 
1557
                branchLabelPainter.calibrate(g2, node);
 
1558
                final double labelHeight = branchLabelPainter.getHeightBound();
 
1559
                final double labelWidth = branchLabelPainter.getPreferredWidth();
 
1560
 
 
1561
                Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, labelWidth, labelHeight);
 
1562
 
 
1563
                // Work out how it is rotated and create a transform that matches that
 
1564
                AffineTransform labelTransform = calculateTransform(null, labelPath, labelWidth, labelHeight, false);
 
1565
 
 
1566
                // and add the translated bounds to the overall bounds
 
1567
                totalTreeBounds.add(labelTransform.createTransformedShape(labelBounds).getBounds2D());
 
1568
            }
 
1569
        }
 
1570
 
 
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);
 
1582
            }
 
1583
        }
 
1584
        totalTreeBounds.add(totalScaleBounds);
 
1585
 
 
1586
        final double availableW = width - insets.left - insets.right;
 
1587
        final double availableH = height - insets.top - insets.bottom;
 
1588
 
 
1589
        // get the difference between the tree's bounds and the overall bounds
 
1590
        boolean maintainAspectRatio = treeLayout.maintainAspectRatio();
 
1591
 
 
1592
        double xDiff;
 
1593
        double yDiff;
 
1594
 
 
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;
 
1603
 
 
1604
            xDiff = 2.0 * (leftDiff > rightDiff ? leftDiff : rightDiff);
 
1605
            yDiff = 2.0 * (topDiff > bottomDiff ? topDiff : bottomDiff);
 
1606
        } else {
 
1607
            xDiff = totalTreeBounds.getWidth() - treeBounds.getWidth();
 
1608
            yDiff = totalTreeBounds.getHeight() - treeBounds.getHeight();
 
1609
            assert xDiff >= 0 && yDiff >= 0;
 
1610
        }
 
1611
 
 
1612
 
 
1613
        // small tree, long labels, label bounds may get larger that window, protect against that
 
1614
 
 
1615
        if( xDiff >= availableW ) {
 
1616
            xDiff = Math.min(availableW, totalTreeBounds.getWidth()) - treeBounds.getWidth();
 
1617
        }
 
1618
 
 
1619
        if( yDiff >= availableH ) {
 
1620
            yDiff = Math.min(availableH, totalTreeBounds.getHeight()) - treeBounds.getHeight();
 
1621
        }
 
1622
 
 
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
 
1625
 
 
1626
        final double w = availableW - xDiff;
 
1627
        final double h = availableH - yDiff;
 
1628
 
 
1629
        double xScale;
 
1630
        double yScale;
 
1631
 
 
1632
        double xOffset = 0.0;
 
1633
        double yOffset = 0.0;
 
1634
 
 
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.
 
1639
 
 
1640
            if ((w / treeBounds.getWidth()) < (h / treeBounds.getHeight())) {
 
1641
                xScale = w / treeBounds.getWidth();
 
1642
                yScale = xScale;
 
1643
            } else {
 
1644
                yScale = h / treeBounds.getHeight();
 
1645
                xScale = yScale;
 
1646
            }
 
1647
 
 
1648
            treeScale = xScale;   assert treeScale > 0;
 
1649
 
 
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);
 
1654
 
 
1655
        } else {
 
1656
            // Otherwise just scale both dimensions
 
1657
            xScale = w / treeBounds.getWidth();
 
1658
            yScale = h / treeBounds.getHeight();
 
1659
 
 
1660
            // and set the origin in the top left corner
 
1661
            xOffset = - (treeBounds.getX() * xScale);
 
1662
            yOffset = - totalTreeBounds.getY();
 
1663
 
 
1664
            treeScale = xScale;
 
1665
        }
 
1666
 
 
1667
        assert treeScale > 0;
 
1668
 
 
1669
        // Create the overall transform
 
1670
        transform = new AffineTransform();
 
1671
        transform.translate(xOffset + insets.left, yOffset + insets.top);
 
1672
        transform.scale(xScale, yScale);
 
1673
 
 
1674
        // Get the bounds for the newly scaled tree
 
1675
        treeBounds = null;
 
1676
 
 
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;
 
1683
            } else {
 
1684
                treeBounds.add(branchBounds);
 
1685
            }
 
1686
        }
 
1687
 
 
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;
 
1693
            } else {
 
1694
                treeBounds.add(branchBounds);
 
1695
            }
 
1696
        }
 
1697
 
 
1698
        // Clear the map of individual taxon label bounds and transforms
 
1699
        tipLabelBounds.clear();
 
1700
        tipLabelTransforms.clear();
 
1701
        tipLabelJustifications.clear();
 
1702
 
 
1703
        if (tipLabelPainter != null && tipLabelPainter.isVisible()) {
 
1704
            final double labelHeight = tipLabelPainter.getPreferredHeight();
 
1705
            Rectangle2D labelBounds = new Rectangle2D.Double(0.0, 0.0, tipLabelWidth, labelHeight);
 
1706
 
 
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);
 
1711
 
 
1712
                // Work out how it is rotated and create a transform that matches that
 
1713
                AffineTransform taxonTransform = calculateTransform(transform, tipPath, tipLabelWidth, labelHeight, true);
 
1714
 
 
1715
                // Store the transformed bounds in the map for use when selecting
 
1716
                tipLabelBounds.put(node, taxonTransform.createTransformedShape(labelBounds));
 
1717
 
 
1718
                // Store the transform in the map for use when drawing
 
1719
                tipLabelTransforms.put(node, taxonTransform);
 
1720
 
 
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);
 
1725
            }
 
1726
        }
 
1727
 
 
1728
        // Clear the map of individual node label bounds and transforms
 
1729
        nodeLabelBounds.clear();
 
1730
        nodeLabelTransforms.clear();
 
1731
        nodeLabelJustifications.clear();
 
1732
 
 
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);
 
1737
 
 
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);
 
1742
 
 
1743
                // Work out how it is rotated and create a transform that matches that
 
1744
                AffineTransform labelTransform = calculateTransform(transform, labelPath, labelWidth, labelHeight, true);
 
1745
 
 
1746
                // Store the transformed bounds in the map for use when selecting
 
1747
                nodeLabelBounds.put(node, labelTransform.createTransformedShape(labelBounds));
 
1748
 
 
1749
                // Store the transform in the map for use when drawing
 
1750
                nodeLabelTransforms.put(node, labelTransform);
 
1751
 
 
1752
                // Store the alignment in the map for use when drawing
 
1753
                if (labelPath.getX1() < labelPath.getX2()) {
 
1754
                    nodeLabelJustifications.put(node, Painter.Justification.LEFT);
 
1755
                } else {
 
1756
                    nodeLabelJustifications.put(node, Painter.Justification.RIGHT);
 
1757
                }
 
1758
            }
 
1759
        }
 
1760
 
 
1761
        branchLabelBounds.clear();
 
1762
        branchLabelTransforms.clear();
 
1763
        branchLabelJustifications.clear();
 
1764
 
 
1765
        if (branchLabelPainter != null && branchLabelPainter.isVisible()) {
 
1766
 
 
1767
            // Iterate though the external nodes with branch labels
 
1768
            for (Node node : treeLayoutCache.getBranchLabelPathMap().keySet()) {
 
1769
 
 
1770
                // Get the line that represents the path for the branch label
 
1771
                Line2D labelPath = treeLayoutCache.getBranchLabelPath(node);
 
1772
 
 
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);
 
1778
 
 
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);
 
1782
 
 
1783
                final Painter.Justification just = labelPath.getX1() < labelPath.getX2() ? Painter.Justification.LEFT :
 
1784
                        Painter.Justification.RIGHT;
 
1785
 
 
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);
 
1791
 
 
1792
                // Store the transformed bounds in the map for use when selecting
 
1793
                branchLabelBounds.put(node, labelTransform.createTransformedShape(labelBounds));
 
1794
 
 
1795
                // Store the transform in the map for use when drawing
 
1796
                branchLabelTransforms.put(node, labelTransform);
 
1797
 
 
1798
                // Store the alignment in the map for use when drawing
 
1799
                branchLabelJustifications.put(node, just);
 
1800
            }
 
1801
        }
 
1802
 
 
1803
 
 
1804
        y = height;
 
1805
        for (ScalePainter scalePainter : scalePainters) {
 
1806
            if (scalePainter.isVisible()) {
 
1807
                scalePainter.calibrate(g2, this);
 
1808
                y -= scalePainter.getPreferredHeight();
 
1809
            }
 
1810
        }
 
1811
 
 
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);
 
1818
                y += h1;
 
1819
                totalScaleBounds.add(sb);
 
1820
                scaleBounds.put(scalePainter, sb);
 
1821
            }
 
1822
        }
 
1823
 
 
1824
        calloutPaths.clear();
 
1825
        clearSelectionPaths();
 
1826
 
 
1827
        calibrated = true;
 
1828
    }
 
1829
 
 
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);
 
1835
        }
 
1836
 
 
1837
        // Work out how it is rotated and create a transform that matches that
 
1838
        AffineTransform lineTransform = new AffineTransform();
 
1839
 
 
1840
        final double dy = line.getY2() - line.getY1();
 
1841
        // efficency
 
1842
        if( dy != 0.0 ) {
 
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());
 
1846
        }
 
1847
 
 
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();
 
1852
        if( just) {
 
1853
            if (line.getX2() > line.getX1()) {
 
1854
                tx += labelXOffset;
 
1855
            } else {
 
1856
                tx -= (labelXOffset + width);
 
1857
            }
 
1858
        }
 
1859
        lineTransform.translate(tx, ty);
 
1860
        return lineTransform;
 
1861
    }
 
1862
 
 
1863
    // Overridden methods to recalibrate tree when bounds change
 
1864
    public void setBounds(int x, int y, int width, int height) {
 
1865
        calibrated = false;
 
1866
        super.setBounds(x, y, width, height);
 
1867
    }
 
1868
 
 
1869
    public void setBounds(Rectangle rectangle) {
 
1870
        calibrated = false;
 
1871
        super.setBounds(rectangle);
 
1872
    }
 
1873
 
 
1874
    public void setSize(Dimension dimension) {
 
1875
        calibrated = false;
 
1876
        super.setSize(dimension);
 
1877
    }
 
1878
 
 
1879
    public void setSize(int width, int height) {
 
1880
        calibrated = false;
 
1881
        super.setSize(width, height);
 
1882
    }
 
1883
 
 
1884
    private RootedTree originalTree = null;
 
1885
    private RootedTree tree = null;
 
1886
    private TreeLayout treeLayout = null;
 
1887
    private TreeLayoutCache treeLayoutCache = new TreeLayoutCache();
 
1888
 
 
1889
    private boolean orderBranchesOn = false;
 
1890
    private SortedRootedTree.BranchOrdering branchOrdering = SortedRootedTree.BranchOrdering.INCREASING_NODE_DENSITY;
 
1891
 
 
1892
    private boolean transformBranchesOn = false;
 
1893
    private TransformedRootedTree.Transform branchTransform = TransformedRootedTree.Transform.CLADOGRAM;
 
1894
 
 
1895
    private boolean isRootingOn = false;
 
1896
    private RootingType rootingType = RootingType.USER_ROOTING;
 
1897
    private Node rootingNode = null;
 
1898
    private double rootingLength = 0.01;
 
1899
 
 
1900
    private Rectangle2D treeBounds = new Rectangle2D.Double();
 
1901
    private double treeScale;
 
1902
 
 
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);
 
1906
 
 
1907
    //private Insets insets = new Insets(0, 0, 0, 0);
 
1908
    private Insets insets = new Insets(6, 6, 6, 6);
 
1909
 
 
1910
    private Set<Node> selectedNodes = new HashSet<Node>();
 
1911
    private Set<Node> selectedTips = new HashSet<Node>();
 
1912
 
 
1913
    private double rulerHeight = -1.0;
 
1914
    private Rectangle2D dragRectangle = null;
 
1915
    private Point2D cursorPosition = null;
 
1916
 
 
1917
    private boolean isCrosshairShown = true;
 
1918
 
 
1919
    private Decorator branchDecorator = null;
 
1920
    private Decorator branchColouringDecorator = null;
 
1921
    private String branchColouringAttribute = null;
 
1922
 
 
1923
    private Decorator nodeBackgroundDecorator = null;
 
1924
 
 
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;
 
1930
 
 
1931
    private NodeBarPainter nodeBarPainter = null;
 
1932
 
 
1933
    private List<ScalePainter> scalePainters = new ArrayList<ScalePainter>();
 
1934
    private Rectangle2D totalScaleBounds = null;
 
1935
    private Map<ScalePainter, Rectangle2D> scaleBounds = new HashMap<ScalePainter, Rectangle2D>();
 
1936
 
 
1937
    private ScaleGridPainter scaleGridPainter = null;
 
1938
 
 
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;
 
1945
 
 
1946
    private boolean calibrated = false;
 
1947
    private AffineTransform transform = null;
 
1948
 
 
1949
    private boolean showingTipCallouts = true;
 
1950
 
 
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>();
 
1954
 
 
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>();
 
1958
 
 
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>();
 
1962
 
 
1963
    private Map<Node, Shape> nodeBars = new HashMap<Node, Shape>();
 
1964
 
 
1965
    private Map<Node, Shape> calloutPaths = new HashMap<Node, Shape>();
 
1966
 
 
1967
}
 
 
b'\\ No newline at end of file'