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