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