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.NJTree;
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.ResidueProperties;
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.Vector;
54 public class TreeCanvas extends Panel implements MouseListener,
59 ScrollPane scrollPane;
63 public static final String PLACEHOLDER = " * ";
67 boolean fitToWindow = true;
69 boolean showDistances = false;
71 boolean showBootstrap = false;
73 boolean markPlaceholders = false;
85 Hashtable nameHash = new Hashtable();
87 Hashtable nodeHash = new Hashtable();
89 SequenceNode highlightNode;
93 public TreeCanvas(AlignmentPanel ap, ScrollPane scroller)
98 scrollPane = scroller;
99 addMouseListener(this);
100 addMouseMotionListener(this);
103 PaintRefresher.Register(this, av.getSequenceSetId());
106 public void treeSelectionChanged(SequenceI sequence)
108 SequenceGroup selected = av.getSelectionGroup();
109 if (selected == null)
111 selected = new SequenceGroup();
112 av.setSelectionGroup(selected);
115 selected.setEndRes(av.getAlignment().getWidth() - 1);
116 selected.addOrRemove(sequence, true);
119 public void setTree(NJTree tree)
122 tree.findHeight(tree.getTopNode());
124 // Now have to calculate longest name based on the leaves
125 Vector<SequenceNode> leaves = tree.findLeaves(tree.getTopNode());
126 boolean has_placeholders = false;
129 for (int i = 0; i < leaves.size(); i++)
131 SequenceNode lf = leaves.elementAt(i);
133 if (lf.isPlaceholder())
135 has_placeholders = true;
138 if (longestName.length() < ((Sequence) lf.element()).getName()
141 longestName = TreeCanvas.PLACEHOLDER
142 + ((Sequence) lf.element()).getName();
146 setMarkPlaceholders(has_placeholders);
149 public void drawNode(Graphics g, SequenceNode node, float chunk,
150 float scale, int width, int offx, int offy)
157 if (node.left() == null && node.right() == null)
161 float height = node.height;
162 float dist = node.dist;
164 int xstart = (int) ((height - dist) * scale) + offx;
165 int xend = (int) (height * scale) + offx;
167 int ypos = (int) (node.ycount * chunk) + offy;
169 if (node.element() instanceof SequenceI)
171 SequenceI seq = (SequenceI) node.element();
173 if (av.getSequenceColour(seq) == Color.white)
175 g.setColor(Color.black);
179 g.setColor(av.getSequenceColour(seq).darker());
185 g.setColor(Color.black);
188 // Draw horizontal line
189 g.drawLine(xstart, ypos, xend, ypos);
191 String nodeLabel = "";
192 if (showDistances && node.dist > 0)
194 nodeLabel = new Format("%-.2f").form(node.dist);
198 int btstrap = node.getBootstrap();
203 nodeLabel = nodeLabel + " : ";
205 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
208 if (!nodeLabel.equals(""))
210 g.drawString(nodeLabel, xstart + 2, ypos - 2);
213 String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
214 .getName()) : node.getName();
215 FontMetrics fm = g.getFontMetrics(font);
216 int charWidth = fm.stringWidth(name) + 3;
217 int charHeight = fm.getHeight();
219 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,
220 charWidth, charHeight);
222 nameHash.put(node.element(), rect);
224 // Colour selected leaves differently
225 SequenceGroup selected = av.getSelectionGroup();
227 && selected.getSequences(null).contains(node.element()))
229 g.setColor(Color.gray);
231 g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);
232 g.setColor(Color.white);
234 g.drawString(name, xend + 10, ypos);
235 g.setColor(Color.black);
239 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
241 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
244 float height = node.height;
245 float dist = node.dist;
247 int xstart = (int) ((height - dist) * scale) + offx;
248 int xend = (int) (height * scale) + offx;
249 int ypos = (int) (node.ycount * chunk) + offy;
251 g.setColor(node.color.darker());
253 // Draw horizontal line
254 g.drawLine(xstart, ypos, xend, ypos);
255 if (node == highlightNode)
257 g.fillRect(xend - 3, ypos - 3, 6, 6);
261 g.fillRect(xend - 2, ypos - 2, 4, 4);
264 int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
266 int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
269 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
270 nodeHash.put(node, pos);
272 g.drawLine((int) (height * scale) + offx, ystart,
273 (int) (height * scale) + offx, yend);
275 String nodeLabel = "";
277 if (showDistances && (node.dist > 0))
279 nodeLabel = new Format("%-.2f").form(node.dist);
284 int btstrap = node.getBootstrap();
289 nodeLabel = nodeLabel + " : ";
291 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
295 if (!nodeLabel.equals(""))
297 g.drawString(nodeLabel, xstart + 2, ypos - 2);
303 public Object findElement(int x, int y)
305 Enumeration keys = nameHash.keys();
307 while (keys.hasMoreElements())
309 Object ob = keys.nextElement();
310 Rectangle rect = (Rectangle) nameHash.get(ob);
312 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
313 && y <= (rect.y + rect.height))
318 keys = nodeHash.keys();
320 while (keys.hasMoreElements())
322 Object ob = keys.nextElement();
323 Rectangle rect = (Rectangle) nodeHash.get(ob);
325 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
326 && y <= (rect.y + rect.height))
335 public void pickNodes(Rectangle pickBox)
337 int width = getSize().width;
338 int height = getSize().height;
340 SequenceNode top = tree.getTopNode();
342 float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
345 top.count = ((SequenceNode) top.left()).count
346 + ((SequenceNode) top.right()).count;
348 float chunk = (float) (height - offy) / top.count;
350 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
353 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
354 float scale, int width, int offx, int offy)
361 if (node.left() == null && node.right() == null)
363 float height = node.height;
364 // float dist = node.dist;
366 // int xstart = (int) ( (height - dist) * scale) + offx;
367 int xend = (int) (height * scale) + offx;
369 int ypos = (int) (node.ycount * chunk) + offy;
371 if (pickBox.contains(new Point(xend, ypos)))
373 if (node.element() instanceof SequenceI)
375 SequenceI seq = (SequenceI) node.element();
376 SequenceGroup sg = av.getSelectionGroup();
379 sg.addOrRemove(seq, true);
386 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
388 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
393 public void setColor(SequenceNode node, Color c)
400 if (node.left() == null && node.right() == null)
404 if (node.element() instanceof SequenceI)
406 av.setSequenceColour((SequenceI) node.element(), c);
412 setColor((SequenceNode) node.left(), c);
413 setColor((SequenceNode) node.right(), c);
418 public void update(Graphics g)
424 public void paint(Graphics g)
431 if (nameHash.size() == 0)
436 int width = scrollPane.getSize().width;
437 int height = scrollPane.getSize().height;
440 height = g.getFontMetrics(font).getHeight() * nameHash.size();
443 if (getSize().width > width)
445 setSize(new Dimension(width, height));
446 scrollPane.validate();
450 setSize(new Dimension(width, height));
453 draw(g, width, height);
457 public void draw(Graphics g, int width, int height)
459 offy = font.getSize() + 10;
461 g.setColor(Color.white);
462 g.fillRect(0, 0, width, height);
464 labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; // 20
469 float wscale = (width - labelLength - offx * 2) / tree.getMaxHeight();
471 SequenceNode top = tree.getTopNode();
475 top.count = ((SequenceNode) top.left()).count
476 + ((SequenceNode) top.right()).count;
478 float chunk = (float) (height - offy) / top.count;
480 drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
484 if (av.getCurrentTree() == tree)
486 g.setColor(Color.red);
490 g.setColor(Color.gray);
493 int x = (int) (threshold * (getSize().width - labelLength - 2 * offx) + offx);
495 g.drawLine(x, 0, x, getSize().height);
501 public void mouseReleased(MouseEvent e)
506 public void mouseEntered(MouseEvent e)
511 public void mouseExited(MouseEvent e)
516 public void mouseClicked(MouseEvent evt)
518 if (highlightNode != null)
520 if (evt.getClickCount() > 1)
522 tree.swapNodes(highlightNode);
523 tree.reCount(tree.getTopNode());
524 tree.findHeight(tree.getTopNode());
528 Vector<SequenceNode> leaves = tree.findLeaves(highlightNode);
530 for (int i = 0; i < leaves.size(); i++)
532 SequenceI seq = (SequenceI) leaves.elementAt(i)
534 treeSelectionChanged(seq);
538 PaintRefresher.Refresh(this, av.getSequenceSetId());
545 public void mouseDragged(MouseEvent ect)
550 public void mouseMoved(MouseEvent evt)
552 av.setCurrentTree(tree);
554 Object ob = findElement(evt.getX(), evt.getY());
556 if (ob instanceof SequenceNode)
558 highlightNode = (SequenceNode) ob;
563 if (highlightNode != null)
565 highlightNode = null;
572 public void mousePressed(MouseEvent e)
574 av.setCurrentTree(tree);
579 Object ob = findElement(x, y);
581 if (ob instanceof SequenceI)
583 treeSelectionChanged((Sequence) ob);
584 PaintRefresher.Refresh(this, av.getSequenceSetId());
589 else if (!(ob instanceof SequenceNode))
593 if (tree.getMaxHeight() != 0)
595 threshold = (float) (x - offx)
596 / (float) (getSize().width - labelLength - 2 * offx);
598 tree.getGroups().removeAllElements();
599 tree.groupNodes(tree.getTopNode(), threshold);
600 setColor(tree.getTopNode(), Color.black);
602 av.setSelectionGroup(null);
603 av.getAlignment().deleteAllGroups();
604 av.clearSequenceColours();
605 final AlignViewportI codingComplement = av.getCodingComplement();
606 if (codingComplement != null)
608 codingComplement.setSelectionGroup(null);
609 codingComplement.getAlignment().deleteAllGroups();
610 codingComplement.clearSequenceColours();
618 PaintRefresher.Refresh(this, av.getSequenceSetId());
625 for (int i = 0; i < tree.getGroups().size(); i++)
628 Color col = new Color((int) (Math.random() * 255),
629 (int) (Math.random() * 255), (int) (Math.random() * 255));
630 setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
632 Vector<SequenceNode> l = tree.findLeaves((SequenceNode) tree
633 .getGroups().elementAt(i));
635 Vector<SequenceI> sequences = new Vector<SequenceI>();
636 for (int j = 0; j < l.size(); j++)
638 SequenceI s1 = (SequenceI) l.elementAt(j)
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.getColour(sg, ColourSchemeProperty
663 .getColourName(av.getGlobalColourScheme()));
665 // cs is null if shading is an annotationColourGradient
668 cs.setThreshold(av.getGlobalColourScheme().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
677 sg.setName("JTreeGroup:" + sg.hashCode());
679 if (av.getGlobalColourScheme() != null
680 && av.getGlobalColourScheme().conservationApplied())
682 Conservation c = new Conservation("Group",
683 ResidueProperties.propHash, 3, sg.getSequences(null),
684 sg.getStartRes(), sg.getEndRes());
687 c.verdict(false, av.getConsPercGaps());
688 cs.setConservation(c);
694 av.getAlignment().addGroup(sg);
696 // TODO this is duplicated with gui TreeCanvas - refactor
697 av.getAlignment().addGroup(sg);
698 final AlignViewportI codingComplement = av.getCodingComplement();
699 if (codingComplement != null)
701 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, av,
703 if (mappedGroup.getSequences().size() > 0)
705 codingComplement.getAlignment().addGroup(mappedGroup);
706 for (SequenceI seq : mappedGroup.getSequences())
708 // TODO why does gui require col.brighter() here??
709 codingComplement.setSequenceColour(seq, col);
715 ap.updateAnnotation();
716 if (av.getCodingComplement() != null)
718 ((AlignmentViewport) av.getCodingComplement()).firePropertyChange(
719 "alignment", null, ap.av.getAlignment().getSequences());
723 public void setShowDistances(boolean state)
725 this.showDistances = state;
729 public void setShowBootstrap(boolean state)
731 this.showBootstrap = state;
735 public void setMarkPlaceholders(boolean state)
737 this.markPlaceholders = state;