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