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