2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
\r
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
\r
5 * This file is part of Jalview.
\r
7 * Jalview is free software: you can redistribute it and/or
\r
8 * modify it under the terms of the GNU General Public License
\r
9 * as published by the Free Software Foundation, either version 3
\r
10 * of the License, or (at your option) any later version.
\r
12 * Jalview is distributed in the hope that it will be useful, but
\r
13 * WITHOUT ANY WARRANTY; without even the implied warranty
\r
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
\r
15 * PURPOSE. See the GNU General Public License for more details.
\r
17 * You should have received a copy of the GNU General Public License
\r
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
\r
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
\r
21 package jalview.appletgui;
\r
23 import jalview.analysis.Conservation;
\r
24 import jalview.analysis.NJTree;
\r
25 import jalview.api.AlignViewportI;
\r
26 import jalview.datamodel.Sequence;
\r
27 import jalview.datamodel.SequenceGroup;
\r
28 import jalview.datamodel.SequenceI;
\r
29 import jalview.datamodel.SequenceNode;
\r
30 import jalview.schemes.ColourSchemeI;
\r
31 import jalview.schemes.ColourSchemeProperty;
\r
32 import jalview.schemes.ResidueProperties;
\r
33 import jalview.schemes.UserColourScheme;
\r
34 import jalview.util.Format;
\r
35 import jalview.util.MappingUtils;
\r
36 import jalview.viewmodel.AlignmentViewport;
\r
38 import java.awt.Color;
\r
39 import java.awt.Dimension;
\r
40 import java.awt.Font;
\r
41 import java.awt.FontMetrics;
\r
42 import java.awt.Graphics;
\r
43 import javax.swing.JPanel;
\r
44 import java.awt.Point;
\r
45 import java.awt.Rectangle;
\r
46 import javax.swing.JScrollPane;
\r
47 import java.awt.event.MouseEvent;
\r
48 import java.awt.event.MouseListener;
\r
49 import java.awt.event.MouseMotionListener;
\r
50 import java.util.Enumeration;
\r
51 import java.util.Hashtable;
\r
52 import java.util.Vector;
\r
54 public class TreeCanvas extends JPanel implements MouseListener,
\r
59 JScrollPane scrollPane;
\r
63 public static final String PLACEHOLDER = " * ";
\r
67 boolean fitToWindow = true;
\r
69 boolean showDistances = false;
\r
71 boolean showBootstrap = false;
\r
73 boolean markPlaceholders = false;
\r
83 int labelLength = -1;
\r
85 Hashtable nameHash = new Hashtable();
\r
87 Hashtable nodeHash = new Hashtable();
\r
89 SequenceNode highlightNode;
\r
93 public TreeCanvas(AlignmentPanel ap, JScrollPane scroller)
\r
97 font = av.getFont();
\r
98 scrollPane = scroller;
\r
99 addMouseListener(this);
\r
100 addMouseMotionListener(this);
\r
103 PaintRefresher.Register(this, av.getSequenceSetId());
\r
106 public void treeSelectionChanged(SequenceI sequence)
\r
108 SequenceGroup selected = av.getSelectionGroup();
\r
109 if (selected == null)
\r
111 selected = new SequenceGroup();
\r
112 av.setSelectionGroup(selected);
\r
115 selected.setEndRes(av.getAlignment().getWidth() - 1);
\r
116 selected.addOrRemove(sequence, true);
\r
119 public void setTree(NJTree tree)
\r
122 tree.findHeight(tree.getTopNode());
\r
124 // Now have to calculate longest name based on the leaves
\r
125 Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
\r
126 boolean has_placeholders = false;
\r
129 for (int i = 0; i < leaves.size(); i++)
\r
131 SequenceNode lf = (SequenceNode) leaves.elementAt(i);
\r
133 if (lf.isPlaceholder())
\r
135 has_placeholders = true;
\r
138 if (longestName.length() < ((Sequence) lf.element()).getName()
\r
141 longestName = TreeCanvas.PLACEHOLDER
\r
142 + ((Sequence) lf.element()).getName();
\r
146 setMarkPlaceholders(has_placeholders);
\r
149 public void drawNode(Graphics g, SequenceNode node, float chunk,
\r
150 float scale, int width, int offx, int offy)
\r
157 if (node.left() == null && node.right() == null)
\r
159 // Drawing leaf node
\r
161 float height = node.height;
\r
162 float dist = node.dist;
\r
164 int xstart = (int) ((height - dist) * scale) + offx;
\r
165 int xend = (int) (height * scale) + offx;
\r
167 int ypos = (int) (node.ycount * chunk) + offy;
\r
169 if (node.element() instanceof SequenceI)
\r
171 SequenceI seq = (SequenceI) node.element();
\r
173 if (av.getSequenceColour(seq) == Color.white)
\r
175 g.setColor(Color.black);
\r
179 g.setColor(av.getSequenceColour(seq).darker());
\r
185 g.setColor(Color.black);
\r
188 // Draw horizontal line
\r
189 g.drawLine(xstart, ypos, xend, ypos);
\r
191 String nodeLabel = "";
\r
192 if (showDistances && node.dist > 0)
\r
194 nodeLabel = new Format("%-.2f").formDouble(node.dist);
\r
198 int btstrap = node.getBootstrap();
\r
203 nodeLabel = nodeLabel + " : ";
\r
205 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
\r
208 if (!nodeLabel.equals(""))
\r
210 g.drawString(nodeLabel, xstart + 2, ypos - 2);
\r
213 String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
\r
214 .getName()) : node.getName();
\r
215 FontMetrics fm = g.getFontMetrics(font);
\r
216 int charWidth = fm.stringWidth(name) + 3;
\r
217 int charHeight = fm.getHeight();
\r
219 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,
\r
220 charWidth, charHeight);
\r
222 nameHash.put(node.element(), rect);
\r
224 // Colour selected leaves differently
\r
225 SequenceGroup selected = av.getSelectionGroup();
\r
226 if (selected != null
\r
227 && selected.getSequences(null).contains(node.element()))
\r
229 g.setColor(Color.gray);
\r
231 g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);
\r
232 g.setColor(Color.white);
\r
234 g.drawString(name, xend + 10, ypos);
\r
235 g.setColor(Color.black);
\r
239 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
\r
241 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
\r
244 float height = node.height;
\r
245 float dist = node.dist;
\r
247 int xstart = (int) ((height - dist) * scale) + offx;
\r
248 int xend = (int) (height * scale) + offx;
\r
249 int ypos = (int) (node.ycount * chunk) + offy;
\r
251 g.setColor(node.color.darker());
\r
253 // Draw horizontal line
\r
254 g.drawLine(xstart, ypos, xend, ypos);
\r
255 if (node == highlightNode)
\r
257 g.fillRect(xend - 3, ypos - 3, 6, 6);
\r
261 g.fillRect(xend - 2, ypos - 2, 4, 4);
\r
264 int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
\r
266 int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
\r
269 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
\r
270 nodeHash.put(node, pos);
\r
272 g.drawLine((int) (height * scale) + offx, ystart,
\r
273 (int) (height * scale) + offx, yend);
\r
275 String nodeLabel = "";
\r
277 if (showDistances && (node.dist > 0))
\r
279 nodeLabel = new Format("%-.2f").formDouble(node.dist);
\r
284 int btstrap = node.getBootstrap();
\r
289 nodeLabel = nodeLabel + " : ";
\r
291 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
\r
295 if (!nodeLabel.equals(""))
\r
297 g.drawString(nodeLabel, xstart + 2, ypos - 2);
\r
303 public Object findElement(int x, int y)
\r
305 Enumeration keys = nameHash.keys();
\r
307 while (keys.hasMoreElements())
\r
309 Object ob = keys.nextElement();
\r
310 Rectangle rect = (Rectangle) nameHash.get(ob);
\r
312 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
\r
313 && y <= (rect.y + rect.height))
\r
318 keys = nodeHash.keys();
\r
320 while (keys.hasMoreElements())
\r
322 Object ob = keys.nextElement();
\r
323 Rectangle rect = (Rectangle) nodeHash.get(ob);
\r
325 if (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y
\r
326 && y <= (rect.y + rect.height))
\r
335 public void pickNodes(Rectangle pickBox)
\r
337 int width = getSize().width;
\r
338 int height = getSize().height;
\r
340 SequenceNode top = tree.getTopNode();
\r
342 float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
\r
343 if (top.count == 0)
\r
345 top.count = ((SequenceNode) top.left()).count
\r
346 + ((SequenceNode) top.right()).count;
\r
348 float chunk = (float) (height - offy) / top.count;
\r
350 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
\r
353 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
\r
354 float scale, int width, int offx, int offy)
\r
361 if (node.left() == null && node.right() == null)
\r
363 float height = node.height;
\r
364 // float dist = node.dist;
\r
366 // int xstart = (int) ( (height - dist) * scale) + offx;
\r
367 int xend = (int) (height * scale) + offx;
\r
369 int ypos = (int) (node.ycount * chunk) + offy;
\r
371 if (pickBox.contains(new Point(xend, ypos)))
\r
373 if (node.element() instanceof SequenceI)
\r
375 SequenceI seq = (SequenceI) node.element();
\r
376 SequenceGroup sg = av.getSelectionGroup();
\r
379 sg.addOrRemove(seq, true);
\r
386 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
\r
388 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
\r
393 public void setColor(SequenceNode node, Color c)
\r
400 if (node.left() == null && node.right() == null)
\r
404 if (node.element() instanceof SequenceI)
\r
406 av.setSequenceColour((SequenceI) node.element(), c);
\r
412 setColor((SequenceNode) node.left(), c);
\r
413 setColor((SequenceNode) node.right(), c);
\r
418 public void update(Graphics g)
\r
424 public void paint(Graphics g)
\r
431 if (nameHash.size() == 0)
\r
436 int width = scrollPane.getSize().width;
\r
437 int height = scrollPane.getSize().height;
\r
440 height = g.getFontMetrics(font).getHeight() * nameHash.size();
\r
443 if (getSize().width > width)
\r
445 setSize(new Dimension(width, height));
\r
446 scrollPane.validate();
\r
450 setSize(new Dimension(width, height));
\r
453 draw(g, width, height);
\r
457 public void draw(Graphics g, int width, int height)
\r
459 offy = font.getSize() + 10;
\r
461 g.setColor(Color.white);
\r
462 g.fillRect(0, 0, width, height);
\r
464 labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; // 20
\r
469 float wscale = (width - labelLength - offx * 2) / tree.getMaxHeight();
\r
471 SequenceNode top = tree.getTopNode();
\r
473 if (top.count == 0)
\r
475 top.count = ((SequenceNode) top.left()).count
\r
476 + ((SequenceNode) top.right()).count;
\r
478 float chunk = (float) (height - offy) / top.count;
\r
480 drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
\r
482 if (threshold != 0)
\r
484 if (av.getCurrentTree() == tree)
\r
486 g.setColor(Color.red);
\r
490 g.setColor(Color.gray);
\r
493 int x = (int) (threshold * (getSize().width - labelLength - 2 * offx) + offx);
\r
495 g.drawLine(x, 0, x, getSize().height);
\r
501 public void mouseReleased(MouseEvent e)
\r
506 public void mouseEntered(MouseEvent e)
\r
511 public void mouseExited(MouseEvent e)
\r
516 public void mouseClicked(MouseEvent evt)
\r
518 if (highlightNode != null)
\r
520 if (evt.getClickCount() > 1)
\r
522 tree.swapNodes(highlightNode);
\r
523 tree.reCount(tree.getTopNode());
\r
524 tree.findHeight(tree.getTopNode());
\r
528 Vector leaves = new Vector();
\r
529 tree.findLeaves(highlightNode, leaves);
\r
531 for (int i = 0; i < leaves.size(); i++)
\r
533 SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
\r
535 treeSelectionChanged(seq);
\r
539 PaintRefresher.Refresh(this, av.getSequenceSetId());
\r
541 av.sendSelection();
\r
546 public void mouseDragged(MouseEvent ect)
\r
551 public void mouseMoved(MouseEvent evt)
\r
553 av.setCurrentTree(tree);
\r
555 Object ob = findElement(evt.getX(), evt.getY());
\r
557 if (ob instanceof SequenceNode)
\r
559 highlightNode = (SequenceNode) ob;
\r
564 if (highlightNode != null)
\r
566 highlightNode = null;
\r
573 public void mousePressed(MouseEvent e)
\r
575 av.setCurrentTree(tree);
\r
580 Object ob = findElement(x, y);
\r
582 if (ob instanceof SequenceI)
\r
584 treeSelectionChanged((Sequence) ob);
\r
585 PaintRefresher.Refresh(this, av.getSequenceSetId());
\r
587 av.sendSelection();
\r
590 else if (!(ob instanceof SequenceNode))
\r
594 if (tree.getMaxHeight() != 0)
\r
596 threshold = (float) (x - offx)
\r
597 / (float) (getSize().width - labelLength - 2 * offx);
\r
599 tree.getGroups().removeAllElements();
\r
600 tree.groupNodes(tree.getTopNode(), threshold);
\r
601 setColor(tree.getTopNode(), Color.black);
\r
603 av.setSelectionGroup(null);
\r
604 av.getAlignment().deleteAllGroups();
\r
605 av.clearSequenceColours();
\r
606 final AlignViewportI codingComplement = av.getCodingComplement();
\r
607 if (codingComplement != null)
\r
609 codingComplement.setSelectionGroup(null);
\r
610 codingComplement.getAlignment().deleteAllGroups();
\r
611 codingComplement.clearSequenceColours();
\r
619 PaintRefresher.Refresh(this, av.getSequenceSetId());
\r
624 void colourGroups()
\r
626 for (int i = 0; i < tree.getGroups().size(); i++)
\r
629 Color col = new Color((int) (Math.random() * 255),
\r
630 (int) (Math.random() * 255), (int) (Math.random() * 255));
\r
631 setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
\r
633 Vector l = tree.findLeaves(
\r
634 (SequenceNode) tree.getGroups().elementAt(i), new Vector());
\r
636 Vector sequences = new Vector();
\r
637 for (int j = 0; j < l.size(); j++)
\r
639 SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
\r
641 if (!sequences.contains(s1))
\r
643 sequences.addElement(s1);
\r
647 ColourSchemeI cs = null;
\r
649 SequenceGroup sg = new SequenceGroup(sequences, "", cs, true, true,
\r
650 false, 0, av.getAlignment().getWidth() - 1);
\r
652 if (av.getGlobalColourScheme() != null)
\r
654 if (av.getGlobalColourScheme() instanceof UserColourScheme)
\r
656 cs = new UserColourScheme(
\r
657 ((UserColourScheme) av.getGlobalColourScheme())
\r
663 cs = ColourSchemeProperty.getColour(sg, ColourSchemeProperty
\r
664 .getColourName(av.getGlobalColourScheme()));
\r
666 // cs is null if shading is an annotationColourGradient
\r
669 cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
\r
670 av.isIgnoreGapsConsensus());
\r
673 // TODO: cs used to be initialized with a sequence collection and
\r
674 // recalcConservation called automatically
\r
675 // instead we set it manually - recalc called after updateAnnotation
\r
678 sg.setName("JTreeGroup:" + sg.hashCode());
\r
679 sg.setIdColour(col);
\r
680 if (av.getGlobalColourScheme() != null
\r
681 && av.getGlobalColourScheme().conservationApplied())
\r
683 Conservation c = new Conservation("Group",
\r
684 ResidueProperties.propHash, 3, sg.getSequences(null),
\r
685 sg.getStartRes(), sg.getEndRes());
\r
688 c.verdict(false, av.getConsPercGaps());
\r
689 cs.setConservation(c);
\r
695 av.getAlignment().addGroup(sg);
\r
697 // TODO this is duplicated with gui TreeCanvas - refactor
\r
698 av.getAlignment().addGroup(sg);
\r
699 final AlignViewportI codingComplement = av.getCodingComplement();
\r
700 if (codingComplement != null)
\r
702 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg, av,
\r
704 if (mappedGroup.getSequences().size() > 0)
\r
706 codingComplement.getAlignment().addGroup(mappedGroup);
\r
707 for (SequenceI seq : mappedGroup.getSequences())
\r
709 // TODO why does gui require col.brighter() here??
\r
710 codingComplement.setSequenceColour(seq, col);
\r
716 ap.updateAnnotation();
\r
717 if (av.getCodingComplement() != null)
\r
719 ((AlignmentViewport) av.getCodingComplement()).firePropertyChange(
\r
720 "alignment", null, ap.av.getAlignment().getSequences());
\r
724 public void setShowDistances(boolean state)
\r
726 this.showDistances = state;
\r
730 public void setShowBootstrap(boolean state)
\r
732 this.showBootstrap = state;
\r
736 public void setMarkPlaceholders(boolean state)
\r
738 this.markPlaceholders = state;
\r