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;
56 boolean fitToWindow = true;
57 boolean showDistances = false;
58 boolean showBootstrap = false;
59 boolean markPlaceholders = false;
66 Hashtable nameHash = new Hashtable();
67 Hashtable nodeHash = new Hashtable();
68 SequenceNode highlightNode;
70 boolean applyToAllViews = false;
73 * Creates a new TreeCanvas object.
75 * @param av DOCUMENT ME!
76 * @param tree DOCUMENT ME!
77 * @param scroller DOCUMENT ME!
78 * @param label DOCUMENT ME!
80 public TreeCanvas(TreePanel tp,
88 scrollPane = scroller;
89 addMouseListener(this);
90 addMouseMotionListener(this);
91 PaintRefresher.Register(tp, ap.av.getSequenceSetId());
92 ToolTipManager.sharedInstance().registerComponent(this);
98 * @param sequence DOCUMENT ME!
100 public void treeSelectionChanged(SequenceI sequence)
102 AlignmentPanel[] aps = getAssociatedPanels();
104 for (int a = 0; a < aps.length; a++)
106 SequenceGroup selected = aps[a].av.getSelectionGroup();
108 if (selected == null)
110 selected = new SequenceGroup();
111 aps[a].av.setSelectionGroup(selected);
114 selected.setEndRes(aps[a].av.alignment.getWidth() - 1);
115 selected.addOrRemove(sequence, true);
122 * @param tree DOCUMENT ME!
124 public void setTree(NJTree tree)
127 tree.findHeight(tree.getTopNode());
129 // Now have to calculate longest name based on the leaves
130 Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
131 boolean has_placeholders = false;
134 for (int i = 0; i < leaves.size(); i++)
136 SequenceNode lf = (SequenceNode) leaves.elementAt(i);
138 if (lf.isPlaceholder())
140 has_placeholders = true;
143 if (longestName.length() < ( (Sequence) lf.element()).getName()
146 longestName = TreeCanvas.PLACEHOLDER +
147 ( (Sequence) lf.element()).getName();
151 setMarkPlaceholders(has_placeholders);
157 * @param g DOCUMENT ME!
158 * @param node DOCUMENT ME!
159 * @param chunk DOCUMENT ME!
160 * @param scale DOCUMENT ME!
161 * @param width DOCUMENT ME!
162 * @param offx DOCUMENT ME!
163 * @param offy DOCUMENT ME!
165 public void drawNode(Graphics g, SequenceNode node, float chunk,
166 float scale, int width, int offx, int offy)
173 if ( (node.left() == null) && (node.right() == null))
176 float height = node.height;
177 float dist = node.dist;
179 int xstart = (int) ( (height - dist) * scale) + offx;
180 int xend = (int) (height * scale) + offx;
182 int ypos = (int) (node.ycount * chunk) + offy;
184 if (node.element() instanceof SequenceI)
186 SequenceI seq = (SequenceI) ( (SequenceNode) node).element();
188 if (av.getSequenceColour(seq) == Color.white)
190 g.setColor(Color.black);
194 g.setColor(av.getSequenceColour(seq).darker());
199 g.setColor(Color.black);
202 // Draw horizontal line
203 g.drawLine(xstart, ypos, xend, ypos);
205 String nodeLabel = "";
207 if (showDistances && (node.dist > 0))
209 nodeLabel = new Format("%-.2f").form(node.dist);
216 nodeLabel = nodeLabel + " : ";
219 nodeLabel = nodeLabel + String.valueOf(node.getBootstrap());
222 if (!nodeLabel.equals(""))
224 g.drawString(nodeLabel, xstart + 2, ypos - 2);
227 String name = (markPlaceholders && node.isPlaceholder())
228 ? (PLACEHOLDER + node.getName()) : node.getName();
230 int charWidth = fm.stringWidth(name) + 3;
231 int charHeight = font.getSize();
233 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
234 charWidth, charHeight);
236 nameHash.put( (SequenceI) node.element(), rect);
238 // Colour selected leaves differently
239 SequenceGroup selected = av.getSelectionGroup();
241 if ( (selected != null) &&
242 selected.getSequences(false).contains( (SequenceI) node.element()))
244 g.setColor(Color.gray);
246 g.fillRect(xend + 10, ypos - charHeight / 2, charWidth,
248 g.setColor(Color.white);
251 g.drawString(name, xend + 10, ypos + fm.getDescent());
252 g.setColor(Color.black);
256 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
258 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
261 float height = node.height;
262 float dist = node.dist;
264 int xstart = (int) ( (height - dist) * scale) + offx;
265 int xend = (int) (height * scale) + offx;
266 int ypos = (int) (node.ycount * chunk) + offy;
268 g.setColor( ( (SequenceNode) node).color.darker());
270 // Draw horizontal line
271 g.drawLine(xstart, ypos, xend, ypos);
272 if (node == highlightNode)
273 g.fillRect(xend - 3, ypos - 3, 6, 6);
275 g.fillRect(xend - 2, ypos - 2, 4, 4);
277 int ystart = (int) ( ( (SequenceNode) node.left()).ycount * chunk) +
279 int yend = (int) ( ( (SequenceNode) node.right()).ycount * chunk) +
282 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
283 nodeHash.put(node, pos);
285 g.drawLine( (int) (height * scale) + offx, ystart,
286 (int) (height * scale) + offx, yend);
288 if (showDistances && (node.dist > 0))
290 g.drawString(new Format("%-.2f").form(node.dist).trim(), xstart + 2,
299 * @param x DOCUMENT ME!
300 * @param y DOCUMENT ME!
302 * @return DOCUMENT ME!
304 public Object findElement(int x, int y)
306 Enumeration keys = nameHash.keys();
308 while (keys.hasMoreElements())
310 Object ob = keys.nextElement();
311 Rectangle rect = (Rectangle) nameHash.get(ob);
313 if ( (x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
314 (y <= (rect.y + rect.height)))
320 keys = nodeHash.keys();
322 while (keys.hasMoreElements())
324 Object ob = keys.nextElement();
325 Rectangle rect = (Rectangle) nodeHash.get(ob);
327 if ( (x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
328 (y <= (rect.y + rect.height)))
340 * @param pickBox DOCUMENT ME!
342 public void pickNodes(Rectangle pickBox)
344 int width = getWidth();
345 int height = getHeight();
347 SequenceNode top = tree.getTopNode();
349 float wscale = (float) ( (width * .8) - (offx * 2)) / tree.getMaxHeight();
353 top.count = ( (SequenceNode) top.left()).count +
354 ( (SequenceNode) top.right()).count;
357 float chunk = (float) (height - (offy)) / top.count;
359 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
365 * @param pickBox DOCUMENT ME!
366 * @param node DOCUMENT ME!
367 * @param chunk DOCUMENT ME!
368 * @param scale DOCUMENT ME!
369 * @param width DOCUMENT ME!
370 * @param offx DOCUMENT ME!
371 * @param offy DOCUMENT ME!
373 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
374 float scale, int width, int offx, int offy)
381 if ( (node.left() == null) && (node.right() == null))
383 float height = node.height;
384 float dist = node.dist;
386 int xstart = (int) ( (height - dist) * scale) + offx;
387 int xend = (int) (height * scale) + offx;
389 int ypos = (int) (node.ycount * chunk) + offy;
391 if (pickBox.contains(new Point(xend, ypos)))
393 if (node.element() instanceof SequenceI)
395 SequenceI seq = (SequenceI) node.element();
396 SequenceGroup sg = av.getSelectionGroup();
400 sg.addOrRemove(seq, true);
407 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
409 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
417 * @param node DOCUMENT ME!
418 * @param c DOCUMENT ME!
420 public void setColor(SequenceNode node, Color c)
427 if ( (node.left() == null) && (node.right() == null))
431 if (node.element() instanceof SequenceI)
433 AlignmentPanel[] aps = getAssociatedPanels();
434 for (int a = 0; a < aps.length; a++)
436 aps[a].av.setSequenceColour( (SequenceI) node.element(), c);
443 setColor( (SequenceNode) node.left(), c);
444 setColor( (SequenceNode) node.right(), c);
453 Thread thread = new Thread(this);
457 // put printing in a thread to avoid painting problems
460 PrinterJob printJob = PrinterJob.getPrinterJob();
461 PageFormat pf = printJob.pageDialog(printJob.defaultPage());
463 printJob.setPrintable(this, pf);
465 if (printJob.printDialog())
471 catch (Exception PrintException)
473 PrintException.printStackTrace();
481 * @param pg DOCUMENT ME!
482 * @param pf DOCUMENT ME!
483 * @param pi DOCUMENT ME!
485 * @return DOCUMENT ME!
487 * @throws PrinterException DOCUMENT ME!
489 public int print(Graphics pg, PageFormat pf, int pi)
490 throws PrinterException
493 pg.translate( (int) pf.getImageableX(), (int) pf.getImageableY());
495 int pwidth = (int) pf.getImageableWidth();
496 int pheight = (int) pf.getImageableHeight();
498 int noPages = getHeight() / pheight;
502 return Printable.NO_SUCH_PAGE;
505 if (pwidth > getWidth())
512 if (pheight > getHeight())
514 pheight = getHeight();
521 FontMetrics fm = pg.getFontMetrics(font);
522 int height = fm.getHeight() * nameHash.size();
523 pg.translate(0, -pi * pheight);
524 pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
526 // translate number of pages,
527 // height is screen size as this is the
528 // non overlapping text size
532 draw(pg, pwidth, pheight);
534 return Printable.PAGE_EXISTS;
540 * @param g DOCUMENT ME!
542 public void paintComponent(Graphics g)
544 super.paintComponent(g);
549 g.drawString("Calculating tree....", 20, getHeight() / 2);
553 fm = g.getFontMetrics(font);
555 if (nameHash.size() == 0)
562 (scrollPane.getHeight() > ( (fm.getHeight() * nameHash.size()) +
565 draw(g, scrollPane.getWidth(), scrollPane.getHeight());
566 setPreferredSize(null);
570 setPreferredSize(new Dimension(scrollPane.getWidth(),
571 fm.getHeight() * nameHash.size()));
572 draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
575 scrollPane.revalidate();
582 * @param fontSize DOCUMENT ME!
584 public void setFont(Font font)
593 * @param g1 DOCUMENT ME!
594 * @param width DOCUMENT ME!
595 * @param height DOCUMENT ME!
597 public void draw(Graphics g1, int width, int height)
599 Graphics2D g2 = (Graphics2D) g1;
600 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
601 RenderingHints.VALUE_ANTIALIAS_ON);
602 g2.setColor(Color.white);
603 g2.fillRect(0, 0, width, height);
607 offy = font.getSize() + 10;
609 fm = g2.getFontMetrics(font);
611 labelLength = fm.stringWidth(longestName) + 20; //20 allows for scrollbar
613 float wscale = (float) (width - labelLength - (offx * 2)) /
616 SequenceNode top = tree.getTopNode();
620 top.count = ( (SequenceNode) top.left()).count +
621 ( (SequenceNode) top.right()).count;
624 float chunk = (float) (height - (offy)) / top.count;
626 drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
630 if (av.getCurrentTree() == tree)
632 g2.setColor(Color.red);
636 g2.setColor(Color.gray);
639 int x = (int) ( (threshold * (float) (getWidth() - labelLength -
640 (2 * offx))) + offx);
642 g2.drawLine(x, 0, x, getHeight());
649 * @param e DOCUMENT ME!
651 public void mouseReleased(MouseEvent e)
658 * @param e DOCUMENT ME!
660 public void mouseEntered(MouseEvent e)
667 * @param e DOCUMENT ME!
669 public void mouseExited(MouseEvent e)
676 * @param e DOCUMENT ME!
678 public void mouseClicked(MouseEvent evt)
680 if (highlightNode != null)
682 if (SwingUtilities.isRightMouseButton(evt))
684 Color col = JColorChooser.showDialog(this, "Select Sub-Tree Colour",
685 highlightNode.color);
687 setColor(highlightNode, col);
690 if (evt.getClickCount() > 1)
692 tree.swapNodes(highlightNode);
693 tree.reCount(tree.getTopNode());
694 tree.findHeight(tree.getTopNode());
698 Vector leaves = new Vector();
699 tree.findLeaves(highlightNode, leaves);
701 for (int i = 0; i < leaves.size(); i++)
704 (SequenceI) ( (SequenceNode) leaves.elementAt(i)).element();
705 treeSelectionChanged(seq);
709 PaintRefresher.Refresh(tp, av.getSequenceSetId());
714 public void mouseMoved(MouseEvent evt)
716 av.setCurrentTree(tree);
718 Object ob = findElement(evt.getX(), evt.getY());
720 if (ob instanceof SequenceNode)
722 highlightNode = (SequenceNode) ob;
724 "<html>Left click to select leaves"
725 + "<br>Double-click to invert leaves"
726 + "<br>Right click to change colour");
732 if (highlightNode != null)
734 highlightNode = null;
735 setToolTipText(null);
741 public void mouseDragged(MouseEvent ect)
747 * @param e DOCUMENT ME!
749 public void mousePressed(MouseEvent e)
751 av.setCurrentTree(tree);
756 Object ob = findElement(x, y);
758 if (ob instanceof SequenceI)
760 treeSelectionChanged( (Sequence) ob);
761 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
765 else if (! (ob instanceof SequenceNode))
768 if (tree.getMaxHeight() != 0)
770 threshold = (float) (x - offx) / (float) (getWidth() -
771 labelLength - (2 * offx));
773 tree.getGroups().removeAllElements();
774 tree.groupNodes(tree.getTopNode(), threshold);
775 setColor(tree.getTopNode(), Color.black);
777 AlignmentPanel[] aps = getAssociatedPanels();
779 for (int a = 0; a < aps.length; a++)
781 aps[a].av.setSelectionGroup(null);
782 aps[a].av.alignment.deleteAllGroups();
783 aps[a].av.sequenceColours = null;
788 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
796 for (int i = 0; i < tree.getGroups().size(); i++)
798 Color col = new Color( (int) (Math.random() * 255),
799 (int) (Math.random() * 255),
800 (int) (Math.random() * 255));
801 setColor( (SequenceNode) tree.getGroups().elementAt(i),
804 Vector l = tree.findLeaves( (SequenceNode) tree.getGroups()
808 Vector sequences = new Vector();
810 for (int j = 0; j < l.size(); j++)
812 SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();
814 if (!sequences.contains(s1))
816 sequences.addElement(s1);
820 ColourSchemeI cs = null;
822 if (av.getGlobalColourScheme() != null)
824 if (av.getGlobalColourScheme() instanceof UserColourScheme)
826 cs = new UserColourScheme(
827 ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
831 cs = ColourSchemeProperty.getColour(sequences,
832 av.alignment.getWidth(),
833 ColourSchemeProperty.
835 av.getGlobalColourScheme()));
837 cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
838 av.getIgnoreGapsConsensus());
841 SequenceGroup sg = new SequenceGroup(sequences,
842 "TreeGroup", cs, true, true, false,
844 av.alignment.getWidth() - 1);
846 AlignmentPanel[] aps = getAssociatedPanels();
847 for (int a = 0; a < aps.length; a++)
849 if (aps[a].av.getGlobalColourScheme() != null
850 && aps[a].av.getGlobalColourScheme().conservationApplied())
852 Conservation c = new Conservation("Group",
853 ResidueProperties.propHash, 3,
854 sg.getSequences(false),
855 sg.getStartRes(), sg.getEndRes());
858 c.verdict(false, aps[a].av.ConsPercGaps);
859 sg.cs.setConservation(c);
862 aps[a].av.alignment.addGroup(sg);
871 * @param state DOCUMENT ME!
873 public void setShowDistances(boolean state)
875 this.showDistances = state;
882 * @param state DOCUMENT ME!
884 public void setShowBootstrap(boolean state)
886 this.showBootstrap = state;
893 * @param state DOCUMENT ME!
895 public void setMarkPlaceholders(boolean state)
897 this.markPlaceholders = state;
901 AlignmentPanel[] getAssociatedPanels()
905 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
908 return new AlignmentPanel[]{ap};