2 * Jalview - A Sequence Alignment Editor and Viewer
3 * Copyright (C) 2007 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
24 import java.awt.event.*;
25 import java.awt.print.*;
28 import jalview.analysis.*;
29 import jalview.datamodel.*;
30 import jalview.schemes.*;
31 import jalview.util.*;
39 public class TreeCanvas
40 extends JPanel implements MouseListener, Runnable,
41 Printable, MouseMotionListener
44 public static final String PLACEHOLDER = " * ";
46 JScrollPane scrollPane;
52 boolean fitToWindow = true;
53 boolean showDistances = false;
54 boolean showBootstrap = false;
55 boolean markPlaceholders = false;
62 Hashtable nameHash = new Hashtable();
63 Hashtable nodeHash = new Hashtable();
64 SequenceNode highlightNode;
66 boolean applyToAllViews = false;
69 * Creates a new TreeCanvas object.
71 * @param av DOCUMENT ME!
72 * @param tree DOCUMENT ME!
73 * @param scroller DOCUMENT ME!
74 * @param label DOCUMENT ME!
76 public TreeCanvas(TreePanel tp,
84 scrollPane = scroller;
85 addMouseListener(this);
86 addMouseMotionListener(this);
87 ToolTipManager.sharedInstance().registerComponent(this);
93 * @param sequence DOCUMENT ME!
95 public void treeSelectionChanged(SequenceI sequence)
97 AlignmentPanel[] aps = getAssociatedPanels();
99 for (int a = 0; a < aps.length; a++)
101 SequenceGroup selected = aps[a].av.getSelectionGroup();
103 if (selected == null)
105 selected = new SequenceGroup();
106 aps[a].av.setSelectionGroup(selected);
109 selected.setEndRes(aps[a].av.alignment.getWidth() - 1);
110 selected.addOrRemove(sequence, true);
117 * @param tree DOCUMENT ME!
119 public void setTree(NJTree tree)
122 tree.findHeight(tree.getTopNode());
124 // Now have to calculate longest name based on the leaves
125 Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
126 boolean has_placeholders = false;
129 for (int i = 0; i < leaves.size(); i++)
131 SequenceNode lf = (SequenceNode) leaves.elementAt(i);
133 if (lf.isPlaceholder())
135 has_placeholders = true;
138 if (longestName.length() < ( (Sequence) lf.element()).getName()
141 longestName = TreeCanvas.PLACEHOLDER +
142 ( (Sequence) lf.element()).getName();
146 setMarkPlaceholders(has_placeholders);
152 * @param g DOCUMENT ME!
153 * @param node DOCUMENT ME!
154 * @param chunk DOCUMENT ME!
155 * @param scale DOCUMENT ME!
156 * @param width DOCUMENT ME!
157 * @param offx DOCUMENT ME!
158 * @param offy DOCUMENT ME!
160 public void drawNode(Graphics g, SequenceNode node, float chunk,
161 float scale, int width, int offx, int offy)
168 if ( (node.left() == null) && (node.right() == null))
171 float height = node.height;
172 float dist = node.dist;
174 int xstart = (int) ( (height - dist) * scale) + offx;
175 int xend = (int) (height * scale) + offx;
177 int ypos = (int) (node.ycount * chunk) + offy;
179 if (node.element() instanceof SequenceI)
181 SequenceI seq = (SequenceI) ( (SequenceNode) node).element();
183 if (av.getSequenceColour(seq) == Color.white)
185 g.setColor(Color.black);
189 g.setColor(av.getSequenceColour(seq).darker());
194 g.setColor(Color.black);
197 // Draw horizontal line
198 g.drawLine(xstart, ypos, xend, ypos);
200 String nodeLabel = "";
202 if (showDistances && (node.dist > 0))
204 nodeLabel = new Format("%-.2f").form(node.dist);
207 if (showBootstrap && node.bootstrap>-1)
211 nodeLabel = nodeLabel + " : ";
214 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
217 if (!nodeLabel.equals(""))
219 g.drawString(nodeLabel, xstart + 2, ypos - 2);
222 String name = (markPlaceholders && node.isPlaceholder())
223 ? (PLACEHOLDER + node.getName()) : node.getName();
225 int charWidth = fm.stringWidth(name) + 3;
226 int charHeight = font.getSize();
228 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
229 charWidth, charHeight);
231 nameHash.put( (SequenceI) node.element(), rect);
233 // Colour selected leaves differently
234 SequenceGroup selected = av.getSelectionGroup();
236 if ( (selected != null) &&
237 selected.getSequences(null).contains( (SequenceI) node.element()))
239 g.setColor(Color.gray);
241 g.fillRect(xend + 10, ypos - charHeight / 2, charWidth,
243 g.setColor(Color.white);
246 g.drawString(name, xend + 10, ypos + fm.getDescent());
247 g.setColor(Color.black);
251 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
253 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
256 float height = node.height;
257 float dist = node.dist;
259 int xstart = (int) ( (height - dist) * scale) + offx;
260 int xend = (int) (height * scale) + offx;
261 int ypos = (int) (node.ycount * chunk) + offy;
263 g.setColor( ( (SequenceNode) node).color.darker());
265 // Draw horizontal line
266 g.drawLine(xstart, ypos, xend, ypos);
267 if (node == highlightNode)
269 g.fillRect(xend - 3, ypos - 3, 6, 6);
273 g.fillRect(xend - 2, ypos - 2, 4, 4);
276 int ystart = (int) ( ( (SequenceNode) node.left()).ycount * chunk) +
278 int yend = (int) ( ( (SequenceNode) node.right()).ycount * chunk) +
281 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
282 nodeHash.put(node, pos);
284 g.drawLine( (int) (height * scale) + offx, ystart,
285 (int) (height * scale) + offx, yend);
287 String nodeLabel = "";
289 if (showDistances && (node.dist > 0))
291 nodeLabel = new Format("%-.2f").form(node.dist);
294 if (showBootstrap && node.bootstrap>-1)
298 nodeLabel = nodeLabel + " : ";
301 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
304 if (!nodeLabel.equals(""))
306 g.drawString(nodeLabel, xstart + 2, ypos - 2);
314 * @param x DOCUMENT ME!
315 * @param y DOCUMENT ME!
317 * @return DOCUMENT ME!
319 public Object findElement(int x, int y)
321 Enumeration keys = nameHash.keys();
323 while (keys.hasMoreElements())
325 Object ob = keys.nextElement();
326 Rectangle rect = (Rectangle) nameHash.get(ob);
328 if ( (x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
329 (y <= (rect.y + rect.height)))
335 keys = nodeHash.keys();
337 while (keys.hasMoreElements())
339 Object ob = keys.nextElement();
340 Rectangle rect = (Rectangle) nodeHash.get(ob);
342 if ( (x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y) &&
343 (y <= (rect.y + rect.height)))
355 * @param pickBox DOCUMENT ME!
357 public void pickNodes(Rectangle pickBox)
359 int width = getWidth();
360 int height = getHeight();
362 SequenceNode top = tree.getTopNode();
364 float wscale = (float) ( (width * .8) - (offx * 2)) / tree.getMaxHeight();
368 top.count = ( (SequenceNode) top.left()).count +
369 ( (SequenceNode) top.right()).count;
372 float chunk = (float) (height - (offy)) / top.count;
374 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
380 * @param pickBox DOCUMENT ME!
381 * @param node DOCUMENT ME!
382 * @param chunk DOCUMENT ME!
383 * @param scale DOCUMENT ME!
384 * @param width DOCUMENT ME!
385 * @param offx DOCUMENT ME!
386 * @param offy DOCUMENT ME!
388 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
389 float scale, int width, int offx, int offy)
396 if ( (node.left() == null) && (node.right() == null))
398 float height = node.height;
399 float dist = node.dist;
401 int xstart = (int) ( (height - dist) * scale) + offx;
402 int xend = (int) (height * scale) + offx;
404 int ypos = (int) (node.ycount * chunk) + offy;
406 if (pickBox.contains(new Point(xend, ypos)))
408 if (node.element() instanceof SequenceI)
410 SequenceI seq = (SequenceI) node.element();
411 SequenceGroup sg = av.getSelectionGroup();
415 sg.addOrRemove(seq, true);
422 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
424 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
432 * @param node DOCUMENT ME!
433 * @param c DOCUMENT ME!
435 public void setColor(SequenceNode node, Color c)
442 if ( (node.left() == null) && (node.right() == null)) // TODO: internal node
446 if (node.element() instanceof SequenceI)
448 AlignmentPanel[] aps = getAssociatedPanels();
449 for (int a = 0; a < aps.length; a++)
451 aps[a].av.setSequenceColour( (SequenceI) node.element(), c);
458 setColor( (SequenceNode) node.left(), c);
459 setColor( (SequenceNode) node.right(), c);
468 Thread thread = new Thread(this);
472 // put printing in a thread to avoid painting problems
475 PrinterJob printJob = PrinterJob.getPrinterJob();
476 PageFormat pf = printJob.pageDialog(printJob.defaultPage());
478 printJob.setPrintable(this, pf);
480 if (printJob.printDialog())
486 catch (Exception PrintException)
488 PrintException.printStackTrace();
496 * @param pg DOCUMENT ME!
497 * @param pf DOCUMENT ME!
498 * @param pi DOCUMENT ME!
500 * @return DOCUMENT ME!
502 * @throws PrinterException DOCUMENT ME!
504 public int print(Graphics pg, PageFormat pf, int pi)
505 throws PrinterException
508 pg.translate( (int) pf.getImageableX(), (int) pf.getImageableY());
510 int pwidth = (int) pf.getImageableWidth();
511 int pheight = (int) pf.getImageableHeight();
513 int noPages = getHeight() / pheight;
517 return Printable.NO_SUCH_PAGE;
520 if (pwidth > getWidth())
527 if (pheight > getHeight())
529 pheight = getHeight();
536 FontMetrics fm = pg.getFontMetrics(font);
537 int height = fm.getHeight() * nameHash.size();
538 pg.translate(0, -pi * pheight);
539 pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
541 // translate number of pages,
542 // height is screen size as this is the
543 // non overlapping text size
547 draw(pg, pwidth, pheight);
549 return Printable.PAGE_EXISTS;
555 * @param g DOCUMENT ME!
557 public void paintComponent(Graphics g)
559 super.paintComponent(g);
564 g.drawString("Calculating tree....", 20, getHeight() / 2);
568 fm = g.getFontMetrics(font);
570 if (nameHash.size() == 0)
577 (scrollPane.getHeight() > ( (fm.getHeight() * nameHash.size()) +
580 draw(g, scrollPane.getWidth(), scrollPane.getHeight());
581 setPreferredSize(null);
585 setPreferredSize(new Dimension(scrollPane.getWidth(),
586 fm.getHeight() * nameHash.size()));
587 draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
590 scrollPane.revalidate();
597 * @param fontSize DOCUMENT ME!
599 public void setFont(Font font)
608 * @param g1 DOCUMENT ME!
609 * @param width DOCUMENT ME!
610 * @param height DOCUMENT ME!
612 public void draw(Graphics g1, int width, int height)
614 Graphics2D g2 = (Graphics2D) g1;
615 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
616 RenderingHints.VALUE_ANTIALIAS_ON);
617 g2.setColor(Color.white);
618 g2.fillRect(0, 0, width, height);
622 offy = font.getSize() + 10;
624 fm = g2.getFontMetrics(font);
626 labelLength = fm.stringWidth(longestName) + 20; //20 allows for scrollbar
628 float wscale = (float) (width - labelLength - (offx * 2)) /
631 SequenceNode top = tree.getTopNode();
635 top.count = ( (SequenceNode) top.left()).count +
636 ( (SequenceNode) top.right()).count;
639 float chunk = (float) (height - (offy)) / top.count;
641 drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
645 if (av.getCurrentTree() == tree)
647 g2.setColor(Color.red);
651 g2.setColor(Color.gray);
654 int x = (int) ( (threshold * (float) (getWidth() - labelLength -
655 (2 * offx))) + offx);
657 g2.drawLine(x, 0, x, getHeight());
664 * @param e DOCUMENT ME!
666 public void mouseReleased(MouseEvent e)
673 * @param e DOCUMENT ME!
675 public void mouseEntered(MouseEvent e)
682 * @param e DOCUMENT ME!
684 public void mouseExited(MouseEvent e)
691 * @param e DOCUMENT ME!
693 public void mouseClicked(MouseEvent evt)
695 if (highlightNode != null)
697 if (SwingUtilities.isRightMouseButton(evt))
699 Color col = JColorChooser.showDialog(this, "Select Sub-Tree Colour",
700 highlightNode.color);
703 setColor(highlightNode, col);
707 if (evt.getClickCount() > 1)
709 tree.swapNodes(highlightNode);
710 tree.reCount(tree.getTopNode());
711 tree.findHeight(tree.getTopNode());
715 Vector leaves = new Vector();
716 tree.findLeaves(highlightNode, leaves);
718 for (int i = 0; i < leaves.size(); i++)
721 (SequenceI) ( (SequenceNode) leaves.elementAt(i)).element();
722 treeSelectionChanged(seq);
726 PaintRefresher.Refresh(tp, av.getSequenceSetId());
731 public void mouseMoved(MouseEvent evt)
733 av.setCurrentTree(tree);
735 Object ob = findElement(evt.getX(), evt.getY());
737 if (ob instanceof SequenceNode)
739 highlightNode = (SequenceNode) ob;
741 "<html>Left click to select leaves"
742 + "<br>Double-click to invert leaves"
743 + "<br>Right click to change colour");
749 if (highlightNode != null)
751 highlightNode = null;
752 setToolTipText(null);
758 public void mouseDragged(MouseEvent ect)
764 * @param e DOCUMENT ME!
766 public void mousePressed(MouseEvent e)
768 av.setCurrentTree(tree);
773 Object ob = findElement(x, y);
775 if (ob instanceof SequenceI)
777 treeSelectionChanged( (Sequence) ob);
778 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
782 else if (! (ob instanceof SequenceNode))
785 if (tree.getMaxHeight() != 0)
787 threshold = (float) (x - offx) / (float) (getWidth() -
788 labelLength - (2 * offx));
790 tree.getGroups().removeAllElements();
791 tree.groupNodes(tree.getTopNode(), threshold);
792 setColor(tree.getTopNode(), Color.black);
794 AlignmentPanel[] aps = getAssociatedPanels();
796 for (int a = 0; a < aps.length; a++)
798 aps[a].av.setSelectionGroup(null);
799 aps[a].av.alignment.deleteAllGroups();
800 aps[a].av.sequenceColours = null;
805 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
813 for (int i = 0; i < tree.getGroups().size(); i++)
815 Color col = new Color( (int) (Math.random() * 255),
816 (int) (Math.random() * 255),
817 (int) (Math.random() * 255));
818 setColor( (SequenceNode) tree.getGroups().elementAt(i),
821 Vector l = tree.findLeaves( (SequenceNode) tree.getGroups()
825 Vector sequences = new Vector();
827 for (int j = 0; j < l.size(); j++)
829 SequenceI s1 = (SequenceI) ( (SequenceNode) l.elementAt(j)).element();
831 if (!sequences.contains(s1))
833 sequences.addElement(s1);
837 ColourSchemeI cs = null;
839 if (av.getGlobalColourScheme() != null)
841 if (av.getGlobalColourScheme() instanceof UserColourScheme)
843 cs = new UserColourScheme(
844 ( (UserColourScheme) av.getGlobalColourScheme()).getColours());
849 cs = ColourSchemeProperty.getColour(sequences,
850 av.alignment.getWidth(),
851 ColourSchemeProperty.
853 av.getGlobalColourScheme()));
856 cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
857 av.getIgnoreGapsConsensus());
860 SequenceGroup sg = new SequenceGroup(sequences,
861 null, cs, true, true, false,
863 av.alignment.getWidth() - 1);
865 sg.setName("JTreeGroup:" + sg.hashCode());
867 AlignmentPanel[] aps = getAssociatedPanels();
868 for (int a = 0; a < aps.length; a++)
870 if (aps[a].av.getGlobalColourScheme() != null
871 && aps[a].av.getGlobalColourScheme().conservationApplied())
873 Conservation c = new Conservation("Group",
874 ResidueProperties.propHash, 3,
875 sg.getSequences(null),
876 sg.getStartRes(), sg.getEndRes());
879 c.verdict(false, aps[a].av.ConsPercGaps);
880 sg.cs.setConservation(c);
883 aps[a].av.alignment.addGroup(sg);
892 * @param state DOCUMENT ME!
894 public void setShowDistances(boolean state)
896 this.showDistances = state;
903 * @param state DOCUMENT ME!
905 public void setShowBootstrap(boolean state)
907 this.showBootstrap = state;
914 * @param state DOCUMENT ME!
916 public void setMarkPlaceholders(boolean state)
918 this.markPlaceholders = state;
922 AlignmentPanel[] getAssociatedPanels()
926 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
930 return new AlignmentPanel[]