d40ab7301f8246759d2605e144b83e12988a1852
[jalview.git] / src / jalview / appletgui / TreeCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.appletgui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24
25 import jalview.analysis.*;
26 import jalview.datamodel.*;
27 import jalview.schemes.*;
28 import jalview.util.*;
29
30 public class TreeCanvas extends Panel implements MouseListener,
31         MouseMotionListener
32 {
33   NJTree tree;
34
35   ScrollPane scrollPane;
36
37   AlignViewport av;
38
39   public static final String PLACEHOLDER = " * ";
40
41   Font font;
42
43   boolean fitToWindow = true;
44
45   boolean showDistances = false;
46
47   boolean showBootstrap = false;
48
49   boolean markPlaceholders = false;
50
51   int offx = 20;
52
53   int offy;
54
55   float threshold;
56
57   String longestName;
58
59   int labelLength = -1;
60
61   Hashtable nameHash = new Hashtable();
62
63   Hashtable nodeHash = new Hashtable();
64
65   SequenceNode highlightNode;
66
67   public TreeCanvas(AlignViewport av, ScrollPane scroller)
68   {
69     this.av = av;
70     font = av.getFont();
71     scrollPane = scroller;
72     addMouseListener(this);
73     addMouseMotionListener(this);
74     setLayout(null);
75
76     PaintRefresher.Register(this, av.getSequenceSetId());
77   }
78
79   public void treeSelectionChanged(SequenceI sequence)
80   {
81     SequenceGroup selected = av.getSelectionGroup();
82     if (selected == null)
83     {
84       selected = new SequenceGroup();
85       av.setSelectionGroup(selected);
86     }
87
88     selected.setEndRes(av.alignment.getWidth() - 1);
89     selected.addOrRemove(sequence, true);
90   }
91
92   public void setTree(NJTree tree)
93   {
94     this.tree = tree;
95     tree.findHeight(tree.getTopNode());
96
97     // Now have to calculate longest name based on the leaves
98     Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
99     boolean has_placeholders = false;
100     longestName = "";
101
102     for (int i = 0; i < leaves.size(); i++)
103     {
104       SequenceNode lf = (SequenceNode) leaves.elementAt(i);
105
106       if (lf.isPlaceholder())
107       {
108         has_placeholders = true;
109       }
110
111       if (longestName.length() < ((Sequence) lf.element()).getName()
112               .length())
113       {
114         longestName = TreeCanvas.PLACEHOLDER
115                 + ((Sequence) lf.element()).getName();
116       }
117     }
118
119     setMarkPlaceholders(has_placeholders);
120   }
121
122   public void drawNode(Graphics g, SequenceNode node, float chunk,
123           float scale, int width, int offx, int offy)
124   {
125     if (node == null)
126     {
127       return;
128     }
129
130     if (node.left() == null && node.right() == null)
131     {
132       // Drawing leaf node
133
134       float height = node.height;
135       float dist = node.dist;
136
137       int xstart = (int) ((height - dist) * scale) + offx;
138       int xend = (int) (height * scale) + offx;
139
140       int ypos = (int) (node.ycount * chunk) + offy;
141
142       if (node.element() instanceof SequenceI)
143       {
144         SequenceI seq = (SequenceI) ((SequenceNode) node).element();
145
146         if (av.getSequenceColour(seq) == Color.white)
147         {
148           g.setColor(Color.black);
149         }
150         else
151         {
152           g.setColor(av.getSequenceColour(seq).darker());
153         }
154
155       }
156       else
157       {
158         g.setColor(Color.black);
159       }
160
161       // Draw horizontal line
162       g.drawLine(xstart, ypos, xend, ypos);
163
164       String nodeLabel = "";
165       if (showDistances && node.dist > 0)
166       {
167         nodeLabel = new Format("%-.2f").form(node.dist);
168       }
169       if (showBootstrap)
170       {
171         int btstrap = node.getBootstrap();
172         if (btstrap > -1)
173         {
174           if (showDistances)
175           {
176             nodeLabel = nodeLabel + " : ";
177           }
178           nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
179         }
180       }
181       if (!nodeLabel.equals(""))
182       {
183         g.drawString(nodeLabel, xstart + 2, ypos - 2);
184       }
185
186       String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
187               .getName()) : node.getName();
188       FontMetrics fm = g.getFontMetrics(font);
189       int charWidth = fm.stringWidth(name) + 3;
190       int charHeight = fm.getHeight();
191
192       Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,
193               charWidth, charHeight);
194
195       nameHash.put((SequenceI) node.element(), rect);
196
197       // Colour selected leaves differently
198       SequenceGroup selected = av.getSelectionGroup();
199       if (selected != null
200               && selected.getSequences(null).contains(
201                       (SequenceI) node.element()))
202       {
203         g.setColor(Color.gray);
204
205         g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);
206         g.setColor(Color.white);
207       }
208       g.drawString(name, xend + 10, ypos);
209       g.setColor(Color.black);
210     }
211     else
212     {
213       drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
214               offy);
215       drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
216               offy);
217
218       float height = node.height;
219       float dist = node.dist;
220
221       int xstart = (int) ((height - dist) * scale) + offx;
222       int xend = (int) (height * scale) + offx;
223       int ypos = (int) (node.ycount * chunk) + offy;
224
225       g.setColor(((SequenceNode) node).color.darker());
226
227       // Draw horizontal line
228       g.drawLine(xstart, ypos, xend, ypos);
229       if (node == highlightNode)
230       {
231         g.fillRect(xend - 3, ypos - 3, 6, 6);
232       }
233       else
234       {
235         g.fillRect(xend - 2, ypos - 2, 4, 4);
236       }
237
238       int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
239               + offy;
240       int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
241               + offy;
242
243       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
244       nodeHash.put(node, pos);
245
246       g.drawLine((int) (height * scale) + offx, ystart,
247               (int) (height * scale) + offx, yend);
248
249       String nodeLabel = "";
250
251       if (showDistances && (node.dist > 0))
252       {
253         nodeLabel = new Format("%-.2f").form(node.dist);
254       }
255
256       if (showBootstrap)
257       {
258         int btstrap = node.getBootstrap();
259         if (btstrap > -1)
260         {
261           if (showDistances)
262           {
263             nodeLabel = nodeLabel + " : ";
264           }
265           nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
266         }
267       }
268
269       if (!nodeLabel.equals(""))
270       {
271         g.drawString(nodeLabel, xstart + 2, ypos - 2);
272       }
273
274     }
275   }
276
277   public Object findElement(int x, int y)
278   {
279     Enumeration keys = nameHash.keys();
280
281     while (keys.hasMoreElements())
282     {
283       Object ob = keys.nextElement();
284       Rectangle rect = (Rectangle) nameHash.get(ob);
285
286       if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
287               && y <= (rect.y + rect.height))
288       {
289         return ob;
290       }
291     }
292     keys = nodeHash.keys();
293
294     while (keys.hasMoreElements())
295     {
296       Object ob = keys.nextElement();
297       Rectangle rect = (Rectangle) nodeHash.get(ob);
298
299       if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
300               && y <= (rect.y + rect.height))
301       {
302         return ob;
303       }
304     }
305     return null;
306
307   }
308
309   public void pickNodes(Rectangle pickBox)
310   {
311     int width = getSize().width;
312     int height = getSize().height;
313
314     SequenceNode top = tree.getTopNode();
315
316     float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
317     if (top.count == 0)
318     {
319       top.count = ((SequenceNode) top.left()).count
320               + ((SequenceNode) top.right()).count;
321     }
322     float chunk = (float) (height - offy) / top.count;
323
324     pickNode(pickBox, top, chunk, wscale, width, offx, offy);
325   }
326
327   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
328           float scale, int width, int offx, int offy)
329   {
330     if (node == null)
331     {
332       return;
333     }
334
335     if (node.left() == null && node.right() == null)
336     {
337       float height = node.height;
338       // float dist = node.dist;
339
340       // int xstart = (int) ( (height - dist) * scale) + offx;
341       int xend = (int) (height * scale) + offx;
342
343       int ypos = (int) (node.ycount * chunk) + offy;
344
345       if (pickBox.contains(new Point(xend, ypos)))
346       {
347         if (node.element() instanceof SequenceI)
348         {
349           SequenceI seq = (SequenceI) node.element();
350           SequenceGroup sg = av.getSelectionGroup();
351           if (sg != null)
352           {
353             sg.addOrRemove(seq, true);
354           }
355         }
356       }
357     }
358     else
359     {
360       pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
361               offx, offy);
362       pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
363               offx, offy);
364     }
365   }
366
367   public void setColor(SequenceNode node, Color c)
368   {
369     if (node == null)
370     {
371       return;
372     }
373
374     if (node.left() == null && node.right() == null)
375     {
376       node.color = c;
377
378       if (node.element() instanceof SequenceI)
379       {
380         av.setSequenceColour((SequenceI) node.element(), c);
381       }
382     }
383     else
384     {
385       node.color = c;
386       setColor((SequenceNode) node.left(), c);
387       setColor((SequenceNode) node.right(), c);
388     }
389   }
390
391   public void update(Graphics g)
392   {
393     paint(g);
394   }
395
396   public void paint(Graphics g)
397   {
398     if (tree == null)
399     {
400       return;
401     }
402
403     if (nameHash.size() == 0)
404     {
405       repaint();
406     }
407
408     int width = scrollPane.getSize().width;
409     int height = scrollPane.getSize().height;
410     if (!fitToWindow)
411     {
412       height = g.getFontMetrics(font).getHeight() * nameHash.size();
413     }
414
415     if (getSize().width > width)
416     {
417       setSize(new Dimension(width, height));
418       scrollPane.validate();
419       return;
420     }
421
422     setSize(new Dimension(width, height));
423
424     g.setFont(font);
425     draw(g, width, height);
426     validate();
427   }
428
429   public void draw(Graphics g, int width, int height)
430   {
431     offy = font.getSize() + 10;
432
433     g.setColor(Color.white);
434     g.fillRect(0, 0, width, height);
435
436     labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; // 20
437     // allows
438     // for
439     // scrollbar
440
441     float wscale = (float) (width - labelLength - offx * 2)
442             / tree.getMaxHeight();
443
444     SequenceNode top = tree.getTopNode();
445
446     if (top.count == 0)
447     {
448       top.count = ((SequenceNode) top.left()).count
449               + ((SequenceNode) top.right()).count;
450     }
451     float chunk = (float) (height - offy) / top.count;
452
453     drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
454
455     if (threshold != 0)
456     {
457       if (av.getCurrentTree() == tree)
458       {
459         g.setColor(Color.red);
460       }
461       else
462       {
463         g.setColor(Color.gray);
464       }
465
466       int x = (int) (threshold
467               * (float) (getSize().width - labelLength - 2 * offx) + offx);
468
469       g.drawLine(x, 0, x, getSize().height);
470     }
471
472   }
473
474   public void mouseReleased(MouseEvent e)
475   {
476   }
477
478   public void mouseEntered(MouseEvent e)
479   {
480   }
481
482   public void mouseExited(MouseEvent e)
483   {
484   }
485
486   public void mouseClicked(MouseEvent evt)
487   {
488     if (highlightNode != null)
489     {
490       if (evt.getClickCount() > 1)
491       {
492         tree.swapNodes(highlightNode);
493         tree.reCount(tree.getTopNode());
494         tree.findHeight(tree.getTopNode());
495       }
496       else
497       {
498         Vector leaves = new Vector();
499         tree.findLeaves(highlightNode, leaves);
500
501         for (int i = 0; i < leaves.size(); i++)
502         {
503           SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
504                   .element();
505           treeSelectionChanged(seq);
506         }
507       }
508
509       PaintRefresher.Refresh(this, av.getSequenceSetId());
510       repaint();
511     }
512   }
513
514   public void mouseDragged(MouseEvent ect)
515   {
516   }
517
518   public void mouseMoved(MouseEvent evt)
519   {
520     av.setCurrentTree(tree);
521
522     Object ob = findElement(evt.getX(), evt.getY());
523
524     if (ob instanceof SequenceNode)
525     {
526       highlightNode = (SequenceNode) ob;
527       repaint();
528     }
529     else
530     {
531       if (highlightNode != null)
532       {
533         highlightNode = null;
534         repaint();
535       }
536     }
537   }
538
539   public void mousePressed(MouseEvent e)
540   {
541     av.setCurrentTree(tree);
542
543     int x = e.getX();
544     int y = e.getY();
545
546     Object ob = findElement(x, y);
547
548     if (ob instanceof SequenceI)
549     {
550       treeSelectionChanged((Sequence) ob);
551       PaintRefresher.Refresh(this, av.getSequenceSetId());
552       repaint();
553       return;
554     }
555     else if (!(ob instanceof SequenceNode))
556     {
557       // Find threshold
558
559       if (tree.getMaxHeight() != 0)
560       {
561         threshold = (float) (x - offx)
562                 / (float) (getSize().width - labelLength - 2 * offx);
563
564         tree.getGroups().removeAllElements();
565         tree.groupNodes(tree.getTopNode(), threshold);
566         setColor(tree.getTopNode(), Color.black);
567
568         av.setSelectionGroup(null);
569         av.alignment.deleteAllGroups();
570         av.sequenceColours = null;
571
572         colourGroups();
573
574       }
575     }
576
577     PaintRefresher.Refresh(this, av.getSequenceSetId());
578     repaint();
579
580   }
581
582   void colourGroups()
583   {
584     for (int i = 0; i < tree.getGroups().size(); i++)
585     {
586
587       Color col = new Color((int) (Math.random() * 255),
588               (int) (Math.random() * 255), (int) (Math.random() * 255));
589       setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
590
591       Vector l = tree.findLeaves(
592               (SequenceNode) tree.getGroups().elementAt(i), new Vector());
593
594       Vector sequences = new Vector();
595       for (int j = 0; j < l.size(); j++)
596       {
597         SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
598                 .element();
599         if (!sequences.contains(s1))
600         {
601           sequences.addElement(s1);
602         }
603       }
604
605       ColourSchemeI cs = null;
606
607       if (av.getGlobalColourScheme() != null)
608       {
609         if (av.getGlobalColourScheme() instanceof UserColourScheme)
610         {
611           cs = new UserColourScheme(
612                   ((UserColourScheme) av.getGlobalColourScheme())
613                           .getColours());
614
615         }
616         else
617         {
618           cs = ColourSchemeProperty.getColour(sequences, av.alignment
619                   .getWidth(), ColourSchemeProperty.getColourName(av
620                   .getGlobalColourScheme()));
621         }
622
623         cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
624                 av.getIgnoreGapsConsensus());
625       }
626
627       SequenceGroup sg = new SequenceGroup(sequences, "", cs, true, true,
628               false, 0, av.alignment.getWidth() - 1);
629
630       sg.setName("JTreeGroup:" + sg.hashCode());
631       sg.setIdColour(col);
632       if (av.getGlobalColourScheme() != null
633               && av.getGlobalColourScheme().conservationApplied())
634       {
635         Conservation c = new Conservation("Group",
636                 ResidueProperties.propHash, 3, sg.getSequences(null),
637                 sg.getStartRes(), sg.getEndRes());
638
639         c.calculate();
640         c.verdict(false, av.ConsPercGaps);
641         cs.setConservation(c);
642
643         sg.cs = cs;
644
645       }
646
647       av.alignment.addGroup(sg);
648
649     }
650
651   }
652
653   public void setShowDistances(boolean state)
654   {
655     this.showDistances = state;
656     repaint();
657   }
658
659   public void setShowBootstrap(boolean state)
660   {
661     this.showBootstrap = state;
662     repaint();
663   }
664
665   public void setMarkPlaceholders(boolean state)
666   {
667     this.markPlaceholders = state;
668     repaint();
669   }
670
671 }