updated to jalview 2.1 and begun ArchiveClient/VamsasClient/VamsasStore updates.
[jalview.git] / src / jalview / appletgui / TreeCanvas.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2006 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
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   //RubberbandRectangle rubberband;
54
55   Vector listeners;
56
57   Hashtable nameHash = new Hashtable();
58   Hashtable nodeHash = new Hashtable();
59
60   public TreeCanvas(AlignViewport av, ScrollPane scroller)
61   {
62     this.av = av;
63     font = av.getFont();
64     scrollPane = scroller;
65     addMouseListener(this);
66     setLayout(null);
67
68     PaintRefresher.Register(this, av.alignment);
69   }
70
71   public void TreeSelectionChanged(Sequence sequence)
72   {
73     SequenceGroup selected = av.getSelectionGroup();
74     if (selected == null)
75     {
76       selected = new SequenceGroup();
77       av.setSelectionGroup(selected);
78     }
79
80     selected.setEndRes(av.alignment.getWidth()-1);
81     selected.addOrRemove(sequence, true);
82
83     PaintRefresher.Refresh(this, av.alignment);
84     repaint();
85   }
86
87   public void setTree(NJTree tree)
88   {
89     this.tree = tree;
90     tree.findHeight(tree.getTopNode());
91
92     // Now have to calculate longest name based on the leaves
93     Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
94     boolean has_placeholders = false;
95     longestName = "";
96
97     for (int i = 0; i < leaves.size(); i++)
98     {
99       SequenceNode lf = (SequenceNode) leaves.elementAt(i);
100
101       if (lf.isPlaceholder())
102       {
103         has_placeholders = true;
104       }
105
106       if (longestName.length() < ( (Sequence) lf.element()).getName()
107           .length())
108       {
109         longestName = TreeCanvas.PLACEHOLDER +
110             ( (Sequence) lf.element()).getName();
111       }
112     }
113
114     setMarkPlaceholders(has_placeholders);
115   }
116
117   public void drawNode(Graphics g, SequenceNode node, float chunk, float scale,
118                        int width, int offx, int offy)
119   {
120     if (node == null)
121     {
122       return;
123     }
124
125     if (node.left() == null && node.right() == null)
126     {
127       // Drawing leaf node
128
129       float height = node.height;
130       float dist = node.dist;
131
132       int xstart = (int) ( (height - dist) * scale) + offx;
133       int xend = (int) (height * scale) + offx;
134
135       int ypos = (int) (node.ycount * chunk) + offy;
136
137       if (node.element() instanceof SequenceI)
138       {
139         if ( ( (SequenceI) ( (SequenceNode) node).element()).getColor() ==
140             Color.white)
141         {
142           g.setColor(Color.black);
143         }
144         else
145         {
146           g.setColor( ( (SequenceI) ( (SequenceNode) node).element()).getColor().
147                      darker());
148         }
149
150       }
151       else
152       {
153         g.setColor(Color.black);
154       }
155
156       // Draw horizontal line
157       g.drawLine(xstart, ypos, xend, ypos);
158
159       String nodeLabel = "";
160       if (showDistances && node.dist > 0)
161       {
162         nodeLabel = new Format("%-.2f").form(node.dist);
163       }
164       if (showBootstrap)
165       {
166         if (showDistances)
167         {
168           nodeLabel = nodeLabel + " : ";
169         }
170         nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
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(false).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       g.fillRect(xend - 2, ypos - 2, 4, 4);
218
219       int ystart = (int) ( ( (SequenceNode) node.left()).ycount * chunk) + offy;
220       int yend = (int) ( ( (SequenceNode) node.right()).ycount * chunk) + offy;
221
222       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
223       nodeHash.put(node, pos);
224
225       g.drawLine( (int) (height * scale) + offx, ystart,
226                  (int) (height * scale) + offx, yend);
227
228       if (showDistances && node.dist > 0)
229       {
230         g.drawString(new Format("%-.2f").form(node.dist), xstart+2, ypos - 2);
231       }
232
233     }
234   }
235
236   public Object findElement(int x, int y)
237   {
238     Enumeration keys = nameHash.keys();
239
240     while (keys.hasMoreElements())
241     {
242       Object ob = keys.nextElement();
243       Rectangle rect = (Rectangle) nameHash.get(ob);
244
245       if (x >= rect.x && x <= (rect.x + rect.width) &&
246           y >= rect.y && y <= (rect.y + rect.height))
247       {
248         return ob;
249       }
250     }
251     keys = nodeHash.keys();
252
253     while (keys.hasMoreElements())
254     {
255       Object ob = keys.nextElement();
256       Rectangle rect = (Rectangle) nodeHash.get(ob);
257
258       if (x >= rect.x && x <= (rect.x + rect.width) &&
259           y >= rect.y && y <= (rect.y + rect.height))
260       {
261         return ob;
262       }
263     }
264     return null;
265
266   }
267
268   public void pickNodes(Rectangle pickBox)
269   {
270     int width = getSize().width;
271     int height = getSize().height;
272
273     SequenceNode top = tree.getTopNode();
274
275     float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight()
276         ;
277     if (top.count == 0)
278     {
279       top.count = ( (SequenceNode) top.left()).count +
280           ( (SequenceNode) top.right()).count;
281     }
282     float chunk = (float) (height - offy) / top.count;
283
284     pickNode(pickBox, top, chunk, wscale, width, offx, offy);
285   }
286
287   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
288                        float scale, int width, int offx, int offy)
289   {
290     if (node == null)
291     {
292       return;
293     }
294
295     if (node.left() == null && node.right() == null)
296     {
297       float height = node.height;
298       //float dist = node.dist;
299
300       //int xstart = (int) ( (height - dist) * scale) + offx;
301       int xend = (int) (height * scale) + offx;
302
303       int ypos = (int) (node.ycount * chunk) + offy;
304
305       if (pickBox.contains(new Point(xend, ypos)))
306       {
307         if (node.element() instanceof SequenceI)
308         {
309           SequenceI seq = (SequenceI) node.element();
310           SequenceGroup sg = av.getSelectionGroup();
311           if (sg != null)
312           {
313             sg.addOrRemove(seq, true);
314           }
315         }
316       }
317     }
318     else
319     {
320       pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width, offx,
321                offy);
322       pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width, offx,
323                offy);
324     }
325   }
326
327   public void setColor(SequenceNode node, Color c)
328   {
329     if (node == null)
330     {
331       return;
332     }
333
334     if (node.left() == null && node.right() == null)
335     {
336       node.color = c;
337
338       if (node.element() instanceof SequenceI)
339       {
340         ( (SequenceI) node.element()).setColor(c);
341       }
342     }
343     else
344     {
345       node.color = c;
346       setColor( (SequenceNode) node.left(), c);
347       setColor( (SequenceNode) node.right(), c);
348     }
349   }
350
351   public void paint(Graphics g)
352   {
353
354     if(tree==null)
355       return;
356
357     g.setFont(font);
358
359
360     FontMetrics fm = g.getFontMetrics(font);
361
362     if (nameHash.size() == 0)
363     {
364       repaint();
365     }
366
367     if (fitToWindow ||
368         (!fitToWindow &&
369          scrollPane.getSize().height > fm.getHeight() * nameHash.size() + offy))
370     {
371       draw(g, scrollPane.getSize().width, scrollPane.getSize().height);
372     }
373     else
374     {
375       setSize(new Dimension(scrollPane.getSize().width,
376                             fm.getHeight() * nameHash.size()));
377       draw(g, scrollPane.getSize().width, fm.getHeight() * nameHash.size());
378     }
379
380     scrollPane.validate();
381   }
382
383
384   public void draw(Graphics g, int width, int height)
385   {
386     offy = font.getSize()+10;
387
388     g.setColor(Color.white);
389     g.fillRect(0, 0, width, height);
390
391     labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; //20 allows for scrollbar
392
393     float wscale = (float) (width - labelLength - offx * 2) / tree.getMaxHeight();
394
395     SequenceNode top = tree.getTopNode();
396
397     if (top.count == 0)
398     {
399       top.count = ( (SequenceNode) top.left()).count +
400           ( (SequenceNode) top.right()).count;
401     }
402     float chunk = (float) (height - offy) / top.count;
403
404     drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
405
406     if (threshold != 0)
407     {
408       if (av.getCurrentTree() == tree)
409       {
410         g.setColor(Color.red);
411       }
412       else
413       {
414         g.setColor(Color.gray);
415       }
416
417       int x = (int) (threshold *
418                      (float) (getSize().width - labelLength - 2 * offx) + offx);
419
420       g.drawLine(x, 0, x, getSize().height);
421     }
422
423   }
424
425   public void mouseReleased(MouseEvent e)
426   {}
427
428   public void mouseEntered(MouseEvent e)
429   {}
430
431   public void mouseExited(MouseEvent e)
432   {}
433
434   public void mouseClicked(MouseEvent e)
435   {
436   }
437
438   public void mousePressed(MouseEvent e)
439   {
440     av.setCurrentTree(tree);
441
442     int x = e.getX();
443     int y = e.getY();
444
445     Object ob = findElement(x, y);
446
447     if (ob instanceof SequenceI)
448     {
449       TreeSelectionChanged( (Sequence) ob);
450       repaint();
451       return;
452
453     }
454     else if (ob instanceof SequenceNode)
455     {
456       SequenceNode tmpnode = (SequenceNode) ob;
457       tree.swapNodes(tmpnode);
458       tree.reCount(tree.getTopNode());
459       tree.findHeight(tree.getTopNode());
460     }
461     else
462     {
463       // Find threshold
464
465       if (tree.getMaxHeight() != 0)
466       {
467         threshold = (float) (x - offx) /
468             (float) (getSize().width - labelLength - 2 * offx);
469
470         tree.getGroups().removeAllElements();
471         tree.groupNodes(tree.getTopNode(), threshold);
472         setColor(tree.getTopNode(), Color.black);
473
474         av.setSelectionGroup(null);
475         av.alignment.deleteAllGroups();
476
477         for (int i = 0; i < tree.getGroups().size(); i++)
478         {
479
480           Color col = new Color( (int) (Math.random() * 255),
481                                 (int) (Math.random() * 255),
482                                 (int) (Math.random() * 255));
483           setColor( (SequenceNode) tree.getGroups().elementAt(i), col.brighter());
484
485           Vector l = tree.findLeaves( (SequenceNode) tree.getGroups().elementAt(
486               i), new Vector());
487
488           Vector sequences = new Vector();
489           for (int j = 0; j < l.size(); j++)
490           {
491             SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();
492             if(!sequences.contains(s1))
493               sequences.addElement(s1);
494           }
495
496           ColourSchemeI cs = null;
497
498           if (av.getGlobalColourScheme() != null)
499           {
500             if (av.getGlobalColourScheme() instanceof UserColourScheme)
501             {
502               cs = new UserColourScheme(
503                   ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
504
505             }
506             else
507               cs = ColourSchemeProperty.getColour(sequences,
508                                                   av.alignment.getWidth(),
509                                                   ColourSchemeProperty.getColourName(
510                                                       av.getGlobalColourScheme()));
511
512               cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
513                                                    av.getIgnoreGapsConsensus());
514           }
515
516           SequenceGroup sg = new SequenceGroup(sequences, "TreeGroup",
517                                                cs, true, true,
518                                                false, 0, av.alignment.getWidth()-1);
519
520
521           if (  av.getGlobalColourScheme()!=null
522              && av.getGlobalColourScheme().conservationApplied())
523             {
524             Conservation c = new Conservation("Group",
525                                               ResidueProperties.propHash, 3,
526                                               sg.getSequences(false),
527                                               sg.getStartRes(),
528                                               sg.getEndRes());
529
530             c.calculate();
531             c.verdict(false, av.ConsPercGaps);
532             cs.setConservation(c);
533
534             sg.cs = cs;
535
536           }
537
538           av.alignment.addGroup(sg);
539
540         }
541       }
542     }
543
544     PaintRefresher.Refresh(this, av.alignment);
545     repaint();
546
547   }
548
549   public void setShowDistances(boolean state)
550   {
551     this.showDistances = state;
552     repaint();
553   }
554
555   public void setShowBootstrap(boolean state)
556   {
557     this.showBootstrap = state;
558     repaint();
559   }
560
561   public void setMarkPlaceholders(boolean state)
562   {
563     this.markPlaceholders = state;
564     repaint();
565   }
566
567 }