Clover icon

Coverage Report

  1. Project Clover database Fri Dec 6 2024 13:47:14 GMT
  2. Package jalview.appletgui

File TreeCanvas.java

 

Coverage histogram

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

Code metrics

122
259
22
1
743
569
99
0.38
11.77
22
4.5

Classes

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