2 * Jalview - A Sequence Alignment Editor and Viewer
\r
3 * Copyright (C) 2006 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
59 public TreeCanvas(AlignViewport av, ScrollPane scroller)
\r
62 font = av.getFont();
\r
63 scrollPane = scroller;
\r
64 addMouseListener(this);
\r
65 addMouseMotionListener(this);
\r
68 PaintRefresher.Register(this, av.getSequenceSetId());
\r
71 public void treeSelectionChanged(SequenceI sequence)
\r
73 SequenceGroup selected = av.getSelectionGroup();
\r
74 if (selected == null)
\r
76 selected = new SequenceGroup();
\r
77 av.setSelectionGroup(selected);
\r
80 selected.setEndRes(av.alignment.getWidth()-1);
\r
81 selected.addOrRemove(sequence, true);
\r
84 public void setTree(NJTree tree)
\r
87 tree.findHeight(tree.getTopNode());
\r
89 // Now have to calculate longest name based on the leaves
\r
90 Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
\r
91 boolean has_placeholders = false;
\r
94 for (int i = 0; i < leaves.size(); i++)
\r
96 SequenceNode lf = (SequenceNode) leaves.elementAt(i);
\r
98 if (lf.isPlaceholder())
\r
100 has_placeholders = true;
\r
103 if (longestName.length() < ( (Sequence) lf.element()).getName()
\r
106 longestName = TreeCanvas.PLACEHOLDER +
\r
107 ( (Sequence) lf.element()).getName();
\r
111 setMarkPlaceholders(has_placeholders);
\r
114 public void drawNode(Graphics g, SequenceNode node, float chunk, float scale,
\r
115 int width, int offx, int offy)
\r
122 if (node.left() == null && node.right() == null)
\r
124 // Drawing leaf node
\r
126 float height = node.height;
\r
127 float dist = node.dist;
\r
129 int xstart = (int) ( (height - dist) * scale) + offx;
\r
130 int xend = (int) (height * scale) + offx;
\r
132 int ypos = (int) (node.ycount * chunk) + offy;
\r
134 if (node.element() instanceof SequenceI)
\r
136 SequenceI seq = (SequenceI) ( (SequenceNode) node).element();
\r
138 if (av.getSequenceColour(seq) == Color.white)
\r
140 g.setColor(Color.black);
\r
144 g.setColor(av.getSequenceColour(seq).darker());
\r
150 g.setColor(Color.black);
\r
153 // Draw horizontal line
\r
154 g.drawLine(xstart, ypos, xend, ypos);
\r
156 String nodeLabel = "";
\r
157 if (showDistances && node.dist > 0)
\r
159 nodeLabel = new Format("%-.2f").form(node.dist);
\r
165 nodeLabel = nodeLabel + " : ";
\r
167 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
\r
169 if (!nodeLabel.equals(""))
\r
171 g.drawString(nodeLabel, xstart+2, ypos - 2);
\r
174 String name = (markPlaceholders && node.isPlaceholder()) ?
\r
175 (PLACEHOLDER + node.getName()) : node.getName();
\r
176 FontMetrics fm = g.getFontMetrics(font);
\r
177 int charWidth = fm.stringWidth(name) + 3;
\r
178 int charHeight = fm.getHeight();
\r
180 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight,
\r
181 charWidth, charHeight);
\r
183 nameHash.put( (SequenceI) node.element(), rect);
\r
185 // Colour selected leaves differently
\r
186 SequenceGroup selected = av.getSelectionGroup();
\r
187 if (selected != null &&
\r
188 selected.getSequences(false).contains( (SequenceI) node.element()))
\r
190 g.setColor(Color.gray);
\r
192 g.fillRect(xend + 10, ypos - charHeight + 3, charWidth, charHeight);
\r
193 g.setColor(Color.white);
\r
195 g.drawString(name, xend + 10, ypos);
\r
196 g.setColor(Color.black);
\r
200 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx, offy);
\r
201 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx, offy);
\r
203 float height = node.height;
\r
204 float dist = node.dist;
\r
206 int xstart = (int) ( (height - dist) * scale) + offx;
\r
207 int xend = (int) (height * scale) + offx;
\r
208 int ypos = (int) (node.ycount * chunk) + offy;
\r
210 g.setColor( ( (SequenceNode) node).color.darker());
\r
212 // Draw horizontal line
\r
213 g.drawLine(xstart, ypos, xend, ypos);
\r
214 if (node == highlightNode)
\r
215 g.fillRect(xend - 3, ypos - 3, 6, 6);
\r
217 g.fillRect(xend - 2, ypos - 2, 4, 4);
\r
219 int ystart = (int) ( ( (SequenceNode) node.left()).ycount * chunk) + offy;
\r
220 int yend = (int) ( ( (SequenceNode) node.right()).ycount * chunk) + offy;
\r
222 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
\r
223 nodeHash.put(node, pos);
\r
225 g.drawLine( (int) (height * scale) + offx, ystart,
\r
226 (int) (height * scale) + offx, yend);
\r
228 if (showDistances && node.dist > 0)
\r
230 g.drawString(new Format("%-.2f").form(node.dist), xstart+2, ypos - 2);
\r
236 public Object findElement(int x, int y)
\r
238 Enumeration keys = nameHash.keys();
\r
240 while (keys.hasMoreElements())
\r
242 Object ob = keys.nextElement();
\r
243 Rectangle rect = (Rectangle) nameHash.get(ob);
\r
245 if (x >= rect.x && x <= (rect.x + rect.width) &&
\r
246 y >= rect.y && y <= (rect.y + rect.height))
\r
251 keys = nodeHash.keys();
\r
253 while (keys.hasMoreElements())
\r
255 Object ob = keys.nextElement();
\r
256 Rectangle rect = (Rectangle) nodeHash.get(ob);
\r
258 if (x >= rect.x && x <= (rect.x + rect.width) &&
\r
259 y >= rect.y && y <= (rect.y + rect.height))
\r
268 public void pickNodes(Rectangle pickBox)
\r
270 int width = getSize().width;
\r
271 int height = getSize().height;
\r
273 SequenceNode top = tree.getTopNode();
\r
275 float wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight()
\r
277 if (top.count == 0)
\r
279 top.count = ( (SequenceNode) top.left()).count +
\r
280 ( (SequenceNode) top.right()).count;
\r
282 float chunk = (float) (height - offy) / top.count;
\r
284 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
\r
287 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
\r
288 float scale, int width, int offx, int offy)
\r
295 if (node.left() == null && node.right() == null)
\r
297 float height = node.height;
\r
298 //float dist = node.dist;
\r
300 //int xstart = (int) ( (height - dist) * scale) + offx;
\r
301 int xend = (int) (height * scale) + offx;
\r
303 int ypos = (int) (node.ycount * chunk) + offy;
\r
305 if (pickBox.contains(new Point(xend, ypos)))
\r
307 if (node.element() instanceof SequenceI)
\r
309 SequenceI seq = (SequenceI) node.element();
\r
310 SequenceGroup sg = av.getSelectionGroup();
\r
313 sg.addOrRemove(seq, true);
\r
320 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width, offx,
\r
322 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width, offx,
\r
327 public void setColor(SequenceNode node, Color c)
\r
334 if (node.left() == null && node.right() == null)
\r
338 if (node.element() instanceof SequenceI)
\r
340 av.setSequenceColour((SequenceI) node.element(), c);
\r
346 setColor( (SequenceNode) node.left(), c);
\r
347 setColor( (SequenceNode) node.right(), c);
\r
351 public void update(Graphics g)
\r
357 public void paint(Graphics g1)
\r
363 if (nameHash.size() == 0)
\r
368 FontMetrics fm = g1.getFontMetrics(font);
\r
370 int width = scrollPane.getSize().width;
\r
371 int height = scrollPane.getSize().height;
\r
373 height = fm.getHeight() * nameHash.size();
\r
376 if(offscreen==null || offscreen.getWidth(this)!=width
\r
377 || offscreen.getHeight(this)!=height)
\r
378 offscreen = createImage(width, height);
\r
380 Graphics g = offscreen.getGraphics();
\r
386 scrollPane.getSize().height > fm.getHeight() * nameHash.size() + offy))
\r
388 draw(g, scrollPane.getSize().width, scrollPane.getSize().height);
\r
392 setSize(new Dimension(scrollPane.getSize().width,
\r
393 fm.getHeight() * nameHash.size()));
\r
394 draw(g, scrollPane.getSize().width, fm.getHeight() * nameHash.size());
\r
397 g1.drawImage(offscreen, 0, 0, this);
\r
398 scrollPane.validate();
\r
402 public void draw(Graphics g, int width, int height)
\r
404 offy = font.getSize()+10;
\r
406 g.setColor(Color.white);
\r
407 g.fillRect(0, 0, width, height);
\r
409 labelLength = g.getFontMetrics(font).stringWidth(longestName) + 20; //20 allows for scrollbar
\r
411 float wscale = (float) (width - labelLength - offx * 2) / tree.getMaxHeight();
\r
413 SequenceNode top = tree.getTopNode();
\r
415 if (top.count == 0)
\r
417 top.count = ( (SequenceNode) top.left()).count +
\r
418 ( (SequenceNode) top.right()).count;
\r
420 float chunk = (float) (height - offy) / top.count;
\r
422 drawNode(g, tree.getTopNode(), chunk, wscale, width, offx, offy);
\r
424 if (threshold != 0)
\r
426 if (av.getCurrentTree() == tree)
\r
428 g.setColor(Color.red);
\r
432 g.setColor(Color.gray);
\r
435 int x = (int) (threshold *
\r
436 (float) (getSize().width - labelLength - 2 * offx) + offx);
\r
438 g.drawLine(x, 0, x, getSize().height);
\r
443 public void mouseReleased(MouseEvent e)
\r
446 public void mouseEntered(MouseEvent e)
\r
449 public void mouseExited(MouseEvent e)
\r
452 public void mouseClicked(MouseEvent evt)
\r
454 if (highlightNode != null)
\r
456 if (evt.getClickCount() > 1)
\r
458 tree.swapNodes(highlightNode);
\r
459 tree.reCount(tree.getTopNode());
\r
460 tree.findHeight(tree.getTopNode());
\r
464 Vector leaves = new Vector();
\r
465 tree.findLeaves(highlightNode, leaves);
\r
467 for (int i = 0; i < leaves.size(); i++)
\r
470 (SequenceI) ( (SequenceNode) leaves.elementAt(i)).element();
\r
471 treeSelectionChanged(seq);
\r
475 PaintRefresher.Refresh(this, av.getSequenceSetId());
\r
480 public void mouseDragged(MouseEvent ect)
\r
484 public void mouseMoved(MouseEvent evt)
\r
486 av.setCurrentTree(tree);
\r
488 Object ob = findElement(evt.getX(), evt.getY());
\r
490 if (ob instanceof SequenceNode)
\r
492 highlightNode = (SequenceNode) ob;
\r
497 if (highlightNode != null)
\r
499 highlightNode = null;
\r
505 public void mousePressed(MouseEvent e)
\r
507 av.setCurrentTree(tree);
\r
512 Object ob = findElement(x, y);
\r
514 if (ob instanceof SequenceI)
\r
516 treeSelectionChanged( (Sequence) ob);
\r
517 PaintRefresher.Refresh(this, av.getSequenceSetId());
\r
521 else if ( !(ob instanceof SequenceNode))
\r
525 if (tree.getMaxHeight() != 0)
\r
527 threshold = (float) (x - offx) /
\r
528 (float) (getSize().width - labelLength - 2 * offx);
\r
530 tree.getGroups().removeAllElements();
\r
531 tree.groupNodes(tree.getTopNode(), threshold);
\r
532 setColor(tree.getTopNode(), Color.black);
\r
534 av.setSelectionGroup(null);
\r
535 av.alignment.deleteAllGroups();
\r
536 av.sequenceColours=null;
\r
543 PaintRefresher.Refresh(this, av.getSequenceSetId());
\r
548 void colourGroups()
\r
550 for (int i = 0; i < tree.getGroups().size(); i++)
\r
553 Color col = new Color( (int) (Math.random() * 255),
\r
554 (int) (Math.random() * 255),
\r
555 (int) (Math.random() * 255));
\r
556 setColor( (SequenceNode) tree.getGroups().elementAt(i), col.brighter());
\r
558 Vector l = tree.findLeaves( (SequenceNode) tree.getGroups().elementAt(
\r
561 Vector sequences = new Vector();
\r
562 for (int j = 0; j < l.size(); j++)
\r
564 SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();
\r
565 if(!sequences.contains(s1))
\r
566 sequences.addElement(s1);
\r
569 ColourSchemeI cs = null;
\r
571 if (av.getGlobalColourScheme() != null)
\r
573 if (av.getGlobalColourScheme() instanceof UserColourScheme)
\r
575 cs = new UserColourScheme(
\r
576 ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
\r
580 cs = ColourSchemeProperty.getColour(sequences,
\r
581 av.alignment.getWidth(),
\r
582 ColourSchemeProperty.getColourName(
\r
583 av.getGlobalColourScheme()));
\r
585 cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
\r
586 av.getIgnoreGapsConsensus());
\r
589 SequenceGroup sg = new SequenceGroup(sequences, "TreeGroup",
\r
591 false, 0, av.alignment.getWidth()-1);
\r
594 if ( av.getGlobalColourScheme()!=null
\r
595 && av.getGlobalColourScheme().conservationApplied())
\r
597 Conservation c = new Conservation("Group",
\r
598 ResidueProperties.propHash, 3,
\r
599 sg.getSequences(false),
\r
604 c.verdict(false, av.ConsPercGaps);
\r
605 cs.setConservation(c);
\r
611 av.alignment.addGroup(sg);
\r
617 public void setShowDistances(boolean state)
\r
619 this.showDistances = state;
\r
623 public void setShowBootstrap(boolean state)
\r
625 this.showBootstrap = state;
\r
629 public void setMarkPlaceholders(boolean state)
\r
631 this.markPlaceholders = state;
\r