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 && ((SequenceNode) node).isPlaceholder())
215 ? (PLACEHOLDER + node.getName())
217 FontMetrics fm = g.getFontMetrics(font);
218 int charWidth = fm.stringWidth(name) + 3;
219 int charHeight = fm.getHeight();
221 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,
222 charWidth, charHeight);
224 nameHash.put(node.element(), rect);
226 // Colour selected leaves differently
227 SequenceGroup selected = av.getSelectionGroup();
229 && selected.getSequences(null).contains(node.element()))
231 g.setColor(Color.gray);
233 g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);
234 g.setColor(Color.white);
236 g.drawString(name, xend + 10, ypos);
237 g.setColor(Color.black);
241 drawNode(g, (BinaryNode) node.left(), chunk, scale, width, offx,
243 drawNode(g, (BinaryNode) node.right(), chunk, scale, width, offx,
246 double height = node.height;
247 double dist = node.dist;
249 int xstart = (int) ((height - dist) * scale) + offx;
250 int xend = (int) (height * scale) + offx;
251 int ypos = (int) (node.ycount * chunk) + offy;
253 g.setColor(node.color.darker());
255 // Draw horizontal line
256 g.drawLine(xstart, ypos, xend, ypos);
257 if (node == highlightNode)
259 g.fillRect(xend - 3, ypos - 3, 6, 6);
263 g.fillRect(xend - 2, ypos - 2, 4, 4);
266 int ystart = (int) (node.left() == null ? 0
267 : (((BinaryNode) node.left()).ycount * chunk)) + offy;
268 int yend = (int) (node.right() == null ? 0
269 : (((BinaryNode) node.right()).ycount * chunk)) + offy;
271 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
272 nodeHash.put(node, pos);
274 g.drawLine((int) (height * scale) + offx, ystart,
275 (int) (height * scale) + offx, yend);
277 String nodeLabel = "";
279 if (showDistances && (node.dist > 0))
281 nodeLabel = new Format("%-.2f").form(node.dist);
286 int btstrap = node.getBootstrap();
291 nodeLabel = nodeLabel + " : ";
293 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
297 if (!nodeLabel.equals(""))
299 g.drawString(nodeLabel, xstart + 2, ypos - 2);
305 public Object findElement(int x, int y)
307 Enumeration keys = nameHash.keys();
309 while (keys.hasMoreElements())
311 Object ob = keys.nextElement();
312 Rectangle rect = (Rectangle) nameHash.get(ob);
314 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
315 && y <= (rect.y + rect.height))
320 keys = nodeHash.keys();
322 while (keys.hasMoreElements())
324 Object ob = keys.nextElement();
325 Rectangle rect = (Rectangle) nodeHash.get(ob);
327 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
328 && y <= (rect.y + rect.height))
337 public void pickNodes(Rectangle pickBox)
339 int width = getSize().width;
340 int height = getSize().height;
342 BinaryNode top = tree.getTopNode();
344 double wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
347 top.count = ((BinaryNode) top.left()).count
348 + ((BinaryNode) top.right()).count;
350 float chunk = (float) (height - offy) / top.count;
352 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
355 public void pickNode(Rectangle pickBox, BinaryNode node, float chunk,
356 double scale, int width, int offx, int offy)
363 if (node.left() == null && node.right() == null)
365 double height = node.height;
366 // float dist = node.dist;
368 // int xstart = (int) ( (height - dist) * scale) + offx;
369 int xend = (int) (height * scale) + offx;
371 int ypos = (int) (node.ycount * chunk) + offy;
373 if (pickBox.contains(new Point(xend, ypos)))
375 if (node.element() instanceof SequenceI)
377 SequenceI seq = (SequenceI) node.element();
378 SequenceGroup sg = av.getSelectionGroup();
381 sg.addOrRemove(seq, true);
388 pickNode(pickBox, (BinaryNode) node.left(), chunk, scale, width,
390 pickNode(pickBox, (BinaryNode) node.right(), chunk, scale, width,
395 public void setColor(BinaryNode node, Color c)
402 if (node.left() == null && node.right() == null)
406 if (node.element() instanceof SequenceI)
408 av.setSequenceColour((SequenceI) node.element(), c);
414 setColor((BinaryNode) node.left(), c);
415 setColor((BinaryNode) node.right(), c);
420 public void update(Graphics g)
426 public void paint(Graphics g)
433 if (nameHash.size() == 0)
438 int width = scrollPane.getSize().width;
439 int height = scrollPane.getSize().height;
442 height = g.getFontMetrics(font).getHeight() * nameHash.size();
445 if (getSize().width > width)
447 setSize(new Dimension(width, height));
448 scrollPane.validate();
452 setSize(new Dimension(width, height));
455 draw(g, width, height);
459 public void draw(Graphics g, int width, int height)
461 offy = font.getSize() + 10;
463 g.setColor(Color.white);
464 g.fillRect(0, 0, width, height);
466 labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; // 20
471 double wscale = (width - labelLength - offx * 2) / tree.getMaxHeight();
473 BinaryNode top = tree.getTopNode();
477 top.count = ((BinaryNode) top.left()).count
478 + ((BinaryNode) top.right()).count;
480 float chunk = (float) (height - offy) / top.count;
482 drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
486 if (av.getCurrentTree() == tree)
488 g.setColor(Color.red);
492 g.setColor(Color.gray);
495 int x = (int) (threshold * (getSize().width - labelLength - 2 * offx)
498 g.drawLine(x, 0, x, getSize().height);
504 public void mouseReleased(MouseEvent e)
509 public void mouseEntered(MouseEvent e)
514 public void mouseExited(MouseEvent e)
519 public void mouseClicked(MouseEvent evt)
521 if (highlightNode != null)
523 if (evt.getClickCount() > 1)
525 tree.swapNodes(highlightNode);
526 tree.reCount(tree.getTopNode());
527 tree.findHeight(tree.getTopNode());
531 Vector<BinaryNode> leaves = tree.findLeaves(highlightNode);
533 for (int i = 0; i < leaves.size(); i++)
535 SequenceI seq = (SequenceI) leaves.elementAt(i).element();
536 treeSelectionChanged(seq);
540 PaintRefresher.Refresh(this, av.getSequenceSetId());
547 public void mouseDragged(MouseEvent ect)
552 public void mouseMoved(MouseEvent evt)
554 av.setCurrentTree(tree);
556 Object ob = findElement(evt.getX(), evt.getY());
558 if (ob instanceof BinaryNode)
560 highlightNode = (BinaryNode) ob;
565 if (highlightNode != null)
567 highlightNode = null;
574 public void mousePressed(MouseEvent e)
576 av.setCurrentTree(tree);
581 Object ob = findElement(x, y);
583 if (ob instanceof SequenceI)
585 treeSelectionChanged((Sequence) ob);
586 PaintRefresher.Refresh(this, av.getSequenceSetId());
591 else if (!(ob instanceof SequenceNode))
595 if (tree.getMaxHeight() != 0)
597 threshold = (float) (x - offx)
598 / (float) (getSize().width - labelLength - 2 * offx);
600 List<BinaryNode> groups = tree.groupNodes(threshold);
601 setColor(tree.getTopNode(), Color.black);
603 av.setSelectionGroup(null);
604 av.getAlignment().deleteAllGroups();
605 av.clearSequenceColours();
606 final AlignViewportI codingComplement = av.getCodingComplement();
607 if (codingComplement != null)
609 codingComplement.setSelectionGroup(null);
610 codingComplement.getAlignment().deleteAllGroups();
611 codingComplement.clearSequenceColours();
614 colourGroups(groups);
619 PaintRefresher.Refresh(this, av.getSequenceSetId());
624 void colourGroups(List<BinaryNode> groups)
626 for (int i = 0; i < groups.size(); i++)
629 Color col = new Color((int) (Math.random() * 255),
630 (int) (Math.random() * 255), (int) (Math.random() * 255));
631 setColor(groups.get(i), col.brighter());
633 Vector<BinaryNode> l = tree.findLeaves(groups.get(i));
635 Vector<SequenceI> sequences = new Vector<>();
636 for (int j = 0; j < l.size(); j++)
638 SequenceI s1 = (SequenceI) l.elementAt(j).element();
639 if (!sequences.contains(s1))
641 sequences.addElement(s1);
645 ColourSchemeI cs = null;
647 SequenceGroup sg = new SequenceGroup(sequences, "", cs, true, true,
648 false, 0, av.getAlignment().getWidth() - 1);
650 if (av.getGlobalColourScheme() != null)
652 if (av.getGlobalColourScheme() instanceof UserColourScheme)
654 cs = new UserColourScheme(
655 ((UserColourScheme) av.getGlobalColourScheme())
661 cs = ColourSchemeProperty.getColourScheme(av, sg,
663 .getColourName(av.getGlobalColourScheme()));
665 // cs is null if shading is an annotationColourGradient
668 // cs.setThreshold(av.getViewportColourScheme().getThreshold(),
669 // av.isIgnoreGapsConsensus());
672 // TODO: cs used to be initialized with a sequence collection and
673 // recalcConservation called automatically
674 // instead we set it manually - recalc called after updateAnnotation
675 sg.setColourScheme(cs);
676 sg.getGroupColourScheme().setThreshold(
677 av.getResidueShading().getThreshold(),
678 av.isIgnoreGapsConsensus());
680 sg.setName("JTreeGroup:" + sg.hashCode());
682 if (av.getGlobalColourScheme() != null
683 && av.getResidueShading().conservationApplied())
685 Conservation c = new Conservation("Group", sg.getSequences(null),
686 sg.getStartRes(), sg.getEndRes());
689 c.verdict(false, av.getConsPercGaps());
691 sg.setColourScheme(cs);
692 sg.getGroupColourScheme().setConservation(c);
695 av.getAlignment().addGroup(sg);
697 // TODO this is duplicated with gui TreeCanvas - refactor
698 av.getAlignment().addGroup(sg);
699 final AlignViewportI codingComplement = av.getCodingComplement();
700 if (codingComplement != null)
702 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, av,
704 if (mappedGroup.getSequences().size() > 0)
706 codingComplement.getAlignment().addGroup(mappedGroup);
707 for (SequenceI seq : mappedGroup.getSequences())
709 // TODO why does gui require col.brighter() here??
710 codingComplement.setSequenceColour(seq, col);
716 ap.updateAnnotation();
717 if (av.getCodingComplement() != null)
719 ((AlignmentViewport) av.getCodingComplement()).firePropertyChange(
720 "alignment", null, ap.av.getAlignment().getSequences());
724 public void setShowDistances(boolean state)
726 this.showDistances = state;
730 public void setShowBootstrap(boolean state)
732 this.showBootstrap = state;
736 public void setMarkPlaceholders(boolean state)
738 this.markPlaceholders = state;