JAL-1807 still testing
[jalviewjs.git] / unused / appletgui / TreeCanvas.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)\r
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors\r
4  * \r
5  * This file is part of Jalview.\r
6  * \r
7  * Jalview is free software: you can redistribute it and/or\r
8  * modify it under the terms of the GNU General Public License \r
9  * as published by the Free Software Foundation, either version 3\r
10  * of the License, or (at your option) any later version.\r
11  *  \r
12  * Jalview is distributed in the hope that it will be useful, but \r
13  * WITHOUT ANY WARRANTY; without even the implied warranty \r
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR \r
15  * PURPOSE.  See the GNU General Public License for more details.\r
16  * \r
17  * You should have received a copy of the GNU General Public License\r
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.\r
19  * The Jalview Authors are detailed in the 'AUTHORS' file.\r
20  */\r
21 package jalview.appletgui;\r
22 \r
23 import jalview.analysis.Conservation;\r
24 import jalview.analysis.NJTree;\r
25 import jalview.api.AlignViewportI;\r
26 import jalview.datamodel.Sequence;\r
27 import jalview.datamodel.SequenceGroup;\r
28 import jalview.datamodel.SequenceI;\r
29 import jalview.datamodel.SequenceNode;\r
30 import jalview.schemes.ColourSchemeI;\r
31 import jalview.schemes.ColourSchemeProperty;\r
32 import jalview.schemes.ResidueProperties;\r
33 import jalview.schemes.UserColourScheme;\r
34 import jalview.util.Format;\r
35 import jalview.util.MappingUtils;\r
36 import jalview.viewmodel.AlignmentViewport;\r
37 \r
38 import java.awt.Color;\r
39 import java.awt.Dimension;\r
40 import java.awt.Font;\r
41 import java.awt.FontMetrics;\r
42 import java.awt.Graphics;\r
43 import javax.swing.JPanel;\r
44 import java.awt.Point;\r
45 import java.awt.Rectangle;\r
46 import javax.swing.JScrollPane;\r
47 import java.awt.event.MouseEvent;\r
48 import java.awt.event.MouseListener;\r
49 import java.awt.event.MouseMotionListener;\r
50 import java.util.Enumeration;\r
51 import java.util.Hashtable;\r
52 import java.util.Vector;\r
53 \r
54 public class TreeCanvas extends JPanel implements MouseListener,\r
55         MouseMotionListener\r
56 {\r
57   NJTree tree;\r
58 \r
59   JScrollPane scrollPane;\r
60 \r
61   AlignViewport av;\r
62 \r
63   public static final String PLACEHOLDER = " * ";\r
64 \r
65   Font font;\r
66 \r
67   boolean fitToWindow = true;\r
68 \r
69   boolean showDistances = false;\r
70 \r
71   boolean showBootstrap = false;\r
72 \r
73   boolean markPlaceholders = false;\r
74 \r
75   int offx = 20;\r
76 \r
77   int offy;\r
78 \r
79   float threshold;\r
80 \r
81   String longestName;\r
82 \r
83   int labelLength = -1;\r
84 \r
85   Hashtable nameHash = new Hashtable();\r
86 \r
87   Hashtable nodeHash = new Hashtable();\r
88 \r
89   SequenceNode highlightNode;\r
90 \r
91   AlignmentPanel ap;\r
92 \r
93   public TreeCanvas(AlignmentPanel ap, JScrollPane scroller)\r
94   {\r
95     this.ap = ap;\r
96     this.av = ap.av;\r
97     font = av.getFont();\r
98     scrollPane = scroller;\r
99     addMouseListener(this);\r
100     addMouseMotionListener(this);\r
101     setLayout(null);\r
102 \r
103     PaintRefresher.Register(this, av.getSequenceSetId());\r
104   }\r
105 \r
106   public void treeSelectionChanged(SequenceI sequence)\r
107   {\r
108     SequenceGroup selected = av.getSelectionGroup();\r
109     if (selected == null)\r
110     {\r
111       selected = new SequenceGroup();\r
112       av.setSelectionGroup(selected);\r
113     }\r
114 \r
115     selected.setEndRes(av.getAlignment().getWidth() - 1);\r
116     selected.addOrRemove(sequence, true);\r
117   }\r
118 \r
119   public void setTree(NJTree tree)\r
120   {\r
121     this.tree = tree;\r
122     tree.findHeight(tree.getTopNode());\r
123 \r
124     // Now have to calculate longest name based on the leaves\r
125     Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());\r
126     boolean has_placeholders = false;\r
127     longestName = "";\r
128 \r
129     for (int i = 0; i < leaves.size(); i++)\r
130     {\r
131       SequenceNode lf = (SequenceNode) leaves.elementAt(i);\r
132 \r
133       if (lf.isPlaceholder())\r
134       {\r
135         has_placeholders = true;\r
136       }\r
137 \r
138       if (longestName.length() < ((Sequence) lf.element()).getName()\r
139               .length())\r
140       {\r
141         longestName = TreeCanvas.PLACEHOLDER\r
142                 + ((Sequence) lf.element()).getName();\r
143       }\r
144     }\r
145 \r
146     setMarkPlaceholders(has_placeholders);\r
147   }\r
148 \r
149   public void drawNode(Graphics g, SequenceNode node, float chunk,\r
150           float scale, int width, int offx, int offy)\r
151   {\r
152     if (node == null)\r
153     {\r
154       return;\r
155     }\r
156 \r
157     if (node.left() == null && node.right() == null)\r
158     {\r
159       // Drawing leaf node\r
160 \r
161       float height = node.height;\r
162       float dist = node.dist;\r
163 \r
164       int xstart = (int) ((height - dist) * scale) + offx;\r
165       int xend = (int) (height * scale) + offx;\r
166 \r
167       int ypos = (int) (node.ycount * chunk) + offy;\r
168 \r
169       if (node.element() instanceof SequenceI)\r
170       {\r
171         SequenceI seq = (SequenceI) node.element();\r
172 \r
173         if (av.getSequenceColour(seq) == Color.white)\r
174         {\r
175           g.setColor(Color.black);\r
176         }\r
177         else\r
178         {\r
179           g.setColor(av.getSequenceColour(seq).darker());\r
180         }\r
181 \r
182       }\r
183       else\r
184       {\r
185         g.setColor(Color.black);\r
186       }\r
187 \r
188       // Draw horizontal line\r
189       g.drawLine(xstart, ypos, xend, ypos);\r
190 \r
191       String nodeLabel = "";\r
192       if (showDistances && node.dist > 0)\r
193       {\r
194         nodeLabel = new Format("%-.2f").formDouble(node.dist);\r
195       }\r
196       if (showBootstrap)\r
197       {\r
198         int btstrap = node.getBootstrap();\r
199         if (btstrap > -1)\r
200         {\r
201           if (showDistances)\r
202           {\r
203             nodeLabel = nodeLabel + " : ";\r
204           }\r
205           nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());\r
206         }\r
207       }\r
208       if (!nodeLabel.equals(""))\r
209       {\r
210         g.drawString(nodeLabel, xstart + 2, ypos - 2);\r
211       }\r
212 \r
213       String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node\r
214               .getName()) : node.getName();\r
215       FontMetrics fm = g.getFontMetrics(font);\r
216       int charWidth = fm.stringWidth(name) + 3;\r
217       int charHeight = fm.getHeight();\r
218 \r
219       Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,\r
220               charWidth, charHeight);\r
221 \r
222       nameHash.put(node.element(), rect);\r
223 \r
224       // Colour selected leaves differently\r
225       SequenceGroup selected = av.getSelectionGroup();\r
226       if (selected != null\r
227               && selected.getSequences(null).contains(node.element()))\r
228       {\r
229         g.setColor(Color.gray);\r
230 \r
231         g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);\r
232         g.setColor(Color.white);\r
233       }\r
234       g.drawString(name, xend + 10, ypos);\r
235       g.setColor(Color.black);\r
236     }\r
237     else\r
238     {\r
239       drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,\r
240               offy);\r
241       drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,\r
242               offy);\r
243 \r
244       float height = node.height;\r
245       float dist = node.dist;\r
246 \r
247       int xstart = (int) ((height - dist) * scale) + offx;\r
248       int xend = (int) (height * scale) + offx;\r
249       int ypos = (int) (node.ycount * chunk) + offy;\r
250 \r
251       g.setColor(node.color.darker());\r
252 \r
253       // Draw horizontal line\r
254       g.drawLine(xstart, ypos, xend, ypos);\r
255       if (node == highlightNode)\r
256       {\r
257         g.fillRect(xend - 3, ypos - 3, 6, 6);\r
258       }\r
259       else\r
260       {\r
261         g.fillRect(xend - 2, ypos - 2, 4, 4);\r
262       }\r
263 \r
264       int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)\r
265               + offy;\r
266       int yend = (int) (((SequenceNode) node.right()).ycount * chunk)\r
267               + offy;\r
268 \r
269       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);\r
270       nodeHash.put(node, pos);\r
271 \r
272       g.drawLine((int) (height * scale) + offx, ystart,\r
273               (int) (height * scale) + offx, yend);\r
274 \r
275       String nodeLabel = "";\r
276 \r
277       if (showDistances && (node.dist > 0))\r
278       {\r
279         nodeLabel = new Format("%-.2f").formDouble(node.dist);\r
280       }\r
281 \r
282       if (showBootstrap)\r
283       {\r
284         int btstrap = node.getBootstrap();\r
285         if (btstrap > -1)\r
286         {\r
287           if (showDistances)\r
288           {\r
289             nodeLabel = nodeLabel + " : ";\r
290           }\r
291           nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());\r
292         }\r
293       }\r
294 \r
295       if (!nodeLabel.equals(""))\r
296       {\r
297         g.drawString(nodeLabel, xstart + 2, ypos - 2);\r
298       }\r
299 \r
300     }\r
301   }\r
302 \r
303   public Object findElement(int x, int y)\r
304   {\r
305     Enumeration keys = nameHash.keys();\r
306 \r
307     while (keys.hasMoreElements())\r
308     {\r
309       Object ob = keys.nextElement();\r
310       Rectangle rect = (Rectangle) nameHash.get(ob);\r
311 \r
312       if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y\r
313               && y <= (rect.y + rect.height))\r
314       {\r
315         return ob;\r
316       }\r
317     }\r
318     keys = nodeHash.keys();\r
319 \r
320     while (keys.hasMoreElements())\r
321     {\r
322       Object ob = keys.nextElement();\r
323       Rectangle rect = (Rectangle) nodeHash.get(ob);\r
324 \r
325       if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y\r
326               && y <= (rect.y + rect.height))\r
327       {\r
328         return ob;\r
329       }\r
330     }\r
331     return null;\r
332 \r
333   }\r
334 \r
335   public void pickNodes(Rectangle pickBox)\r
336   {\r
337     int width = getSize().width;\r
338     int height = getSize().height;\r
339 \r
340     SequenceNode top = tree.getTopNode();\r
341 \r
342     float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();\r
343     if (top.count == 0)\r
344     {\r
345       top.count = ((SequenceNode) top.left()).count\r
346               + ((SequenceNode) top.right()).count;\r
347     }\r
348     float chunk = (float) (height - offy) / top.count;\r
349 \r
350     pickNode(pickBox, top, chunk, wscale, width, offx, offy);\r
351   }\r
352 \r
353   public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,\r
354           float scale, int width, int offx, int offy)\r
355   {\r
356     if (node == null)\r
357     {\r
358       return;\r
359     }\r
360 \r
361     if (node.left() == null && node.right() == null)\r
362     {\r
363       float height = node.height;\r
364       // float dist = node.dist;\r
365 \r
366       // int xstart = (int) ( (height - dist) * scale) + offx;\r
367       int xend = (int) (height * scale) + offx;\r
368 \r
369       int ypos = (int) (node.ycount * chunk) + offy;\r
370 \r
371       if (pickBox.contains(new Point(xend, ypos)))\r
372       {\r
373         if (node.element() instanceof SequenceI)\r
374         {\r
375           SequenceI seq = (SequenceI) node.element();\r
376           SequenceGroup sg = av.getSelectionGroup();\r
377           if (sg != null)\r
378           {\r
379             sg.addOrRemove(seq, true);\r
380           }\r
381         }\r
382       }\r
383     }\r
384     else\r
385     {\r
386       pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,\r
387               offx, offy);\r
388       pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,\r
389               offx, offy);\r
390     }\r
391   }\r
392 \r
393   public void setColor(SequenceNode node, Color c)\r
394   {\r
395     if (node == null)\r
396     {\r
397       return;\r
398     }\r
399 \r
400     if (node.left() == null && node.right() == null)\r
401     {\r
402       node.color = c;\r
403 \r
404       if (node.element() instanceof SequenceI)\r
405       {\r
406         av.setSequenceColour((SequenceI) node.element(), c);\r
407       }\r
408     }\r
409     else\r
410     {\r
411       node.color = c;\r
412       setColor((SequenceNode) node.left(), c);\r
413       setColor((SequenceNode) node.right(), c);\r
414     }\r
415   }\r
416 \r
417   @Override\r
418   public void update(Graphics g)\r
419   {\r
420     paint(g);\r
421   }\r
422 \r
423   @Override\r
424   public void paint(Graphics g)\r
425   {\r
426     if (tree == null)\r
427     {\r
428       return;\r
429     }\r
430 \r
431     if (nameHash.size() == 0)\r
432     {\r
433       repaint();\r
434     }\r
435 \r
436     int width = scrollPane.getSize().width;\r
437     int height = scrollPane.getSize().height;\r
438     if (!fitToWindow)\r
439     {\r
440       height = g.getFontMetrics(font).getHeight() * nameHash.size();\r
441     }\r
442 \r
443     if (getSize().width > width)\r
444     {\r
445       setSize(new Dimension(width, height));\r
446       scrollPane.validate();\r
447       return;\r
448     }\r
449 \r
450     setSize(new Dimension(width, height));\r
451 \r
452     g.setFont(font);\r
453     draw(g, width, height);\r
454     validate();\r
455   }\r
456 \r
457   public void draw(Graphics g, int width, int height)\r
458   {\r
459     offy = font.getSize() + 10;\r
460 \r
461     g.setColor(Color.white);\r
462     g.fillRect(0, 0, width, height);\r
463 \r
464     labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; // 20\r
465     // allows\r
466     // for\r
467     // scrollbar\r
468 \r
469     float wscale = (width - labelLength - offx * 2) / tree.getMaxHeight();\r
470 \r
471     SequenceNode top = tree.getTopNode();\r
472 \r
473     if (top.count == 0)\r
474     {\r
475       top.count = ((SequenceNode) top.left()).count\r
476               + ((SequenceNode) top.right()).count;\r
477     }\r
478     float chunk = (float) (height - offy) / top.count;\r
479 \r
480     drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);\r
481 \r
482     if (threshold != 0)\r
483     {\r
484       if (av.getCurrentTree() == tree)\r
485       {\r
486         g.setColor(Color.red);\r
487       }\r
488       else\r
489       {\r
490         g.setColor(Color.gray);\r
491       }\r
492 \r
493       int x = (int) (threshold * (getSize().width - labelLength - 2 * offx) + offx);\r
494 \r
495       g.drawLine(x, 0, x, getSize().height);\r
496     }\r
497 \r
498   }\r
499 \r
500   @Override\r
501   public void mouseReleased(MouseEvent e)\r
502   {\r
503   }\r
504 \r
505   @Override\r
506   public void mouseEntered(MouseEvent e)\r
507   {\r
508   }\r
509 \r
510   @Override\r
511   public void mouseExited(MouseEvent e)\r
512   {\r
513   }\r
514 \r
515   @Override\r
516   public void mouseClicked(MouseEvent evt)\r
517   {\r
518     if (highlightNode != null)\r
519     {\r
520       if (evt.getClickCount() > 1)\r
521       {\r
522         tree.swapNodes(highlightNode);\r
523         tree.reCount(tree.getTopNode());\r
524         tree.findHeight(tree.getTopNode());\r
525       }\r
526       else\r
527       {\r
528         Vector leaves = new Vector();\r
529         tree.findLeaves(highlightNode, leaves);\r
530 \r
531         for (int i = 0; i < leaves.size(); i++)\r
532         {\r
533           SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))\r
534                   .element();\r
535           treeSelectionChanged(seq);\r
536         }\r
537       }\r
538 \r
539       PaintRefresher.Refresh(this, av.getSequenceSetId());\r
540       repaint();\r
541       av.sendSelection();\r
542     }\r
543   }\r
544 \r
545   @Override\r
546   public void mouseDragged(MouseEvent ect)\r
547   {\r
548   }\r
549 \r
550   @Override\r
551   public void mouseMoved(MouseEvent evt)\r
552   {\r
553     av.setCurrentTree(tree);\r
554 \r
555     Object ob = findElement(evt.getX(), evt.getY());\r
556 \r
557     if (ob instanceof SequenceNode)\r
558     {\r
559       highlightNode = (SequenceNode) ob;\r
560       repaint();\r
561     }\r
562     else\r
563     {\r
564       if (highlightNode != null)\r
565       {\r
566         highlightNode = null;\r
567         repaint();\r
568       }\r
569     }\r
570   }\r
571 \r
572   @Override\r
573   public void mousePressed(MouseEvent e)\r
574   {\r
575     av.setCurrentTree(tree);\r
576 \r
577     int x = e.getX();\r
578     int y = e.getY();\r
579 \r
580     Object ob = findElement(x, y);\r
581 \r
582     if (ob instanceof SequenceI)\r
583     {\r
584       treeSelectionChanged((Sequence) ob);\r
585       PaintRefresher.Refresh(this, av.getSequenceSetId());\r
586       repaint();\r
587       av.sendSelection();\r
588       return;\r
589     }\r
590     else if (!(ob instanceof SequenceNode))\r
591     {\r
592       // Find threshold\r
593 \r
594       if (tree.getMaxHeight() != 0)\r
595       {\r
596         threshold = (float) (x - offx)\r
597                 / (float) (getSize().width - labelLength - 2 * offx);\r
598 \r
599         tree.getGroups().removeAllElements();\r
600         tree.groupNodes(tree.getTopNode(), threshold);\r
601         setColor(tree.getTopNode(), Color.black);\r
602 \r
603         av.setSelectionGroup(null);\r
604         av.getAlignment().deleteAllGroups();\r
605         av.clearSequenceColours();\r
606         final AlignViewportI codingComplement = av.getCodingComplement();\r
607         if (codingComplement != null)\r
608         {\r
609           codingComplement.setSelectionGroup(null);\r
610           codingComplement.getAlignment().deleteAllGroups();\r
611           codingComplement.clearSequenceColours();\r
612         }\r
613 \r
614         colourGroups();\r
615 \r
616       }\r
617     }\r
618 \r
619     PaintRefresher.Refresh(this, av.getSequenceSetId());\r
620     repaint();\r
621 \r
622   }\r
623 \r
624   void colourGroups()\r
625   {\r
626     for (int i = 0; i < tree.getGroups().size(); i++)\r
627     {\r
628 \r
629       Color col = new Color((int) (Math.random() * 255),\r
630               (int) (Math.random() * 255), (int) (Math.random() * 255));\r
631       setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());\r
632 \r
633       Vector l = tree.findLeaves(\r
634               (SequenceNode) tree.getGroups().elementAt(i), new Vector());\r
635 \r
636       Vector sequences = new Vector();\r
637       for (int j = 0; j < l.size(); j++)\r
638       {\r
639         SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))\r
640                 .element();\r
641         if (!sequences.contains(s1))\r
642         {\r
643           sequences.addElement(s1);\r
644         }\r
645       }\r
646 \r
647       ColourSchemeI cs = null;\r
648 \r
649       SequenceGroup sg = new SequenceGroup(sequences, "", cs, true, true,\r
650               false, 0, av.getAlignment().getWidth() - 1);\r
651 \r
652       if (av.getGlobalColourScheme() != null)\r
653       {\r
654         if (av.getGlobalColourScheme() instanceof UserColourScheme)\r
655         {\r
656           cs = new UserColourScheme(\r
657                   ((UserColourScheme) av.getGlobalColourScheme())\r
658                           .getColours());\r
659 \r
660         }\r
661         else\r
662         {\r
663           cs = ColourSchemeProperty.getColour(sg, ColourSchemeProperty\r
664                   .getColourName(av.getGlobalColourScheme()));\r
665         }\r
666         // cs is null if shading is an annotationColourGradient\r
667         if (cs != null)\r
668         {\r
669           cs.setThreshold(av.getGlobalColourScheme().getThreshold(),\r
670                   av.isIgnoreGapsConsensus());\r
671         }\r
672       }\r
673       // TODO: cs used to be initialized with a sequence collection and\r
674       // recalcConservation called automatically\r
675       // instead we set it manually - recalc called after updateAnnotation\r
676       sg.cs = cs;\r
677 \r
678       sg.setName("JTreeGroup:" + sg.hashCode());\r
679       sg.setIdColour(col);\r
680       if (av.getGlobalColourScheme() != null\r
681               && av.getGlobalColourScheme().conservationApplied())\r
682       {\r
683         Conservation c = new Conservation("Group",\r
684                 ResidueProperties.propHash, 3, sg.getSequences(null),\r
685                 sg.getStartRes(), sg.getEndRes());\r
686 \r
687         c.calculate();\r
688         c.verdict(false, av.getConsPercGaps());\r
689         cs.setConservation(c);\r
690 \r
691         sg.cs = cs;\r
692 \r
693       }\r
694 \r
695       av.getAlignment().addGroup(sg);\r
696 \r
697       // TODO this is duplicated with gui TreeCanvas - refactor\r
698       av.getAlignment().addGroup(sg);\r
699       final AlignViewportI codingComplement = av.getCodingComplement();\r
700       if (codingComplement != null)\r
701       {\r
702         SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, av,\r
703                 codingComplement);\r
704         if (mappedGroup.getSequences().size() > 0)\r
705         {\r
706           codingComplement.getAlignment().addGroup(mappedGroup);\r
707           for (SequenceI seq : mappedGroup.getSequences())\r
708           {\r
709             // TODO why does gui require col.brighter() here??\r
710             codingComplement.setSequenceColour(seq, col);\r
711           }\r
712         }\r
713       }\r
714 \r
715     }\r
716     ap.updateAnnotation();\r
717     if (av.getCodingComplement() != null)\r
718     {\r
719       ((AlignmentViewport) av.getCodingComplement()).firePropertyChange(\r
720               "alignment", null, ap.av.getAlignment().getSequences());\r
721     }\r
722   }\r
723 \r
724   public void setShowDistances(boolean state)\r
725   {\r
726     this.showDistances = state;\r
727     repaint();\r
728   }\r
729 \r
730   public void setShowBootstrap(boolean state)\r
731   {\r
732     this.showBootstrap = state;\r
733     repaint();\r
734   }\r
735 \r
736   public void setMarkPlaceholders(boolean state)\r
737   {\r
738     this.markPlaceholders = state;\r
739     repaint();\r
740   }\r
741 \r
742 }\r