2 * Jalview - A Sequence Alignment Editor and Viewer
3 * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 import jalview.analysis.*;
23 import jalview.datamodel.*;
25 import jalview.schemes.*;
27 import jalview.util.*;
30 import java.awt.event.*;
31 import java.awt.print.*;
44 public class TreeCanvas extends JPanel implements MouseListener, Runnable,
45 Printable, MouseMotionListener
48 public static final String PLACEHOLDER = " * ";
50 JScrollPane scrollPane;
55 boolean fitToWindow = true;
56 boolean showDistances = false;
57 boolean showBootstrap = false;
58 boolean markPlaceholders = false;
65 Hashtable nameHash = new Hashtable();
66 Hashtable nodeHash = new Hashtable();
67 SequenceNode highlightNode;
69 boolean applyToAllViews = false;
72 * Creates a new TreeCanvas object.
74 * @param av DOCUMENT ME!
75 * @param tree DOCUMENT ME!
76 * @param scroller DOCUMENT ME!
77 * @param label DOCUMENT ME!
79 public TreeCanvas(AlignmentPanel ap, JScrollPane scroller)
84 scrollPane = scroller;
85 addMouseListener(this);
86 addMouseMotionListener(this);
87 PaintRefresher.Register(this, ap.av.getSequenceSetId());
88 ToolTipManager.sharedInstance().registerComponent(this);
94 * @param sequence DOCUMENT ME!
96 public void treeSelectionChanged(SequenceI sequence)
98 AlignmentPanel[] aps = getAssociatedPanels();
100 for (int a = 0; a < aps.length; a++)
102 SequenceGroup selected = aps[a].av.getSelectionGroup();
104 if (selected == null)
106 selected = new SequenceGroup();
107 aps[a].av.setSelectionGroup(selected);
110 selected.setEndRes(aps[a].av.alignment.getWidth() - 1);
111 selected.addOrRemove(sequence, true);
118 * @param tree DOCUMENT ME!
120 public void setTree(NJTree tree)
123 tree.findHeight(tree.getTopNode());
125 // Now have to calculate longest name based on the leaves
126 Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
127 boolean has_placeholders = false;
130 for (int i = 0; i < leaves.size(); i++)
132 SequenceNode lf = (SequenceNode) leaves.elementAt(i);
134 if (lf.isPlaceholder())
136 has_placeholders = true;
139 if (longestName.length() < ( (Sequence) lf.element()).getName()
142 longestName = TreeCanvas.PLACEHOLDER +
143 ( (Sequence) lf.element()).getName();
147 setMarkPlaceholders(has_placeholders);
153 * @param g DOCUMENT ME!
154 * @param node DOCUMENT ME!
155 * @param chunk DOCUMENT ME!
156 * @param scale DOCUMENT ME!
157 * @param width DOCUMENT ME!
158 * @param offx DOCUMENT ME!
159 * @param offy DOCUMENT ME!
161 public void drawNode(Graphics g, SequenceNode node, float chunk,
162 float scale, int width, int offx, int offy)
169 if ( (node.left() == null) && (node.right() == null))
172 float height = node.height;
173 float dist = node.dist;
175 int xstart = (int) ( (height - dist) * scale) + offx;
176 int xend = (int) (height * scale) + offx;
178 int ypos = (int) (node.ycount * chunk) + offy;
180 if (node.element() instanceof SequenceI)
182 SequenceI seq = (SequenceI) ( (SequenceNode) node).element();
184 if (av.getSequenceColour(seq) == Color.white)
186 g.setColor(Color.black);
190 g.setColor(av.getSequenceColour(seq).darker());
195 g.setColor(Color.black);
198 // Draw horizontal line
199 g.drawLine(xstart, ypos, xend, ypos);
201 String nodeLabel = "";
203 if (showDistances && (node.dist > 0))
205 nodeLabel = new Format("%-.2f").form(node.dist);
212 nodeLabel = nodeLabel + " : ";
215 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
218 if (!nodeLabel.equals(""))
220 g.drawString(nodeLabel, xstart + 2, ypos - 2);
223 String name = (markPlaceholders && node.isPlaceholder())
224 ? (PLACEHOLDER + node.getName()) : node.getName();
226 int charWidth = fm.stringWidth(name) + 3;
227 int charHeight = font.getSize();
229 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
230 charWidth, charHeight);
232 nameHash.put( (SequenceI) node.element(), rect);
234 // Colour selected leaves differently
235 SequenceGroup selected = av.getSelectionGroup();
237 if ( (selected != null) &&
238 selected.getSequences(false).contains( (SequenceI) node.element()))
240 g.setColor(Color.gray);
242 g.fillRect(xend + 10, ypos - charHeight / 2, charWidth,
244 g.setColor(Color.white);
247 g.drawString(name, xend + 10, ypos + fm.getDescent());
248 g.setColor(Color.black);
252 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
254 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
257 float height = node.height;
258 float dist = node.dist;
260 int xstart = (int) ( (height - dist) * scale) + offx;
261 int xend = (int) (height * scale) + offx;
262 int ypos = (int) (node.ycount * chunk) + offy;
264 g.setColor( ( (SequenceNode) node).color.darker());
266 // Draw horizontal line
267 g.drawLine(xstart, ypos, xend, ypos);
268 if (node == highlightNode)
269 g.fillRect(xend - 3, ypos - 3, 6, 6);
271 g.fillRect(xend - 2, ypos - 2, 4, 4);
273 int ystart = (int) ( ( (SequenceNode) node.left()).ycount * chunk) +
275 int yend = (int) ( ( (SequenceNode) node.right()).ycount * chunk) +
278 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
279 nodeHash.put(node, pos);
281 g.drawLine( (int) (height * scale) + offx, ystart,
282 (int) (height * scale) + offx, yend);
284 if (showDistances && (node.dist > 0))
286 g.drawString(new Format("%-.2f").form(node.dist).trim(), xstart + 2,
295 * @param x DOCUMENT ME!
296 * @param y DOCUMENT ME!
298 * @return DOCUMENT ME!
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)))
316 keys = nodeHash.keys();
318 while (keys.hasMoreElements())
320 Object ob = keys.nextElement();
321 Rectangle rect = (Rectangle) nodeHash.get(ob);
323 if ( (x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
324 (y <= (rect.y + rect.height)))
336 * @param pickBox DOCUMENT ME!
338 public void pickNodes(Rectangle pickBox)
340 int width = getWidth();
341 int height = getHeight();
343 SequenceNode top = tree.getTopNode();
345 float wscale = (float) ( (width * .8) - (offx * 2)) / tree.getMaxHeight();
349 top.count = ( (SequenceNode) top.left()).count +
350 ( (SequenceNode) top.right()).count;
353 float chunk = (float) (height - (offy)) / top.count;
355 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
361 * @param pickBox DOCUMENT ME!
362 * @param node DOCUMENT ME!
363 * @param chunk DOCUMENT ME!
364 * @param scale DOCUMENT ME!
365 * @param width DOCUMENT ME!
366 * @param offx DOCUMENT ME!
367 * @param offy DOCUMENT ME!
369 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
370 float scale, int width, int offx, int offy)
377 if ( (node.left() == null) && (node.right() == null))
379 float height = node.height;
380 float dist = node.dist;
382 int xstart = (int) ( (height - dist) * scale) + offx;
383 int xend = (int) (height * scale) + offx;
385 int ypos = (int) (node.ycount * chunk) + offy;
387 if (pickBox.contains(new Point(xend, ypos)))
389 if (node.element() instanceof SequenceI)
391 SequenceI seq = (SequenceI) node.element();
392 SequenceGroup sg = av.getSelectionGroup();
396 sg.addOrRemove(seq, true);
403 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
405 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
413 * @param node DOCUMENT ME!
414 * @param c DOCUMENT ME!
416 public void setColor(SequenceNode node, Color c)
423 if ( (node.left() == null) && (node.right() == null))
427 if (node.element() instanceof SequenceI)
429 AlignmentPanel[] aps = getAssociatedPanels();
430 for (int a = 0; a < aps.length; a++)
432 aps[a].av.setSequenceColour( (SequenceI) node.element(), c);
439 setColor( (SequenceNode) node.left(), c);
440 setColor( (SequenceNode) node.right(), c);
449 Thread thread = new Thread(this);
453 // put printing in a thread to avoid painting problems
456 PrinterJob printJob = PrinterJob.getPrinterJob();
457 PageFormat pf = printJob.pageDialog(printJob.defaultPage());
459 printJob.setPrintable(this, pf);
461 if (printJob.printDialog())
467 catch (Exception PrintException)
469 PrintException.printStackTrace();
477 * @param pg DOCUMENT ME!
478 * @param pf DOCUMENT ME!
479 * @param pi DOCUMENT ME!
481 * @return DOCUMENT ME!
483 * @throws PrinterException DOCUMENT ME!
485 public int print(Graphics pg, PageFormat pf, int pi)
486 throws PrinterException
489 pg.translate( (int) pf.getImageableX(), (int) pf.getImageableY());
491 int pwidth = (int) pf.getImageableWidth();
492 int pheight = (int) pf.getImageableHeight();
494 int noPages = getHeight() / pheight;
498 return Printable.NO_SUCH_PAGE;
501 if (pwidth > getWidth())
508 if (pheight > getHeight())
510 pheight = getHeight();
517 FontMetrics fm = pg.getFontMetrics(font);
518 int height = fm.getHeight() * nameHash.size();
519 pg.translate(0, -pi * pheight);
520 pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
522 // translate number of pages,
523 // height is screen size as this is the
524 // non overlapping text size
528 draw(pg, pwidth, pheight);
530 return Printable.PAGE_EXISTS;
536 * @param g DOCUMENT ME!
538 public void paintComponent(Graphics g)
540 super.paintComponent(g);
545 g.drawString("Calculating tree....", 20, getHeight() / 2);
549 fm = g.getFontMetrics(font);
551 if (nameHash.size() == 0)
558 (scrollPane.getHeight() > ( (fm.getHeight() * nameHash.size()) +
561 draw(g, scrollPane.getWidth(), scrollPane.getHeight());
562 setPreferredSize(null);
566 setPreferredSize(new Dimension(scrollPane.getWidth(),
567 fm.getHeight() * nameHash.size()));
568 draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
571 scrollPane.revalidate();
578 * @param fontSize DOCUMENT ME!
580 public void setFont(Font font)
589 * @param g1 DOCUMENT ME!
590 * @param width DOCUMENT ME!
591 * @param height DOCUMENT ME!
593 public void draw(Graphics g1, int width, int height)
595 Graphics2D g2 = (Graphics2D) g1;
596 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
597 RenderingHints.VALUE_ANTIALIAS_ON);
598 g2.setColor(Color.white);
599 g2.fillRect(0, 0, width, height);
603 offy = font.getSize() + 10;
605 fm = g2.getFontMetrics(font);
607 labelLength = fm.stringWidth(longestName) + 20; //20 allows for scrollbar
609 float wscale = (float) (width - labelLength - (offx * 2)) /
612 SequenceNode top = tree.getTopNode();
616 top.count = ( (SequenceNode) top.left()).count +
617 ( (SequenceNode) top.right()).count;
620 float chunk = (float) (height - (offy)) / top.count;
622 drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
626 if (av.getCurrentTree() == tree)
628 g2.setColor(Color.red);
632 g2.setColor(Color.gray);
635 int x = (int) ( (threshold * (float) (getWidth() - labelLength -
636 (2 * offx))) + offx);
638 g2.drawLine(x, 0, x, getHeight());
645 * @param e DOCUMENT ME!
647 public void mouseReleased(MouseEvent e)
654 * @param e DOCUMENT ME!
656 public void mouseEntered(MouseEvent e)
663 * @param e DOCUMENT ME!
665 public void mouseExited(MouseEvent e)
672 * @param e DOCUMENT ME!
674 public void mouseClicked(MouseEvent evt)
676 if (highlightNode != null)
678 if (SwingUtilities.isRightMouseButton(evt))
680 Color col = JColorChooser.showDialog(this, "Select Sub-Tree Colour",
681 highlightNode.color);
683 setColor(highlightNode, col);
686 if (evt.getClickCount() > 1)
688 tree.swapNodes(highlightNode);
689 tree.reCount(tree.getTopNode());
690 tree.findHeight(tree.getTopNode());
694 Vector leaves = new Vector();
695 tree.findLeaves(highlightNode, leaves);
697 for (int i = 0; i < leaves.size(); i++)
700 (SequenceI) ( (SequenceNode) leaves.elementAt(i)).element();
701 treeSelectionChanged(seq);
705 PaintRefresher.Refresh(this, av.getSequenceSetId());
710 public void mouseMoved(MouseEvent evt)
712 av.setCurrentTree(tree);
714 Object ob = findElement(evt.getX(), evt.getY());
716 if (ob instanceof SequenceNode)
718 highlightNode = (SequenceNode) ob;
720 "<html>Left click to select leaves"
721 + "<br>Double-click to invert leaves"
722 + "<br>Right click to change colour");
728 if (highlightNode != null)
730 highlightNode = null;
731 setToolTipText(null);
737 public void mouseDragged(MouseEvent ect)
743 * @param e DOCUMENT ME!
745 public void mousePressed(MouseEvent e)
747 av.setCurrentTree(tree);
752 Object ob = findElement(x, y);
754 if (ob instanceof SequenceI)
756 treeSelectionChanged( (Sequence) ob);
757 PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
761 else if (! (ob instanceof SequenceNode))
764 if (tree.getMaxHeight() != 0)
766 threshold = (float) (x - offx) / (float) (getWidth() -
767 labelLength - (2 * offx));
769 tree.getGroups().removeAllElements();
770 tree.groupNodes(tree.getTopNode(), threshold);
771 setColor(tree.getTopNode(), Color.black);
773 AlignmentPanel[] aps = getAssociatedPanels();
775 for (int a = 0; a < aps.length; a++)
777 aps[a].av.setSelectionGroup(null);
778 aps[a].av.alignment.deleteAllGroups();
779 aps[a].av.sequenceColours = null;
784 PaintRefresher.Refresh(this, ap.av.getSequenceSetId());
792 for (int i = 0; i < tree.getGroups().size(); i++)
794 Color col = new Color( (int) (Math.random() * 255),
795 (int) (Math.random() * 255),
796 (int) (Math.random() * 255));
797 setColor( (SequenceNode) tree.getGroups().elementAt(i),
800 Vector l = tree.findLeaves( (SequenceNode) tree.getGroups()
804 Vector sequences = new Vector();
806 for (int j = 0; j < l.size(); j++)
808 SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();
810 if (!sequences.contains(s1))
812 sequences.addElement(s1);
816 ColourSchemeI cs = null;
818 if (av.getGlobalColourScheme() != null)
820 if (av.getGlobalColourScheme() instanceof UserColourScheme)
822 cs = new UserColourScheme(
823 ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
827 cs = ColourSchemeProperty.getColour(sequences,
828 av.alignment.getWidth(),
829 ColourSchemeProperty.
831 av.getGlobalColourScheme()));
833 cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
834 av.getIgnoreGapsConsensus());
837 SequenceGroup sg = new SequenceGroup(sequences,
838 "TreeGroup", cs, true, true, false,
840 av.alignment.getWidth() - 1);
842 AlignmentPanel[] aps = getAssociatedPanels();
843 for (int a = 0; a < aps.length; a++)
845 if (aps[a].av.getGlobalColourScheme() != null
846 && aps[a].av.getGlobalColourScheme().conservationApplied())
848 Conservation c = new Conservation("Group",
849 ResidueProperties.propHash, 3,
850 sg.getSequences(false),
851 sg.getStartRes(), sg.getEndRes());
854 c.verdict(false, aps[a].av.ConsPercGaps);
855 sg.cs.setConservation(c);
858 aps[a].av.alignment.addGroup(sg);
867 * @param state DOCUMENT ME!
869 public void setShowDistances(boolean state)
871 this.showDistances = state;
878 * @param state DOCUMENT ME!
880 public void setShowBootstrap(boolean state)
882 this.showBootstrap = state;
889 * @param state DOCUMENT ME!
891 public void setMarkPlaceholders(boolean state)
893 this.markPlaceholders = state;
897 AlignmentPanel[] getAssociatedPanels()
901 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
904 return new AlignmentPanel[]{ap};