GPL license added
[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 \r
23 import jalview.analysis.*;\r
24 import jalview.datamodel.*;\r
25 import jalview.util.*;\r
26 import jalview.schemes.*;\r
27 import java.awt.*;\r
28 import java.awt.event.*;\r
29 import java.util.*;\r
30 \r
31 \r
32 public class TreeCanvas extends Panel implements MouseListener\r
33 {\r
34   NJTree tree;\r
35   ScrollPane scrollPane;\r
36   AlignViewport av;\r
37   public static final String PLACEHOLDER=" * ";\r
38   Font font;\r
39   int  fontSize = 12;\r
40 \r
41   boolean fitToWindow = true;\r
42   boolean showDistances = false;\r
43   boolean showBootstrap = false;\r
44   boolean markPlaceholders = false;\r
45 \r
46   int offx = 20;\r
47   int offy = 20;\r
48 \r
49   float threshold;\r
50 \r
51   String longestName;\r
52   int labelLength=-1;\r
53 \r
54   //RubberbandRectangle rubberband;\r
55 \r
56   Vector    listeners;\r
57 \r
58   Hashtable nameHash = new Hashtable();\r
59   Hashtable nodeHash = new Hashtable();\r
60 \r
61   public TreeCanvas(AlignViewport av, NJTree tree, ScrollPane scroller, String label)\r
62   {\r
63     this.av = av;\r
64     this.tree     = tree;\r
65     scrollPane = scroller;\r
66     addMouseListener(this);\r
67     tree.findHeight(tree.getTopNode());\r
68     longestName = label;\r
69     setLayout(null);\r
70 \r
71     PaintRefresher.Register(this);\r
72   }\r
73   public void TreeSelectionChanged(Sequence sequence)\r
74  {\r
75     SequenceGroup selected = av.getSelectionGroup();\r
76     if(selected == null)\r
77     {\r
78       selected = new SequenceGroup();\r
79       av.setSelectionGroup(selected);\r
80     }\r
81 \r
82     selected.setEndRes(av.alignment.getWidth());\r
83     selected.addOrRemove(sequence);\r
84 \r
85 \r
86     PaintRefresher.Refresh(this);\r
87     repaint();\r
88  }\r
89 \r
90 \r
91 \r
92   public void setTree(NJTree tree) {\r
93     this.tree = tree;\r
94     tree.findHeight(tree.getTopNode());\r
95   }\r
96 \r
97     public void drawNode(Graphics g,SequenceNode node, float chunk, float scale, int width,int offx, int offy) {\r
98     if (node == null) {\r
99       return;\r
100     }\r
101 \r
102     if (node.left() == null && node.right() == null) {\r
103       // Drawing leaf node\r
104 \r
105       float height = node.height;\r
106       float dist   = node.dist;\r
107 \r
108       int xstart = (int)((height-dist)*scale) + offx;\r
109       int xend =   (int)(height*scale)        + offx;\r
110 \r
111       int ypos = (int)(node.ycount * chunk) + offy;\r
112 \r
113       if (node.element() instanceof SequenceI)\r
114       {\r
115        if ( ( (SequenceI) ( (SequenceNode) node).element()).getColor() == Color.white)\r
116        {\r
117          g.setColor(Color.black);\r
118        }\r
119        else\r
120         g.setColor( ( (SequenceI) ( (SequenceNode) node).element()).getColor().\r
121                    darker());\r
122 \r
123       }\r
124       else\r
125           g.setColor(Color.black);\r
126 \r
127 \r
128       // Draw horizontal line\r
129       g.drawLine(xstart,ypos,xend,ypos);\r
130 \r
131       String nodeLabel = "";\r
132       if (showDistances && node.dist > 0) {\r
133         nodeLabel = new Format("%5.2f").form(node.dist);\r
134       }\r
135       if (showBootstrap) {\r
136         if (showDistances) {\r
137           nodeLabel = nodeLabel + " : ";\r
138         }\r
139         nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());\r
140       }\r
141       if (! nodeLabel.equals("")) {\r
142       g.drawString(nodeLabel,xstart,ypos - 10);\r
143       }\r
144 \r
145       String name    = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER+node.getName()) : node.getName();\r
146       FontMetrics fm = g.getFontMetrics(font);\r
147       int charWidth  = fm.stringWidth(name) + 3;\r
148       int charHeight = fm.getHeight();\r
149 \r
150       Rectangle rect = new Rectangle(xend+20,ypos-charHeight,\r
151                                      charWidth,charHeight);\r
152 \r
153       nameHash.put((SequenceI)node.element(),rect);\r
154 \r
155       // Colour selected leaves differently\r
156       SequenceGroup selected = av.getSelectionGroup();\r
157       if (selected!=null && selected.sequences.contains((SequenceI)node.element())) {\r
158         g.setColor(Color.gray);\r
159 \r
160         g.fillRect(xend + 10, ypos - charHeight + 3,charWidth,charHeight);\r
161         g.setColor(Color.white);\r
162       }\r
163       g.drawString(name,xend+10,ypos);\r
164       g.setColor(Color.black);\r
165     } else {\r
166       drawNode(g,(SequenceNode)node.left(), chunk,scale,width,offx,offy);\r
167       drawNode(g,(SequenceNode)node.right(),chunk,scale,width,offx,offy);\r
168 \r
169       float height = node.height;\r
170       float dist   = node.dist;\r
171 \r
172       int xstart = (int)((height-dist)*scale) + offx;\r
173       int xend   = (int)(height       *scale) + offx;\r
174       int ypos   = (int)(node.ycount  *chunk) + offy;\r
175 \r
176       g.setColor(((SequenceNode)node).color.darker());\r
177 \r
178       // Draw horizontal line\r
179       g.drawLine(xstart,ypos,xend,ypos);\r
180       g.fillRect(xend-2, ypos-2, 4,4);\r
181 \r
182       int ystart = (int)(((SequenceNode)node.left()) .ycount * chunk) + offy;\r
183       int yend   = (int)(((SequenceNode)node.right()).ycount * chunk) + offy;\r
184 \r
185       Rectangle pos = new Rectangle(xend-2,ypos-2,5,5);\r
186       nodeHash.put(node,pos);\r
187 \r
188       g.drawLine((int)(height*scale) + offx, ystart,\r
189                  (int)(height*scale) + offx, yend);\r
190 \r
191       if (showDistances && node.dist > 0) {\r
192         g.drawString(new Format("%5.2f").form(node.dist),xstart,ypos - 5);\r
193       }\r
194 \r
195     }\r
196   }\r
197   public Object findElement(int x, int y) {\r
198        Enumeration keys = nameHash.keys();\r
199 \r
200     while (keys.hasMoreElements()) {\r
201             Object ob = keys.nextElement();\r
202             Rectangle rect = (Rectangle)nameHash.get(ob);\r
203 \r
204             if (x >= rect.x && x <= (rect.x + rect.width) &&\r
205               y >= rect.y && y <= (rect.y + rect.height)) {\r
206               return ob;\r
207             }\r
208   }\r
209   keys = nodeHash.keys();\r
210 \r
211     while (keys.hasMoreElements()) {\r
212             Object ob = keys.nextElement();\r
213             Rectangle rect = (Rectangle)nodeHash.get(ob);\r
214 \r
215             if (x >= rect.x && x <= (rect.x + rect.width) &&\r
216               y >= rect.y && y <= (rect.y + rect.height)) {\r
217               return ob;\r
218             }\r
219     }\r
220       return null;\r
221 \r
222   }\r
223 \r
224   public void pickNodes(Rectangle pickBox) {\r
225     int width  = getSize().width;\r
226     int height = getSize().height;\r
227 \r
228     SequenceNode top = tree.getTopNode();\r
229 \r
230     float wscale = (float)(width*.8-offx*2)/tree.getMaxHeight()\r
231 ;\r
232     if (top.count == 0) {\r
233       top.count = ((SequenceNode)top.left()).count + ((SequenceNode)top.right()).count ;\r
234     }\r
235     float chunk = (float)(height-offy*2)/top.count;\r
236 \r
237     pickNode(pickBox,top,chunk,wscale,width,offx,offy);\r
238   }\r
239 \r
240   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk, float scale, int width,int offx, int offy) {\r
241     if (node == null) {\r
242       return;\r
243     }\r
244 \r
245     if (node.left() == null && node.right() == null) {\r
246       float height = node.height;\r
247       float dist   = node.dist;\r
248 \r
249       int xstart = (int)((height-dist)*scale) + offx;\r
250       int xend   = (int)(height*scale) + offx;\r
251 \r
252       int ypos = (int)(node.ycount * chunk) + offy;\r
253 \r
254       if (pickBox.contains(new Point(xend,ypos))) {\r
255         if (node.element() instanceof SequenceI) {\r
256           SequenceI seq = (SequenceI)node.element();\r
257           SequenceGroup sg = av.getSelectionGroup();\r
258           if(sg!=null)\r
259             sg.addOrRemove(seq);\r
260         }\r
261       }\r
262     } else {\r
263       pickNode(pickBox,(SequenceNode)node.left(), chunk,scale,width,offx,offy);\r
264       pickNode(pickBox,(SequenceNode)node.right(),chunk,scale,width,offx,offy);\r
265     }\r
266   }\r
267 \r
268   public void setColor(SequenceNode node, Color c)\r
269   {\r
270     if (node == null)\r
271       return;\r
272 \r
273     if (node.left() == null && node.right() == null)\r
274     {\r
275       node.color = c;\r
276 \r
277       if (node.element() instanceof SequenceI)\r
278           ((SequenceI)node.element()).setColor(c);\r
279     } else\r
280     {\r
281       node.color = c;\r
282       setColor((SequenceNode)node.left(),c);\r
283       setColor((SequenceNode)node.right(),c);\r
284     }\r
285   }\r
286 \r
287 \r
288   public void paint(Graphics g)\r
289   {\r
290 \r
291     font = new Font("Verdana",Font.PLAIN,fontSize);\r
292     g.setFont(font);\r
293 \r
294     FontMetrics fm = g.getFontMetrics(font);\r
295 \r
296     if(nameHash.size()==0)\r
297       repaint();\r
298 \r
299 \r
300     if( fitToWindow || (!fitToWindow && scrollPane.getSize().height > fm.getHeight() * nameHash.size()+offy ) )\r
301      {\r
302          draw(g,scrollPane.getSize().width,scrollPane.getSize().height);\r
303      }\r
304     else\r
305      {\r
306          setSize(new Dimension(scrollPane.getSize().width, fm.getHeight() * nameHash.size()));\r
307          draw( g,scrollPane.getSize().width, fm.getHeight() * nameHash.size());\r
308      }\r
309 \r
310     scrollPane.validate();\r
311   }\r
312     public int getFontSize() {\r
313         return fontSize;\r
314     }\r
315     public void setFontSize(int fontSize) {\r
316         this.fontSize = fontSize;\r
317         repaint();\r
318     }\r
319   public void draw(Graphics g, int width, int height) {\r
320 \r
321       g.setColor(Color.white);\r
322       g.fillRect(0,0,width,height);\r
323 \r
324 \r
325       labelLength = g.getFontMetrics(font).stringWidth(longestName)+ 20;//20 allows for scrollbar\r
326 \r
327       float wscale =(float)(width - labelLength -offx*2)/tree.getMaxHeight();\r
328 \r
329       SequenceNode top = tree.getTopNode();\r
330 \r
331       if (top.count == 0) {\r
332           top.count = ((SequenceNode)top.left()).count + ((SequenceNode)top.right()).count ;\r
333       }\r
334       float chunk = (float)(height-offy*2)/top.count ;\r
335 \r
336       drawNode(g,tree.getTopNode(),chunk,wscale,width,offx,offy);\r
337 \r
338       if (threshold != 0)\r
339       {\r
340         if(av.getCurrentTree() == tree)\r
341           g.setColor(Color.red);\r
342         else\r
343           g.setColor(Color.gray);\r
344 \r
345           int x = (int)(    threshold * (float)(getSize().width-labelLength - 2*offx) +offx   ) ;\r
346 \r
347           g.drawLine(x,0,x,getSize().height);\r
348       }\r
349 \r
350   }\r
351 \r
352   public void mouseReleased(MouseEvent e) { }\r
353   public void mouseEntered(MouseEvent e) { }\r
354   public void mouseExited(MouseEvent e) { }\r
355   public void mouseClicked(MouseEvent e) {\r
356   }\r
357 \r
358   public void mousePressed(MouseEvent e) {\r
359 \r
360       av.setCurrentTree(tree);\r
361 \r
362       int x = e.getX();\r
363       int y = e.getY();\r
364 \r
365       Object ob = findElement(x,y);\r
366 \r
367       if (ob instanceof SequenceI)\r
368       {\r
369           TreeSelectionChanged((Sequence)ob);\r
370           repaint();\r
371           return;\r
372 \r
373       } else if (ob instanceof SequenceNode) {\r
374           SequenceNode tmpnode = (SequenceNode)ob;\r
375           tree.swapNodes(tmpnode);\r
376           tree.reCount(tree.getTopNode());\r
377           tree.findHeight(tree.getTopNode());\r
378       } else {\r
379           // Find threshold\r
380 \r
381           if (tree.getMaxHeight() != 0) {\r
382               threshold = (float)(x - offx)/(float)(getSize().width-labelLength - 2*offx);\r
383 \r
384               tree.getGroups().removeAllElements();\r
385               tree.groupNodes(tree.getTopNode(),threshold);\r
386               setColor(tree.getTopNode(),Color.black);\r
387 \r
388               av.setSelectionGroup(null);\r
389               av.alignment.deleteAllGroups();\r
390 \r
391               for (int i=0; i < tree.getGroups().size(); i++)\r
392               {\r
393 \r
394                   Color col = new Color((int)(Math.random()*255),\r
395                                         (int)(Math.random()*255),\r
396                                         (int)(Math.random()*255));\r
397                   setColor((SequenceNode)tree.getGroups().elementAt(i),col.brighter());\r
398 \r
399                   Vector l = tree.findLeaves((SequenceNode)tree.getGroups().elementAt(i),new Vector());\r
400                   SequenceGroup sg = null;\r
401                   for (int j = 0; j < l.size(); j++)\r
402                   {\r
403                     SequenceNode sn = (SequenceNode) l.elementAt(j);\r
404                     if(sg==null)\r
405                        sg = new SequenceGroup("TreeGroup", av.getGlobalColourScheme(), true, true,false,0,av.alignment.getWidth());\r
406 \r
407                     sg.addSequence( (Sequence) sn.element());\r
408                   }\r
409 \r
410                   if (av.getGlobalColourScheme() instanceof ConservationColourScheme)\r
411                   {\r
412                     ConservationColourScheme ccs = (ConservationColourScheme) av.getGlobalColourScheme();\r
413                     Conservation c = new Conservation("Group",\r
414                                                       ResidueProperties.propHash, 3,\r
415                                                       sg.sequences, sg.getStartRes(),\r
416                                                       sg.getEndRes());\r
417 \r
418                     c.calculate();\r
419                     c.verdict(false, av.ConsPercGaps);\r
420                     ccs = new ConservationColourScheme(c, ccs.cs);\r
421 \r
422                     sg.cs = ccs;\r
423 \r
424                   }\r
425 \r
426 \r
427 \r
428                   av.alignment.addGroup(sg);\r
429 \r
430               }\r
431           }\r
432       }\r
433 \r
434       PaintRefresher.Refresh(this);\r
435       repaint();\r
436 \r
437   }\r
438 \r
439     public void setShowDistances(boolean state) {\r
440         this.showDistances = state;\r
441         repaint();\r
442     }\r
443 \r
444     public void setShowBootstrap(boolean state) {\r
445       this.showBootstrap = state;\r
446       repaint();\r
447     }\r
448     public void setMarkPlaceholders(boolean state) {\r
449             this.markPlaceholders = state;\r
450             repaint();\r
451     }\r
452 \r
453 }\r
454 \r