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