2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.0b1)
3 * Copyright (C) 2014 The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
11 * Jalview is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14 * PURPOSE. See the GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along with Jalview. If not, see <http://www.gnu.org/licenses/>.
17 * The Jalview Authors are detailed in the 'AUTHORS' file.
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 extends JPanel implements MouseListener, Runnable,
40 Printable, MouseMotionListener
43 public static final String PLACEHOLDER = " * ";
47 JScrollPane scrollPane;
59 boolean fitToWindow = true;
61 boolean showDistances = false;
63 boolean showBootstrap = false;
65 boolean markPlaceholders = false;
77 Hashtable nameHash = new Hashtable();
79 Hashtable nodeHash = new Hashtable();
81 SequenceNode highlightNode;
83 boolean applyToAllViews = false;
86 * Creates a new TreeCanvas object.
97 public TreeCanvas(TreePanel tp, AlignmentPanel ap, JScrollPane scroller)
103 scrollPane = scroller;
104 addMouseListener(this);
105 addMouseMotionListener(this);
106 ToolTipManager.sharedInstance().registerComponent(this);
115 public void treeSelectionChanged(SequenceI sequence)
117 AlignmentPanel[] aps = getAssociatedPanels();
119 for (int a = 0; a < aps.length; a++)
121 SequenceGroup selected = aps[a].av.getSelectionGroup();
123 if (selected == null)
125 selected = new SequenceGroup();
126 aps[a].av.setSelectionGroup(selected);
129 selected.setEndRes(aps[a].av.getAlignment().getWidth() - 1);
130 selected.addOrRemove(sequence, true);
140 public void setTree(NJTree tree)
143 tree.findHeight(tree.getTopNode());
145 // Now have to calculate longest name based on the leaves
146 Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
147 boolean has_placeholders = false;
150 for (int i = 0; i < leaves.size(); i++)
152 SequenceNode lf = (SequenceNode) leaves.elementAt(i);
154 if (lf.isPlaceholder())
156 has_placeholders = true;
159 if (longestName.length() < ((Sequence) lf.element()).getName()
162 longestName = TreeCanvas.PLACEHOLDER
163 + ((Sequence) lf.element()).getName();
167 setMarkPlaceholders(has_placeholders);
188 public void drawNode(Graphics g, SequenceNode node, float chunk,
189 float scale, int width, int offx, int offy)
196 if ((node.left() == null) && (node.right() == null))
199 float height = node.height;
200 float dist = node.dist;
202 int xstart = (int) ((height - dist) * scale) + offx;
203 int xend = (int) (height * scale) + offx;
205 int ypos = (int) (node.ycount * chunk) + offy;
207 if (node.element() instanceof SequenceI)
209 SequenceI seq = (SequenceI) ((SequenceNode) node).element();
211 if (av.getSequenceColour(seq) == Color.white)
213 g.setColor(Color.black);
217 g.setColor(av.getSequenceColour(seq).darker());
222 g.setColor(Color.black);
225 // Draw horizontal line
226 g.drawLine(xstart, ypos, xend, ypos);
228 String nodeLabel = "";
230 if (showDistances && (node.dist > 0))
232 nodeLabel = new Format("%-.2f").form(node.dist);
235 if (showBootstrap && node.bootstrap > -1)
239 nodeLabel = nodeLabel + " : ";
242 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
245 if (!nodeLabel.equals(""))
247 g.drawString(nodeLabel, xstart + 2, ypos - 2);
250 String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
251 .getName()) : node.getName();
253 int charWidth = fm.stringWidth(name) + 3;
254 int charHeight = font.getSize();
256 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
257 charWidth, charHeight);
259 nameHash.put((SequenceI) node.element(), rect);
261 // Colour selected leaves differently
262 SequenceGroup selected = av.getSelectionGroup();
264 if ((selected != null)
265 && selected.getSequences(null).contains(
266 (SequenceI) node.element()))
268 g.setColor(Color.gray);
270 g.fillRect(xend + 10, ypos - charHeight / 2, charWidth, charHeight);
271 g.setColor(Color.white);
274 g.drawString(name, xend + 10, ypos + fm.getDescent());
275 g.setColor(Color.black);
279 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
281 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
284 float height = node.height;
285 float dist = node.dist;
287 int xstart = (int) ((height - dist) * scale) + offx;
288 int xend = (int) (height * scale) + offx;
289 int ypos = (int) (node.ycount * chunk) + offy;
291 g.setColor(((SequenceNode) node).color.darker());
293 // Draw horizontal line
294 g.drawLine(xstart, ypos, xend, ypos);
295 if (node == highlightNode)
297 g.fillRect(xend - 3, ypos - 3, 6, 6);
301 g.fillRect(xend - 2, ypos - 2, 4, 4);
304 int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
306 int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
309 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
310 nodeHash.put(node, pos);
312 g.drawLine((int) (height * scale) + offx, ystart,
313 (int) (height * scale) + offx, yend);
315 String nodeLabel = "";
317 if (showDistances && (node.dist > 0))
319 nodeLabel = new Format("%-.2f").form(node.dist);
322 if (showBootstrap && node.bootstrap > -1)
326 nodeLabel = nodeLabel + " : ";
329 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
332 if (!nodeLabel.equals(""))
334 g.drawString(nodeLabel, xstart + 2, ypos - 2);
347 * @return DOCUMENT ME!
349 public Object findElement(int x, int y)
351 Enumeration keys = nameHash.keys();
353 while (keys.hasMoreElements())
355 Object ob = keys.nextElement();
356 Rectangle rect = (Rectangle) nameHash.get(ob);
358 if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
359 && (y <= (rect.y + rect.height)))
365 keys = nodeHash.keys();
367 while (keys.hasMoreElements())
369 Object ob = keys.nextElement();
370 Rectangle rect = (Rectangle) nodeHash.get(ob);
372 if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
373 && (y <= (rect.y + rect.height)))
388 public void pickNodes(Rectangle pickBox)
390 int width = getWidth();
391 int height = getHeight();
393 SequenceNode top = tree.getTopNode();
395 float wscale = (float) ((width * .8) - (offx * 2))
396 / tree.getMaxHeight();
400 top.count = ((SequenceNode) top.left()).count
401 + ((SequenceNode) top.right()).count;
404 float chunk = (float) (height - (offy)) / top.count;
406 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
427 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
428 float scale, int width, int offx, int offy)
435 if ((node.left() == null) && (node.right() == null))
437 float height = node.height;
438 float dist = node.dist;
440 int xstart = (int) ((height - dist) * scale) + offx;
441 int xend = (int) (height * scale) + offx;
443 int ypos = (int) (node.ycount * chunk) + offy;
445 if (pickBox.contains(new Point(xend, ypos)))
447 if (node.element() instanceof SequenceI)
449 SequenceI seq = (SequenceI) node.element();
450 SequenceGroup sg = av.getSelectionGroup();
454 sg.addOrRemove(seq, true);
461 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
463 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
476 public void setColor(SequenceNode node, Color c)
483 if ((node.left() == null) && (node.right() == null)) // TODO: internal node
487 if (node.element() instanceof SequenceI)
489 AlignmentPanel[] aps = getAssociatedPanels();
492 for (int a = 0; a < aps.length; a++)
494 aps[a].av.setSequenceColour((SequenceI) node.element(), c);
502 setColor((SequenceNode) node.left(), c);
503 setColor((SequenceNode) node.right(), c);
512 Thread thread = new Thread(this);
516 // put printing in a thread to avoid painting problems
519 PrinterJob printJob = PrinterJob.getPrinterJob();
520 PageFormat pf = printJob.pageDialog(printJob.defaultPage());
522 printJob.setPrintable(this, pf);
524 if (printJob.printDialog())
529 } catch (Exception PrintException)
531 PrintException.printStackTrace();
546 * @return DOCUMENT ME!
548 * @throws PrinterException
551 public int print(Graphics pg, PageFormat pf, int pi)
552 throws PrinterException
555 pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
557 int pwidth = (int) pf.getImageableWidth();
558 int pheight = (int) pf.getImageableHeight();
560 int noPages = getHeight() / pheight;
564 return Printable.NO_SUCH_PAGE;
567 if (pwidth > getWidth())
574 if (pheight > getHeight())
576 pheight = getHeight();
583 FontMetrics fm = pg.getFontMetrics(font);
584 int height = fm.getHeight() * nameHash.size();
585 pg.translate(0, -pi * pheight);
586 pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
588 // translate number of pages,
589 // height is screen size as this is the
590 // non overlapping text size
594 draw(pg, pwidth, pheight);
596 return Printable.PAGE_EXISTS;
605 public void paintComponent(Graphics g)
607 super.paintComponent(g);
612 g.drawString(MessageManager.getString("label.calculating_tree") + "....", 20, getHeight() / 2);
616 fm = g.getFontMetrics(font);
618 if (nameHash.size() == 0)
624 || (!fitToWindow && (scrollPane.getHeight() > ((fm
625 .getHeight() * nameHash.size()) + offy))))
627 draw(g, scrollPane.getWidth(), scrollPane.getHeight());
628 setPreferredSize(null);
632 setPreferredSize(new Dimension(scrollPane.getWidth(),
633 fm.getHeight() * nameHash.size()));
634 draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
637 scrollPane.revalidate();
647 public void setFont(Font font)
663 public void draw(Graphics g1, int width, int height)
665 Graphics2D g2 = (Graphics2D) g1;
666 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
667 RenderingHints.VALUE_ANTIALIAS_ON);
668 g2.setColor(Color.white);
669 g2.fillRect(0, 0, width, height);
672 if (longestName==null || tree ==null)
674 g2.drawString("Calculating tree.",20,20);
676 offy = font.getSize() + 10;
678 fm = g2.getFontMetrics(font);
680 labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
682 float wscale = (float) (width - labelLength - (offx * 2))
683 / tree.getMaxHeight();
685 SequenceNode top = tree.getTopNode();
689 top.count = ((SequenceNode) top.left()).count
690 + ((SequenceNode) top.right()).count;
693 float chunk = (float) (height - (offy)) / top.count;
695 drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
699 if (av.getCurrentTree() == tree)
701 g2.setColor(Color.red);
705 g2.setColor(Color.gray);
708 int x = (int) ((threshold * (float) (getWidth() - labelLength - (2 * offx))) + offx);
710 g2.drawLine(x, 0, x, getHeight());
720 public void mouseReleased(MouseEvent e)
730 public void mouseEntered(MouseEvent e)
740 public void mouseExited(MouseEvent e)
750 public void mouseClicked(MouseEvent evt)
752 if (highlightNode != null)
754 if (SwingUtilities.isRightMouseButton(evt))
756 Color col = JColorChooser.showDialog(this,
757 "Select Sub-Tree Colour", highlightNode.color);
760 setColor(highlightNode, col);
763 else if (evt.getClickCount() > 1)
765 tree.swapNodes(highlightNode);
766 tree.reCount(tree.getTopNode());
767 tree.findHeight(tree.getTopNode());
771 Vector leaves = new Vector();
772 tree.findLeaves(highlightNode, leaves);
774 for (int i = 0; i < leaves.size(); i++)
776 SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
778 treeSelectionChanged(seq);
783 PaintRefresher.Refresh(tp, av.getSequenceSetId());
788 public void mouseMoved(MouseEvent evt)
790 av.setCurrentTree(tree);
792 Object ob = findElement(evt.getX(), evt.getY());
794 if (ob instanceof SequenceNode)
796 highlightNode = (SequenceNode) ob;
797 this.setToolTipText("<html>" + MessageManager.getString("label.highlightnode"));
803 if (highlightNode != null)
805 highlightNode = null;
806 setToolTipText(null);
812 public void mouseDragged(MouseEvent ect)
822 public void mousePressed(MouseEvent e)
824 av.setCurrentTree(tree);
829 Object ob = findElement(x, y);
831 if (ob instanceof SequenceI)
833 treeSelectionChanged((Sequence) ob);
834 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
839 else if (!(ob instanceof SequenceNode))
842 if (tree.getMaxHeight() != 0)
844 threshold = (float) (x - offx)
845 / (float) (getWidth() - labelLength - (2 * offx));
847 tree.getGroups().removeAllElements();
848 tree.groupNodes(tree.getTopNode(), threshold);
849 setColor(tree.getTopNode(), Color.black);
851 AlignmentPanel[] aps = getAssociatedPanels();
853 for (int a = 0; a < aps.length; a++)
855 aps[a].av.setSelectionGroup(null);
856 aps[a].av.getAlignment().deleteAllGroups();
857 aps[a].av.clearSequenceColours();
862 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
870 AlignmentPanel[] aps = getAssociatedPanels();
871 for (int i = 0; i < tree.getGroups().size(); i++)
873 Color col = new Color((int) (Math.random() * 255),
874 (int) (Math.random() * 255), (int) (Math.random() * 255));
875 setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
877 Vector l = tree.findLeaves(
878 (SequenceNode) tree.getGroups().elementAt(i), new Vector());
880 Vector sequences = new Vector();
882 for (int j = 0; j < l.size(); j++)
884 SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
887 if (!sequences.contains(s1))
889 sequences.addElement(s1);
893 ColourSchemeI cs = null;
894 SequenceGroup sg = new SequenceGroup(sequences, null, cs, true, true,
895 false, 0, av.getAlignment().getWidth() - 1);
897 if (av.getGlobalColourScheme() != null)
899 if (av.getGlobalColourScheme() instanceof UserColourScheme)
901 cs = new UserColourScheme(
902 ((UserColourScheme) av.getGlobalColourScheme())
908 cs = ColourSchemeProperty.getColour(sg, ColourSchemeProperty
909 .getColourName(av.getGlobalColourScheme()));
911 // cs is null if shading is an annotationColourGradient
914 cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
915 av.getIgnoreGapsConsensus());
919 // sg.recalcConservation();
920 sg.setName("JTreeGroup:" + sg.hashCode());
922 for (int a = 0; a < aps.length; a++)
924 if (aps[a].av.getGlobalColourScheme() != null
925 && aps[a].av.getGlobalColourScheme().conservationApplied())
927 Conservation c = new Conservation("Group",
928 ResidueProperties.propHash, 3, sg.getSequences(null),
929 sg.getStartRes(), sg.getEndRes());
932 c.verdict(false, aps[a].av.getConsPercGaps());
933 sg.cs.setConservation(c);
936 aps[a].av.getAlignment().addGroup(new SequenceGroup(sg));
939 // notify the panel to redo any group specific stuff.
940 for (int a = 0; a < aps.length; a++)
942 aps[a].updateAnnotation();
943 // TODO: JAL-868 - need to ensure view colour change message is broadcast
944 // to any Jmols listening in
955 public void setShowDistances(boolean state)
957 this.showDistances = state;
967 public void setShowBootstrap(boolean state)
969 this.showBootstrap = state;
979 public void setMarkPlaceholders(boolean state)
981 this.markPlaceholders = state;
985 AlignmentPanel[] getAssociatedPanels()
989 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
993 return new AlignmentPanel[]