2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.appletgui;
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;
38 import java.awt.Color;
39 import java.awt.Dimension;
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;
55 public class TreeCanvas extends Panel
56 implements MouseListener, MouseMotionListener
60 ScrollPane scrollPane;
64 public static final String PLACEHOLDER = " * ";
68 boolean fitToWindow = true;
70 boolean showDistances = false;
72 boolean showBootstrap = false;
74 boolean markPlaceholders = false;
86 Hashtable nameHash = new Hashtable();
88 Hashtable nodeHash = new Hashtable();
90 BinaryNode highlightNode;
94 public TreeCanvas(AlignmentPanel ap, ScrollPane scroller)
99 scrollPane = scroller;
100 addMouseListener(this);
101 addMouseMotionListener(this);
104 PaintRefresher.Register(this, av.getSequenceSetId());
107 public void treeSelectionChanged(SequenceI sequence)
109 SequenceGroup selected = av.getSelectionGroup();
110 if (selected == null)
112 selected = new SequenceGroup();
113 av.setSelectionGroup(selected);
116 selected.setEndRes(av.getAlignment().getWidth() - 1);
117 selected.addOrRemove(sequence, true);
120 public void setTree(TreeModel tree2)
123 tree2.findHeight(tree2.getTopNode());
125 // Now have to calculate longest name based on the leaves
126 Vector<BinaryNode> leaves = tree2.findLeaves(tree2.getTopNode());
127 boolean has_placeholders = false;
130 for (int i = 0; i < leaves.size(); i++)
132 BinaryNode lf = leaves.elementAt(i);
134 if (lf instanceof SequenceNode && ((SequenceNode) lf).isPlaceholder())
136 has_placeholders = true;
139 if (longestName.length() < ((Sequence) lf.element()).getName()
142 longestName = TreeCanvas.PLACEHOLDER
143 + ((Sequence) lf.element()).getName();
147 setMarkPlaceholders(has_placeholders);
150 public void drawNode(Graphics g, BinaryNode node, float chunk,
151 double scale, int width, int offx, int offy)
158 if (node.left() == null && node.right() == null)
162 double height = node.height;
163 double dist = node.dist;
165 int xstart = (int) ((height - dist) * scale) + offx;
166 int xend = (int) (height * scale) + offx;
168 int ypos = (int) (node.ycount * chunk) + offy;
170 if (node.element() instanceof SequenceI)
172 SequenceI seq = (SequenceI) node.element();
174 if (av.getSequenceColour(seq) == Color.white)
176 g.setColor(Color.black);
180 g.setColor(av.getSequenceColour(seq).darker());
186 g.setColor(Color.black);
189 // Draw horizontal line
190 g.drawLine(xstart, ypos, xend, ypos);
192 String nodeLabel = "";
193 if (showDistances && node.dist > 0)
195 nodeLabel = new Format("%-.2f").form(node.dist);
199 int btstrap = node.getBootstrap();
204 nodeLabel = nodeLabel + " : ";
206 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
209 if (!nodeLabel.equals(""))
211 g.drawString(nodeLabel, xstart + 2, ypos - 2);
214 String name = (markPlaceholders && node instanceof SequenceNode
215 && ((SequenceNode) node).isPlaceholder())
216 ? (PLACEHOLDER + node.getName())
218 FontMetrics fm = g.getFontMetrics(font);
219 int charWidth = fm.stringWidth(name) + 3;
220 int charHeight = fm.getHeight();
222 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,
223 charWidth, charHeight);
225 nameHash.put(node.element(), rect);
227 // Colour selected leaves differently
228 SequenceGroup selected = av.getSelectionGroup();
230 && selected.getSequences(null).contains(node.element()))
232 g.setColor(Color.gray);
234 g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);
235 g.setColor(Color.white);
237 g.drawString(name, xend + 10, ypos);
238 g.setColor(Color.black);
242 drawNode(g, (BinaryNode) node.left(), chunk, scale, width, offx,
244 drawNode(g, (BinaryNode) node.right(), chunk, scale, width, offx,
247 double height = node.height;
248 double dist = node.dist;
250 int xstart = (int) ((height - dist) * scale) + offx;
251 int xend = (int) (height * scale) + offx;
252 int ypos = (int) (node.ycount * chunk) + offy;
254 g.setColor(node.color.darker());
256 // Draw horizontal line
257 g.drawLine(xstart, ypos, xend, ypos);
258 if (node == highlightNode)
260 g.fillRect(xend - 3, ypos - 3, 6, 6);
264 g.fillRect(xend - 2, ypos - 2, 4, 4);
267 int ystart = (int) (node.left() == null ? 0
268 : (((BinaryNode) node.left()).ycount * chunk)) + offy;
269 int yend = (int) (node.right() == null ? 0
270 : (((BinaryNode) node.right()).ycount * chunk)) + offy;
272 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
273 nodeHash.put(node, pos);
275 g.drawLine((int) (height * scale) + offx, ystart,
276 (int) (height * scale) + offx, yend);
278 String nodeLabel = "";
280 if (showDistances && (node.dist > 0))
282 nodeLabel = new Format("%-.2f").form(node.dist);
287 int btstrap = node.getBootstrap();
292 nodeLabel = nodeLabel + " : ";
294 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
298 if (!nodeLabel.equals(""))
300 g.drawString(nodeLabel, xstart + 2, ypos - 2);
306 public Object findElement(int x, int y)
308 Enumeration keys = nameHash.keys();
310 while (keys.hasMoreElements())
312 Object ob = keys.nextElement();
313 Rectangle rect = (Rectangle) nameHash.get(ob);
315 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
316 && y <= (rect.y + rect.height))
321 keys = nodeHash.keys();
323 while (keys.hasMoreElements())
325 Object ob = keys.nextElement();
326 Rectangle rect = (Rectangle) nodeHash.get(ob);
328 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
329 && y <= (rect.y + rect.height))
338 public void pickNodes(Rectangle pickBox)
340 int width = getSize().width;
341 int height = getSize().height;
343 BinaryNode top = tree.getTopNode();
345 double wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
348 top.count = ((BinaryNode) top.left()).count
349 + ((BinaryNode) top.right()).count;
351 float chunk = (float) (height - offy) / top.count;
353 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
356 public void pickNode(Rectangle pickBox, BinaryNode node, float chunk,
357 double scale, int width, int offx, int offy)
364 if (node.left() == null && node.right() == null)
366 double height = node.height;
367 // float dist = node.dist;
369 // int xstart = (int) ( (height - dist) * scale) + offx;
370 int xend = (int) (height * scale) + offx;
372 int ypos = (int) (node.ycount * chunk) + offy;
374 if (pickBox.contains(new Point(xend, ypos)))
376 if (node.element() instanceof SequenceI)
378 SequenceI seq = (SequenceI) node.element();
379 SequenceGroup sg = av.getSelectionGroup();
382 sg.addOrRemove(seq, true);
389 pickNode(pickBox, (BinaryNode) node.left(), chunk, scale, width, offx,
391 pickNode(pickBox, (BinaryNode) node.right(), chunk, scale, width,
396 public void setColor(BinaryNode node, Color c)
403 if (node.left() == null && node.right() == null)
407 if (node.element() instanceof SequenceI)
409 av.setSequenceColour((SequenceI) node.element(), c);
415 setColor((BinaryNode) node.left(), c);
416 setColor((BinaryNode) node.right(), c);
421 public void update(Graphics g)
427 public void paint(Graphics g)
434 if (nameHash.size() == 0)
439 int width = scrollPane.getSize().width;
440 int height = scrollPane.getSize().height;
443 height = g.getFontMetrics(font).getHeight() * nameHash.size();
446 if (getSize().width > width)
448 setSize(new Dimension(width, height));
449 scrollPane.validate();
453 setSize(new Dimension(width, height));
456 draw(g, width, height);
460 public void draw(Graphics g, int width, int height)
462 offy = font.getSize() + 10;
464 g.setColor(Color.white);
465 g.fillRect(0, 0, width, height);
467 labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; // 20
472 double wscale = (width - labelLength - offx * 2) / tree.getMaxHeight();
474 BinaryNode top = tree.getTopNode();
478 top.count = ((BinaryNode) top.left()).count
479 + ((BinaryNode) top.right()).count;
481 float chunk = (float) (height - offy) / top.count;
483 drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
487 if (av.getCurrentTree() == tree)
489 g.setColor(Color.red);
493 g.setColor(Color.gray);
496 int x = (int) (threshold * (getSize().width - labelLength - 2 * offx)
499 g.drawLine(x, 0, x, getSize().height);
505 public void mouseReleased(MouseEvent e)
510 public void mouseEntered(MouseEvent e)
515 public void mouseExited(MouseEvent e)
520 public void mouseClicked(MouseEvent evt)
522 if (highlightNode != null)
524 if (evt.getClickCount() > 1)
526 tree.swapNodes(highlightNode);
527 tree.reCount(tree.getTopNode());
528 tree.findHeight(tree.getTopNode());
532 Vector<BinaryNode> leaves = tree.findLeaves(highlightNode);
534 for (int i = 0; i < leaves.size(); i++)
536 SequenceI seq = (SequenceI) leaves.elementAt(i).element();
537 treeSelectionChanged(seq);
541 PaintRefresher.Refresh(this, av.getSequenceSetId());
548 public void mouseDragged(MouseEvent ect)
553 public void mouseMoved(MouseEvent evt)
555 av.setCurrentTree(tree);
557 Object ob = findElement(evt.getX(), evt.getY());
559 if (ob instanceof BinaryNode)
561 highlightNode = (BinaryNode) ob;
566 if (highlightNode != null)
568 highlightNode = null;
575 public void mousePressed(MouseEvent e)
577 av.setCurrentTree(tree);
582 Object ob = findElement(x, y);
584 if (ob instanceof SequenceI)
586 treeSelectionChanged((Sequence) ob);
587 PaintRefresher.Refresh(this, av.getSequenceSetId());
592 else if (!(ob instanceof SequenceNode))
596 if (tree.getMaxHeight() != 0)
598 threshold = (float) (x - offx)
599 / (float) (getSize().width - labelLength - 2 * offx);
601 List<BinaryNode> groups = tree.groupNodes(threshold);
602 setColor(tree.getTopNode(), Color.black);
604 av.setSelectionGroup(null);
605 av.getAlignment().deleteAllGroups();
606 av.clearSequenceColours();
607 final AlignViewportI codingComplement = av.getCodingComplement();
608 if (codingComplement != null)
610 codingComplement.setSelectionGroup(null);
611 codingComplement.getAlignment().deleteAllGroups();
612 codingComplement.clearSequenceColours();
615 colourGroups(groups);
620 PaintRefresher.Refresh(this, av.getSequenceSetId());
625 void colourGroups(List<BinaryNode> groups)
627 for (int i = 0; i < groups.size(); i++)
630 Color col = new Color((int) (Math.random() * 255),
631 (int) (Math.random() * 255), (int) (Math.random() * 255));
632 setColor(groups.get(i), col.brighter());
634 Vector<BinaryNode> l = tree.findLeaves(groups.get(i));
636 Vector<SequenceI> sequences = new Vector<>();
637 for (int j = 0; j < l.size(); j++)
639 SequenceI s1 = (SequenceI) l.elementAt(j).element();
640 if (!sequences.contains(s1))
642 sequences.addElement(s1);
646 ColourSchemeI cs = null;
648 SequenceGroup sg = new SequenceGroup(sequences, "", cs, true, true,
649 false, 0, av.getAlignment().getWidth() - 1);
651 if (av.getGlobalColourScheme() != null)
653 if (av.getGlobalColourScheme() instanceof UserColourScheme)
655 cs = new UserColourScheme(
656 ((UserColourScheme) av.getGlobalColourScheme())
662 cs = ColourSchemeProperty.getColourScheme(av, sg,
664 .getColourName(av.getGlobalColourScheme()));
666 // cs is null if shading is an annotationColourGradient
669 // cs.setThreshold(av.getViewportColourScheme().getThreshold(),
670 // av.isIgnoreGapsConsensus());
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 sg.setColourScheme(cs);
677 sg.getGroupColourScheme().setThreshold(
678 av.getResidueShading().getThreshold(),
679 av.isIgnoreGapsConsensus());
681 sg.setName("JTreeGroup:" + sg.hashCode());
683 if (av.getGlobalColourScheme() != null
684 && av.getResidueShading().conservationApplied())
686 Conservation c = new Conservation("Group", sg.getSequences(null),
687 sg.getStartRes(), sg.getEndRes());
690 c.verdict(false, av.getConsPercGaps());
692 sg.setColourScheme(cs);
693 sg.getGroupColourScheme().setConservation(c);
696 av.getAlignment().addGroup(sg);
698 // TODO this is duplicated with gui TreeCanvas - refactor
699 av.getAlignment().addGroup(sg);
700 final AlignViewportI codingComplement = av.getCodingComplement();
701 if (codingComplement != null)
703 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, av,
705 if (mappedGroup.getSequences().size() > 0)
707 codingComplement.getAlignment().addGroup(mappedGroup);
708 for (SequenceI seq : mappedGroup.getSequences())
710 // TODO why does gui require col.brighter() here??
711 codingComplement.setSequenceColour(seq, col);
717 ap.updateAnnotation();
718 if (av.getCodingComplement() != null)
720 ((AlignmentViewport) av.getCodingComplement()).firePropertyChange(
721 "alignment", null, ap.av.getAlignment().getSequences());
725 public void setShowDistances(boolean state)
727 this.showDistances = state;
731 public void setShowBootstrap(boolean state)
733 this.showBootstrap = state;
737 public void setMarkPlaceholders(boolean state)
739 this.markPlaceholders = state;