2 * Jalview - A Sequence Alignment Editor and Viewer
\r
3 * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
\r
5 * This program is free software; you can redistribute it and/or
\r
6 * modify it under the terms of the GNU General Public License
\r
7 * as published by the Free Software Foundation; either version 2
\r
8 * of the License, or (at your option) any later version.
\r
10 * This program is distributed in the hope that it will be useful,
\r
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 * GNU General Public License for more details.
\r
15 * You should have received a copy of the GNU General Public License
\r
16 * along with this program; if not, write to the Free Software
\r
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
\r
20 package jalview.appletgui;
\r
25 import java.awt.event.*;
\r
27 import jalview.analysis.*;
\r
28 import jalview.datamodel.*;
\r
29 import jalview.schemes.*;
\r
30 import jalview.util.*;
\r
32 public class TreeCanvas
\r
33 extends Panel implements MouseListener, MouseMotionListener
\r
36 ScrollPane scrollPane;
\r
38 public static final String PLACEHOLDER = " * ";
\r
40 boolean fitToWindow = true;
\r
41 boolean showDistances = false;
\r
42 boolean showBootstrap = false;
\r
43 boolean markPlaceholders = false;
\r
51 int labelLength = -1;
\r
53 Hashtable nameHash = new Hashtable();
\r
54 Hashtable nodeHash = new Hashtable();
\r
56 SequenceNode highlightNode;
\r
58 public TreeCanvas(AlignViewport av, ScrollPane scroller)
\r
61 font = av.getFont();
\r
62 scrollPane = scroller;
\r
63 addMouseListener(this);
\r
64 addMouseMotionListener(this);
\r
67 PaintRefresher.Register(this, av.getSequenceSetId());
\r
70 public void treeSelectionChanged(SequenceI sequence)
\r
72 SequenceGroup selected = av.getSelectionGroup();
\r
73 if (selected == null)
\r
75 selected = new SequenceGroup();
\r
76 av.setSelectionGroup(selected);
\r
79 selected.setEndRes(av.alignment.getWidth() - 1);
\r
80 selected.addOrRemove(sequence, true);
\r
83 public void setTree(NJTree tree)
\r
86 tree.findHeight(tree.getTopNode());
\r
88 // Now have to calculate longest name based on the leaves
\r
89 Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
\r
90 boolean has_placeholders = false;
\r
93 for (int i = 0; i < leaves.size(); i++)
\r
95 SequenceNode lf = (SequenceNode) leaves.elementAt(i);
\r
97 if (lf.isPlaceholder())
\r
99 has_placeholders = true;
\r
102 if (longestName.length() < ( (Sequence) lf.element()).getName()
\r
105 longestName = TreeCanvas.PLACEHOLDER +
\r
106 ( (Sequence) lf.element()).getName();
\r
110 setMarkPlaceholders(has_placeholders);
\r
113 public void drawNode(Graphics g, SequenceNode node, float chunk, float scale,
\r
114 int width, int offx, int offy)
\r
121 if (node.left() == null && node.right() == null)
\r
123 // Drawing leaf node
\r
125 float height = node.height;
\r
126 float dist = node.dist;
\r
128 int xstart = (int) ( (height - dist) * scale) + offx;
\r
129 int xend = (int) (height * scale) + offx;
\r
131 int ypos = (int) (node.ycount * chunk) + offy;
\r
133 if (node.element() instanceof SequenceI)
\r
135 SequenceI seq = (SequenceI) ( (SequenceNode) node).element();
\r
137 if (av.getSequenceColour(seq) == Color.white)
\r
139 g.setColor(Color.black);
\r
143 g.setColor(av.getSequenceColour(seq).darker());
\r
149 g.setColor(Color.black);
\r
152 // Draw horizontal line
\r
153 g.drawLine(xstart, ypos, xend, ypos);
\r
155 String nodeLabel = "";
\r
156 if (showDistances && node.dist > 0)
\r
158 nodeLabel = new Format("%-.2f").form(node.dist);
\r
164 nodeLabel = nodeLabel + " : ";
\r
166 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
\r
168 if (!nodeLabel.equals(""))
\r
170 g.drawString(nodeLabel, xstart + 2, ypos - 2);
\r
173 String name = (markPlaceholders && node.isPlaceholder()) ?
\r
174 (PLACEHOLDER + node.getName()) : node.getName();
\r
175 FontMetrics fm = g.getFontMetrics(font);
\r
176 int charWidth = fm.stringWidth(name) + 3;
\r
177 int charHeight = fm.getHeight();
\r
179 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,
\r
180 charWidth, charHeight);
\r
182 nameHash.put( (SequenceI) node.element(), rect);
\r
184 // Colour selected leaves differently
\r
185 SequenceGroup selected = av.getSelectionGroup();
\r
186 if (selected != null &&
\r
187 selected.getSequences(null).contains( (SequenceI) node.element()))
\r
189 g.setColor(Color.gray);
\r
191 g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);
\r
192 g.setColor(Color.white);
\r
194 g.drawString(name, xend + 10, ypos);
\r
195 g.setColor(Color.black);
\r
199 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx, offy);
\r
200 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx, offy);
\r
202 float height = node.height;
\r
203 float dist = node.dist;
\r
205 int xstart = (int) ( (height - dist) * scale) + offx;
\r
206 int xend = (int) (height * scale) + offx;
\r
207 int ypos = (int) (node.ycount * chunk) + offy;
\r
209 g.setColor( ( (SequenceNode) node).color.darker());
\r
211 // Draw horizontal line
\r
212 g.drawLine(xstart, ypos, xend, ypos);
\r
213 if (node == highlightNode)
\r
215 g.fillRect(xend - 3, ypos - 3, 6, 6);
\r
219 g.fillRect(xend - 2, ypos - 2, 4, 4);
\r
222 int ystart = (int) ( ( (SequenceNode) node.left()).ycount * chunk) + offy;
\r
223 int yend = (int) ( ( (SequenceNode) node.right()).ycount * chunk) + offy;
\r
225 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
\r
226 nodeHash.put(node, pos);
\r
228 g.drawLine( (int) (height * scale) + offx, ystart,
\r
229 (int) (height * scale) + offx, yend);
\r
231 if (showDistances && node.dist > 0)
\r
233 g.drawString(new Format("%-.2f").form(node.dist), xstart + 2, ypos - 2);
\r
239 public Object findElement(int x, int y)
\r
241 Enumeration keys = nameHash.keys();
\r
243 while (keys.hasMoreElements())
\r
245 Object ob = keys.nextElement();
\r
246 Rectangle rect = (Rectangle) nameHash.get(ob);
\r
248 if (x >= rect.x && x <= (rect.x + rect.width) &&
\r
249 y >= rect.y && y <= (rect.y + rect.height))
\r
254 keys = nodeHash.keys();
\r
256 while (keys.hasMoreElements())
\r
258 Object ob = keys.nextElement();
\r
259 Rectangle rect = (Rectangle) nodeHash.get(ob);
\r
261 if (x >= rect.x && x <= (rect.x + rect.width) &&
\r
262 y >= rect.y && y <= (rect.y + rect.height))
\r
271 public void pickNodes(Rectangle pickBox)
\r
273 int width = getSize().width;
\r
274 int height = getSize().height;
\r
276 SequenceNode top = tree.getTopNode();
\r
278 float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight()
\r
280 if (top.count == 0)
\r
282 top.count = ( (SequenceNode) top.left()).count +
\r
283 ( (SequenceNode) top.right()).count;
\r
285 float chunk = (float) (height - offy) / top.count;
\r
287 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
\r
290 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
\r
291 float scale, int width, int offx, int offy)
\r
298 if (node.left() == null && node.right() == null)
\r
300 float height = node.height;
\r
301 //float dist = node.dist;
\r
303 //int xstart = (int) ( (height - dist) * scale) + offx;
\r
304 int xend = (int) (height * scale) + offx;
\r
306 int ypos = (int) (node.ycount * chunk) + offy;
\r
308 if (pickBox.contains(new Point(xend, ypos)))
\r
310 if (node.element() instanceof SequenceI)
\r
312 SequenceI seq = (SequenceI) node.element();
\r
313 SequenceGroup sg = av.getSelectionGroup();
\r
316 sg.addOrRemove(seq, true);
\r
323 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width, offx,
\r
325 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width, offx,
\r
330 public void setColor(SequenceNode node, Color c)
\r
337 if (node.left() == null && node.right() == null)
\r
341 if (node.element() instanceof SequenceI)
\r
343 av.setSequenceColour( (SequenceI) node.element(), c);
\r
349 setColor( (SequenceNode) node.left(), c);
\r
350 setColor( (SequenceNode) node.right(), c);
\r
354 public void update(Graphics g)
\r
359 public void paint(Graphics g)
\r
366 if (nameHash.size() == 0)
\r
371 int width = scrollPane.getSize().width;
\r
372 int height = scrollPane.getSize().height;
\r
375 height = g.getFontMetrics(font).getHeight() * nameHash.size();
\r
378 if (getSize().width > width)
\r
380 setSize(new Dimension(width, height));
\r
381 scrollPane.validate();
\r
385 setSize(new Dimension(width, height));
\r
389 draw(g, width, height);
\r
393 public void draw(Graphics g, int width, int height)
\r
395 offy = font.getSize() + 10;
\r
397 g.setColor(Color.white);
\r
398 g.fillRect(0, 0, width, height);
\r
400 labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; //20 allows for scrollbar
\r
402 float wscale = (float) (width - labelLength - offx * 2) / tree.getMaxHeight();
\r
404 SequenceNode top = tree.getTopNode();
\r
406 if (top.count == 0)
\r
408 top.count = ( (SequenceNode) top.left()).count +
\r
409 ( (SequenceNode) top.right()).count;
\r
411 float chunk = (float) (height - offy) / top.count;
\r
413 drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
\r
415 if (threshold != 0)
\r
417 if (av.getCurrentTree() == tree)
\r
419 g.setColor(Color.red);
\r
423 g.setColor(Color.gray);
\r
426 int x = (int) (threshold *
\r
427 (float) (getSize().width - labelLength - 2 * offx) + offx);
\r
429 g.drawLine(x, 0, x, getSize().height);
\r
434 public void mouseReleased(MouseEvent e)
\r
437 public void mouseEntered(MouseEvent e)
\r
440 public void mouseExited(MouseEvent e)
\r
443 public void mouseClicked(MouseEvent evt)
\r
445 if (highlightNode != null)
\r
447 if (evt.getClickCount() > 1)
\r
449 tree.swapNodes(highlightNode);
\r
450 tree.reCount(tree.getTopNode());
\r
451 tree.findHeight(tree.getTopNode());
\r
455 Vector leaves = new Vector();
\r
456 tree.findLeaves(highlightNode, leaves);
\r
458 for (int i = 0; i < leaves.size(); i++)
\r
461 (SequenceI) ( (SequenceNode) leaves.elementAt(i)).element();
\r
462 treeSelectionChanged(seq);
\r
466 PaintRefresher.Refresh(this, av.getSequenceSetId());
\r
471 public void mouseDragged(MouseEvent ect)
\r
474 public void mouseMoved(MouseEvent evt)
\r
476 av.setCurrentTree(tree);
\r
478 Object ob = findElement(evt.getX(), evt.getY());
\r
480 if (ob instanceof SequenceNode)
\r
482 highlightNode = (SequenceNode) ob;
\r
487 if (highlightNode != null)
\r
489 highlightNode = null;
\r
495 public void mousePressed(MouseEvent e)
\r
497 av.setCurrentTree(tree);
\r
502 Object ob = findElement(x, y);
\r
504 if (ob instanceof SequenceI)
\r
506 treeSelectionChanged( (Sequence) ob);
\r
507 PaintRefresher.Refresh(this, av.getSequenceSetId());
\r
511 else if (! (ob instanceof SequenceNode))
\r
515 if (tree.getMaxHeight() != 0)
\r
517 threshold = (float) (x - offx) /
\r
518 (float) (getSize().width - labelLength - 2 * offx);
\r
520 tree.getGroups().removeAllElements();
\r
521 tree.groupNodes(tree.getTopNode(), threshold);
\r
522 setColor(tree.getTopNode(), Color.black);
\r
524 av.setSelectionGroup(null);
\r
525 av.alignment.deleteAllGroups();
\r
526 av.sequenceColours = null;
\r
533 PaintRefresher.Refresh(this, av.getSequenceSetId());
\r
538 void colourGroups()
\r
540 for (int i = 0; i < tree.getGroups().size(); i++)
\r
543 Color col = new Color( (int) (Math.random() * 255),
\r
544 (int) (Math.random() * 255),
\r
545 (int) (Math.random() * 255));
\r
546 setColor( (SequenceNode) tree.getGroups().elementAt(i), col.brighter());
\r
548 Vector l = tree.findLeaves( (SequenceNode) tree.getGroups().elementAt(
\r
551 Vector sequences = new Vector();
\r
552 for (int j = 0; j < l.size(); j++)
\r
554 SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();
\r
555 if (!sequences.contains(s1))
\r
557 sequences.addElement(s1);
\r
561 ColourSchemeI cs = null;
\r
563 if (av.getGlobalColourScheme() != null)
\r
565 if (av.getGlobalColourScheme() instanceof UserColourScheme)
\r
567 cs = new UserColourScheme(
\r
568 ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
\r
573 cs = ColourSchemeProperty.getColour(sequences,
\r
574 av.alignment.getWidth(),
\r
575 ColourSchemeProperty.
\r
577 av.getGlobalColourScheme()));
\r
580 cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
\r
581 av.getIgnoreGapsConsensus());
\r
584 SequenceGroup sg = new SequenceGroup(sequences, "",
\r
587 av.alignment.getWidth() - 1);
\r
589 sg.setName("JTreeGroup:" + sg.hashCode());
\r
591 if (av.getGlobalColourScheme() != null
\r
592 && av.getGlobalColourScheme().conservationApplied())
\r
594 Conservation c = new Conservation("Group",
\r
595 ResidueProperties.propHash, 3,
\r
596 sg.getSequences(null),
\r
601 c.verdict(false, av.ConsPercGaps);
\r
602 cs.setConservation(c);
\r
608 av.alignment.addGroup(sg);
\r
614 public void setShowDistances(boolean state)
\r
616 this.showDistances = state;
\r
620 public void setShowBootstrap(boolean state)
\r
622 this.showBootstrap = state;
\r
626 public void setMarkPlaceholders(boolean state)
\r
628 this.markPlaceholders = state;
\r