Jalview 2.6 source licence
[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())
188               : node.getName();
189       FontMetrics fm = g.getFontMetrics(font);
190       int charWidth = fm.stringWidth(name) + 3;
191       int charHeight = fm.getHeight();
192
193       Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,
194               charWidth, charHeight);
195
196       nameHash.put((SequenceI) node.element(), rect);
197
198       // Colour selected leaves differently
199       SequenceGroup selected = av.getSelectionGroup();
200       if (selected != null
201               && selected.getSequences(null).contains(
202                       (SequenceI) node.element()))
203       {
204         g.setColor(Color.gray);
205
206         g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);
207         g.setColor(Color.white);
208       }
209       g.drawString(name, xend + 10, ypos);
210       g.setColor(Color.black);
211     }
212     else
213     {
214       drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
215               offy);
216       drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
217               offy);
218
219       float height = node.height;
220       float dist = node.dist;
221
222       int xstart = (int) ((height - dist) * scale) + offx;
223       int xend = (int) (height * scale) + offx;
224       int ypos = (int) (node.ycount * chunk) + offy;
225
226       g.setColor(((SequenceNode) node).color.darker());
227
228       // Draw horizontal line
229       g.drawLine(xstart, ypos, xend, ypos);
230       if (node == highlightNode)
231       {
232         g.fillRect(xend - 3, ypos - 3, 6, 6);
233       }
234       else
235       {
236         g.fillRect(xend - 2, ypos - 2, 4, 4);
237       }
238
239       int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
240               + offy;
241       int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
242               + offy;
243
244       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
245       nodeHash.put(node, pos);
246
247       g.drawLine((int) (height * scale) + offx, ystart,
248               (int) (height * scale) + offx, yend);
249
250       String nodeLabel = "";
251
252       if (showDistances && (node.dist > 0))
253       {
254         nodeLabel = new Format("%-.2f").form(node.dist);
255       }
256
257       if (showBootstrap)
258       {
259         int btstrap = node.getBootstrap();
260         if (btstrap > -1)
261         {
262           if (showDistances)
263           {
264             nodeLabel = nodeLabel + " : ";
265           }
266           nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
267         }
268       }
269
270       if (!nodeLabel.equals(""))
271       {
272         g.drawString(nodeLabel, xstart + 2, ypos - 2);
273       }
274
275     }
276   }
277
278   public Object findElement(int x, int y)
279   {
280     Enumeration keys = nameHash.keys();
281
282     while (keys.hasMoreElements())
283     {
284       Object ob = keys.nextElement();
285       Rectangle rect = (Rectangle) nameHash.get(ob);
286
287       if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
288               && y <= (rect.y + rect.height))
289       {
290         return ob;
291       }
292     }
293     keys = nodeHash.keys();
294
295     while (keys.hasMoreElements())
296     {
297       Object ob = keys.nextElement();
298       Rectangle rect = (Rectangle) nodeHash.get(ob);
299
300       if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
301               && y <= (rect.y + rect.height))
302       {
303         return ob;
304       }
305     }
306     return null;
307
308   }
309
310   public void pickNodes(Rectangle pickBox)
311   {
312     int width = getSize().width;
313     int height = getSize().height;
314
315     SequenceNode top = tree.getTopNode();
316
317     float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
318     if (top.count == 0)
319     {
320       top.count = ((SequenceNode) top.left()).count
321               + ((SequenceNode) top.right()).count;
322     }
323     float chunk = (float) (height - offy) / top.count;
324
325     pickNode(pickBox, top, chunk, wscale, width, offx, offy);
326   }
327
328   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
329           float scale, int width, int offx, int offy)
330   {
331     if (node == null)
332     {
333       return;
334     }
335
336     if (node.left() == null && node.right() == null)
337     {
338       float height = node.height;
339       // float dist = node.dist;
340
341       // int xstart = (int) ( (height - dist) * scale) + offx;
342       int xend = (int) (height * scale) + offx;
343
344       int ypos = (int) (node.ycount * chunk) + offy;
345
346       if (pickBox.contains(new Point(xend, ypos)))
347       {
348         if (node.element() instanceof SequenceI)
349         {
350           SequenceI seq = (SequenceI) node.element();
351           SequenceGroup sg = av.getSelectionGroup();
352           if (sg != null)
353           {
354             sg.addOrRemove(seq, true);
355           }
356         }
357       }
358     }
359     else
360     {
361       pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
362               offx, offy);
363       pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
364               offx, offy);
365     }
366   }
367
368   public void setColor(SequenceNode node, Color c)
369   {
370     if (node == null)
371     {
372       return;
373     }
374
375     if (node.left() == null && node.right() == null)
376     {
377       node.color = c;
378
379       if (node.element() instanceof SequenceI)
380       {
381         av.setSequenceColour((SequenceI) node.element(), c);
382       }
383     }
384     else
385     {
386       node.color = c;
387       setColor((SequenceNode) node.left(), c);
388       setColor((SequenceNode) node.right(), c);
389     }
390   }
391
392   public void update(Graphics g)
393   {
394     paint(g);
395   }
396
397   public void paint(Graphics g)
398   {
399     if (tree == null)
400     {
401       return;
402     }
403
404     if (nameHash.size() == 0)
405     {
406       repaint();
407     }
408
409     int width = scrollPane.getSize().width;
410     int height = scrollPane.getSize().height;
411     if (!fitToWindow)
412     {
413       height = g.getFontMetrics(font).getHeight() * nameHash.size();
414     }
415
416     if (getSize().width > width)
417     {
418       setSize(new Dimension(width, height));
419       scrollPane.validate();
420       return;
421     }
422
423     setSize(new Dimension(width, height));
424
425     g.setFont(font);
426     draw(g, width, height);
427     validate();
428   }
429
430   public void draw(Graphics g, int width, int height)
431   {
432     offy = font.getSize() + 10;
433
434     g.setColor(Color.white);
435     g.fillRect(0, 0, width, height);
436
437     labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; // 20
438     // allows
439     // for
440     // scrollbar
441
442     float wscale = (float) (width - labelLength - offx * 2)
443             / tree.getMaxHeight();
444
445     SequenceNode top = tree.getTopNode();
446
447     if (top.count == 0)
448     {
449       top.count = ((SequenceNode) top.left()).count
450               + ((SequenceNode) top.right()).count;
451     }
452     float chunk = (float) (height - offy) / top.count;
453
454     drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
455
456     if (threshold != 0)
457     {
458       if (av.getCurrentTree() == tree)
459       {
460         g.setColor(Color.red);
461       }
462       else
463       {
464         g.setColor(Color.gray);
465       }
466
467       int x = (int) (threshold
468               * (float) (getSize().width - labelLength - 2 * offx) + offx);
469
470       g.drawLine(x, 0, x, getSize().height);
471     }
472
473   }
474
475   public void mouseReleased(MouseEvent e)
476   {
477   }
478
479   public void mouseEntered(MouseEvent e)
480   {
481   }
482
483   public void mouseExited(MouseEvent e)
484   {
485   }
486
487   public void mouseClicked(MouseEvent evt)
488   {
489     if (highlightNode != null)
490     {
491       if (evt.getClickCount() > 1)
492       {
493         tree.swapNodes(highlightNode);
494         tree.reCount(tree.getTopNode());
495         tree.findHeight(tree.getTopNode());
496       }
497       else
498       {
499         Vector leaves = new Vector();
500         tree.findLeaves(highlightNode, leaves);
501
502         for (int i = 0; i < leaves.size(); i++)
503         {
504           SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
505                   .element();
506           treeSelectionChanged(seq);
507         }
508       }
509
510       PaintRefresher.Refresh(this, av.getSequenceSetId());
511       repaint();
512     }
513   }
514
515   public void mouseDragged(MouseEvent ect)
516   {
517   }
518
519   public void mouseMoved(MouseEvent evt)
520   {
521     av.setCurrentTree(tree);
522
523     Object ob = findElement(evt.getX(), evt.getY());
524
525     if (ob instanceof SequenceNode)
526     {
527       highlightNode = (SequenceNode) ob;
528       repaint();
529     }
530     else
531     {
532       if (highlightNode != null)
533       {
534         highlightNode = null;
535         repaint();
536       }
537     }
538   }
539
540   public void mousePressed(MouseEvent e)
541   {
542     av.setCurrentTree(tree);
543
544     int x = e.getX();
545     int y = e.getY();
546
547     Object ob = findElement(x, y);
548
549     if (ob instanceof SequenceI)
550     {
551       treeSelectionChanged((Sequence) ob);
552       PaintRefresher.Refresh(this, av.getSequenceSetId());
553       repaint();
554       return;
555     }
556     else if (!(ob instanceof SequenceNode))
557     {
558       // Find threshold
559
560       if (tree.getMaxHeight() != 0)
561       {
562         threshold = (float) (x - offx)
563                 / (float) (getSize().width - labelLength - 2 * offx);
564
565         tree.getGroups().removeAllElements();
566         tree.groupNodes(tree.getTopNode(), threshold);
567         setColor(tree.getTopNode(), Color.black);
568
569         av.setSelectionGroup(null);
570         av.alignment.deleteAllGroups();
571         av.sequenceColours = null;
572
573         colourGroups();
574
575       }
576     }
577
578     PaintRefresher.Refresh(this, av.getSequenceSetId());
579     repaint();
580
581   }
582
583   void colourGroups()
584   {
585     for (int i = 0; i < tree.getGroups().size(); i++)
586     {
587
588       Color col = new Color((int) (Math.random() * 255), (int) (Math
589               .random() * 255), (int) (Math.random() * 255));
590       setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
591
592       Vector l = tree.findLeaves((SequenceNode) tree.getGroups().elementAt(
593               i), new Vector());
594
595       Vector sequences = new Vector();
596       for (int j = 0; j < l.size(); j++)
597       {
598         SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
599                 .element();
600         if (!sequences.contains(s1))
601         {
602           sequences.addElement(s1);
603         }
604       }
605
606       ColourSchemeI cs = null;
607
608       if (av.getGlobalColourScheme() != null)
609       {
610         if (av.getGlobalColourScheme() instanceof UserColourScheme)
611         {
612           cs = new UserColourScheme(((UserColourScheme) av
613                   .getGlobalColourScheme()).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(), av
624                 .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), sg
637                         .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 }