Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.appletgui

File TreeCanvas.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
56% of files have more coverage

Code metrics

122
259
22
1
741
567
97
0.37
11.77
22
4.41

Classes

Class Line # Actions
TreeCanvas 54 259 97
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

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