2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3 * Copyright (C) 2014 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.NJTree;
25 import jalview.datamodel.Sequence;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.datamodel.SequenceNode;
29 import jalview.schemes.ColourSchemeI;
30 import jalview.schemes.ColourSchemeProperty;
31 import jalview.schemes.ResidueProperties;
32 import jalview.schemes.UserColourScheme;
33 import jalview.util.Format;
35 import java.awt.Color;
36 import java.awt.Dimension;
38 import java.awt.FontMetrics;
39 import java.awt.Graphics;
40 import java.awt.Panel;
41 import java.awt.Point;
42 import java.awt.Rectangle;
43 import java.awt.ScrollPane;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseListener;
46 import java.awt.event.MouseMotionListener;
47 import java.util.Enumeration;
48 import java.util.Hashtable;
49 import java.util.Vector;
51 public class TreeCanvas extends Panel implements MouseListener,
56 ScrollPane scrollPane;
60 public static final String PLACEHOLDER = " * ";
64 boolean fitToWindow = true;
66 boolean showDistances = false;
68 boolean showBootstrap = false;
70 boolean markPlaceholders = false;
82 Hashtable nameHash = new Hashtable();
84 Hashtable nodeHash = new Hashtable();
86 SequenceNode highlightNode;
90 public TreeCanvas(AlignmentPanel ap, ScrollPane scroller)
95 scrollPane = scroller;
96 addMouseListener(this);
97 addMouseMotionListener(this);
100 PaintRefresher.Register(this, av.getSequenceSetId());
103 public void treeSelectionChanged(SequenceI sequence)
105 SequenceGroup selected = av.getSelectionGroup();
106 if (selected == null)
108 selected = new SequenceGroup();
109 av.setSelectionGroup(selected);
112 selected.setEndRes(av.getAlignment().getWidth() - 1);
113 selected.addOrRemove(sequence, true);
116 public void setTree(NJTree tree)
119 tree.findHeight(tree.getTopNode());
121 // Now have to calculate longest name based on the leaves
122 Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
123 boolean has_placeholders = false;
126 for (int i = 0; i < leaves.size(); i++)
128 SequenceNode lf = (SequenceNode) leaves.elementAt(i);
130 if (lf.isPlaceholder())
132 has_placeholders = true;
135 if (longestName.length() < ((Sequence) lf.element()).getName()
138 longestName = TreeCanvas.PLACEHOLDER
139 + ((Sequence) lf.element()).getName();
143 setMarkPlaceholders(has_placeholders);
146 public void drawNode(Graphics g, SequenceNode node, float chunk,
147 float scale, int width, int offx, int offy)
154 if (node.left() == null && node.right() == null)
158 float height = node.height;
159 float dist = node.dist;
161 int xstart = (int) ((height - dist) * scale) + offx;
162 int xend = (int) (height * scale) + offx;
164 int ypos = (int) (node.ycount * chunk) + offy;
166 if (node.element() instanceof SequenceI)
168 SequenceI seq = (SequenceI) node.element();
170 if (av.getSequenceColour(seq) == Color.white)
172 g.setColor(Color.black);
176 g.setColor(av.getSequenceColour(seq).darker());
182 g.setColor(Color.black);
185 // Draw horizontal line
186 g.drawLine(xstart, ypos, xend, ypos);
188 String nodeLabel = "";
189 if (showDistances && node.dist > 0)
191 nodeLabel = new Format("%-.2f").form(node.dist);
195 int btstrap = node.getBootstrap();
200 nodeLabel = nodeLabel + " : ";
202 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
205 if (!nodeLabel.equals(""))
207 g.drawString(nodeLabel, xstart + 2, ypos - 2);
210 String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
211 .getName()) : node.getName();
212 FontMetrics fm = g.getFontMetrics(font);
213 int charWidth = fm.stringWidth(name) + 3;
214 int charHeight = fm.getHeight();
216 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,
217 charWidth, charHeight);
219 nameHash.put(node.element(), rect);
221 // Colour selected leaves differently
222 SequenceGroup selected = av.getSelectionGroup();
224 && selected.getSequences(null).contains(node.element()))
226 g.setColor(Color.gray);
228 g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);
229 g.setColor(Color.white);
231 g.drawString(name, xend + 10, ypos);
232 g.setColor(Color.black);
236 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
238 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
241 float height = node.height;
242 float dist = node.dist;
244 int xstart = (int) ((height - dist) * scale) + offx;
245 int xend = (int) (height * scale) + offx;
246 int ypos = (int) (node.ycount * chunk) + offy;
248 g.setColor(node.color.darker());
250 // Draw horizontal line
251 g.drawLine(xstart, ypos, xend, ypos);
252 if (node == highlightNode)
254 g.fillRect(xend - 3, ypos - 3, 6, 6);
258 g.fillRect(xend - 2, ypos - 2, 4, 4);
261 int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
263 int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
266 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
267 nodeHash.put(node, pos);
269 g.drawLine((int) (height * scale) + offx, ystart,
270 (int) (height * scale) + offx, yend);
272 String nodeLabel = "";
274 if (showDistances && (node.dist > 0))
276 nodeLabel = new Format("%-.2f").form(node.dist);
281 int btstrap = node.getBootstrap();
286 nodeLabel = nodeLabel + " : ";
288 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
292 if (!nodeLabel.equals(""))
294 g.drawString(nodeLabel, xstart + 2, ypos - 2);
300 public Object findElement(int x, int y)
302 Enumeration keys = nameHash.keys();
304 while (keys.hasMoreElements())
306 Object ob = keys.nextElement();
307 Rectangle rect = (Rectangle) nameHash.get(ob);
309 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
310 && y <= (rect.y + rect.height))
315 keys = nodeHash.keys();
317 while (keys.hasMoreElements())
319 Object ob = keys.nextElement();
320 Rectangle rect = (Rectangle) nodeHash.get(ob);
322 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
323 && y <= (rect.y + rect.height))
332 public void pickNodes(Rectangle pickBox)
334 int width = getSize().width;
335 int height = getSize().height;
337 SequenceNode top = tree.getTopNode();
339 float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
342 top.count = ((SequenceNode) top.left()).count
343 + ((SequenceNode) top.right()).count;
345 float chunk = (float) (height - offy) / top.count;
347 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
350 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
351 float scale, int width, int offx, int offy)
358 if (node.left() == null && node.right() == null)
360 float height = node.height;
361 // float dist = node.dist;
363 // int xstart = (int) ( (height - dist) * scale) + offx;
364 int xend = (int) (height * scale) + offx;
366 int ypos = (int) (node.ycount * chunk) + offy;
368 if (pickBox.contains(new Point(xend, ypos)))
370 if (node.element() instanceof SequenceI)
372 SequenceI seq = (SequenceI) node.element();
373 SequenceGroup sg = av.getSelectionGroup();
376 sg.addOrRemove(seq, true);
383 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
385 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
390 public void setColor(SequenceNode node, Color c)
397 if (node.left() == null && node.right() == null)
401 if (node.element() instanceof SequenceI)
403 av.setSequenceColour((SequenceI) node.element(), c);
409 setColor((SequenceNode) node.left(), c);
410 setColor((SequenceNode) node.right(), c);
415 public void update(Graphics g)
421 public void paint(Graphics g)
428 if (nameHash.size() == 0)
433 int width = scrollPane.getSize().width;
434 int height = scrollPane.getSize().height;
437 height = g.getFontMetrics(font).getHeight() * nameHash.size();
440 if (getSize().width > width)
442 setSize(new Dimension(width, height));
443 scrollPane.validate();
447 setSize(new Dimension(width, height));
450 draw(g, width, height);
454 public void draw(Graphics g, int width, int height)
456 offy = font.getSize() + 10;
458 g.setColor(Color.white);
459 g.fillRect(0, 0, width, height);
461 labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; // 20
466 float wscale = (width - labelLength - offx * 2) / tree.getMaxHeight();
468 SequenceNode top = tree.getTopNode();
472 top.count = ((SequenceNode) top.left()).count
473 + ((SequenceNode) top.right()).count;
475 float chunk = (float) (height - offy) / top.count;
477 drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
481 if (av.getCurrentTree() == tree)
483 g.setColor(Color.red);
487 g.setColor(Color.gray);
490 int x = (int) (threshold * (getSize().width - labelLength - 2 * offx) + offx);
492 g.drawLine(x, 0, x, getSize().height);
498 public void mouseReleased(MouseEvent e)
503 public void mouseEntered(MouseEvent e)
508 public void mouseExited(MouseEvent e)
513 public void mouseClicked(MouseEvent evt)
515 if (highlightNode != null)
517 if (evt.getClickCount() > 1)
519 tree.swapNodes(highlightNode);
520 tree.reCount(tree.getTopNode());
521 tree.findHeight(tree.getTopNode());
525 Vector leaves = new Vector();
526 tree.findLeaves(highlightNode, leaves);
528 for (int i = 0; i < leaves.size(); i++)
530 SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
532 treeSelectionChanged(seq);
536 PaintRefresher.Refresh(this, av.getSequenceSetId());
543 public void mouseDragged(MouseEvent ect)
548 public void mouseMoved(MouseEvent evt)
550 av.setCurrentTree(tree);
552 Object ob = findElement(evt.getX(), evt.getY());
554 if (ob instanceof SequenceNode)
556 highlightNode = (SequenceNode) ob;
561 if (highlightNode != null)
563 highlightNode = null;
570 public void mousePressed(MouseEvent e)
572 av.setCurrentTree(tree);
577 Object ob = findElement(x, y);
579 if (ob instanceof SequenceI)
581 treeSelectionChanged((Sequence) ob);
582 PaintRefresher.Refresh(this, av.getSequenceSetId());
587 else if (!(ob instanceof SequenceNode))
591 if (tree.getMaxHeight() != 0)
593 threshold = (float) (x - offx)
594 / (float) (getSize().width - labelLength - 2 * offx);
596 tree.getGroups().removeAllElements();
597 tree.groupNodes(tree.getTopNode(), threshold);
598 setColor(tree.getTopNode(), Color.black);
600 av.setSelectionGroup(null);
601 av.getAlignment().deleteAllGroups();
602 av.clearSequenceColours();
609 PaintRefresher.Refresh(this, av.getSequenceSetId());
616 for (int i = 0; i < tree.getGroups().size(); i++)
619 Color col = new Color((int) (Math.random() * 255),
620 (int) (Math.random() * 255), (int) (Math.random() * 255));
621 setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
623 Vector l = tree.findLeaves(
624 (SequenceNode) tree.getGroups().elementAt(i), new Vector());
626 Vector sequences = new Vector();
627 for (int j = 0; j < l.size(); j++)
629 SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
631 if (!sequences.contains(s1))
633 sequences.addElement(s1);
637 ColourSchemeI cs = null;
639 SequenceGroup sg = new SequenceGroup(sequences, "", cs, true, true,
640 false, 0, av.getAlignment().getWidth() - 1);
642 if (av.getGlobalColourScheme() != null)
644 if (av.getGlobalColourScheme() instanceof UserColourScheme)
646 cs = new UserColourScheme(
647 ((UserColourScheme) av.getGlobalColourScheme())
653 cs = ColourSchemeProperty.getColour(sg, ColourSchemeProperty
654 .getColourName(av.getGlobalColourScheme()));
656 // cs is null if shading is an annotationColourGradient
659 cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
660 av.isIgnoreGapsConsensus());
663 // TODO: cs used to be initialized with a sequence collection and
664 // recalcConservation called automatically
665 // instead we set it manually - recalc called after updateAnnotation
668 sg.setName("JTreeGroup:" + sg.hashCode());
670 if (av.getGlobalColourScheme() != null
671 && av.getGlobalColourScheme().conservationApplied())
673 Conservation c = new Conservation("Group",
674 ResidueProperties.propHash, 3, sg.getSequences(null),
675 sg.getStartRes(), sg.getEndRes());
678 c.verdict(false, av.getConsPercGaps());
679 cs.setConservation(c);
685 av.getAlignment().addGroup(sg);
688 ap.updateAnnotation();
692 public void setShowDistances(boolean state)
694 this.showDistances = state;
698 public void setShowBootstrap(boolean state)
700 this.showBootstrap = state;
704 public void setMarkPlaceholders(boolean state)
706 this.markPlaceholders = state;