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