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