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