2 * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
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
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
26 import java.awt.event.*;
27 import java.awt.print.*;
30 import jalview.analysis.*;
31 import jalview.datamodel.*;
32 import jalview.schemes.*;
33 import jalview.util.*;
41 public class TreeCanvas extends JPanel implements MouseListener, Runnable,
42 Printable, MouseMotionListener
45 public static final String PLACEHOLDER = " * ";
49 JScrollPane scrollPane;
61 boolean fitToWindow = true;
63 boolean showDistances = false;
65 boolean showBootstrap = false;
67 boolean markPlaceholders = false;
79 Hashtable nameHash = new Hashtable();
81 Hashtable nodeHash = new Hashtable();
83 SequenceNode highlightNode;
85 boolean applyToAllViews = false;
88 * Creates a new TreeCanvas object.
99 public TreeCanvas(TreePanel tp, AlignmentPanel ap, JScrollPane scroller)
105 scrollPane = scroller;
106 addMouseListener(this);
107 addMouseMotionListener(this);
108 ToolTipManager.sharedInstance().registerComponent(this);
117 public void treeSelectionChanged(SequenceI sequence)
119 AlignmentPanel[] aps = getAssociatedPanels();
121 for (int a = 0; a < aps.length; a++)
123 SequenceGroup selected = aps[a].av.getSelectionGroup();
125 if (selected == null)
127 selected = new SequenceGroup();
128 aps[a].av.setSelectionGroup(selected);
131 selected.setEndRes(aps[a].av.getAlignment().getWidth() - 1);
132 selected.addOrRemove(sequence, true);
142 public void setTree(NJTree tree)
145 tree.findHeight(tree.getTopNode());
147 // Now have to calculate longest name based on the leaves
148 Vector leaves = tree.findLeaves(tree.getTopNode(), new Vector());
149 boolean has_placeholders = false;
152 for (int i = 0; i < leaves.size(); i++)
154 SequenceNode lf = (SequenceNode) leaves.elementAt(i);
156 if (lf.isPlaceholder())
158 has_placeholders = true;
161 if (longestName.length() < ((Sequence) lf.element()).getName()
164 longestName = TreeCanvas.PLACEHOLDER
165 + ((Sequence) lf.element()).getName();
169 setMarkPlaceholders(has_placeholders);
190 public void drawNode(Graphics g, SequenceNode node, float chunk,
191 float scale, int width, int offx, int offy)
198 if ((node.left() == null) && (node.right() == null))
201 float height = node.height;
202 float dist = node.dist;
204 int xstart = (int) ((height - dist) * scale) + offx;
205 int xend = (int) (height * scale) + offx;
207 int ypos = (int) (node.ycount * chunk) + offy;
209 if (node.element() instanceof SequenceI)
211 SequenceI seq = (SequenceI) ((SequenceNode) node).element();
213 if (av.getSequenceColour(seq) == Color.white)
215 g.setColor(Color.black);
219 g.setColor(av.getSequenceColour(seq).darker());
224 g.setColor(Color.black);
227 // Draw horizontal line
228 g.drawLine(xstart, ypos, xend, ypos);
230 String nodeLabel = "";
232 if (showDistances && (node.dist > 0))
234 nodeLabel = new Format("%-.2f").form(node.dist);
237 if (showBootstrap && node.bootstrap > -1)
241 nodeLabel = nodeLabel + " : ";
244 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
247 if (!nodeLabel.equals(""))
249 g.drawString(nodeLabel, xstart + 2, ypos - 2);
252 String name = (markPlaceholders && node.isPlaceholder()) ? (PLACEHOLDER + node
253 .getName()) : node.getName();
255 int charWidth = fm.stringWidth(name) + 3;
256 int charHeight = font.getSize();
258 Rectangle rect = new Rectangle(xend + 10, ypos - charHeight / 2,
259 charWidth, charHeight);
261 nameHash.put((SequenceI) node.element(), rect);
263 // Colour selected leaves differently
264 SequenceGroup selected = av.getSelectionGroup();
266 if ((selected != null)
267 && selected.getSequences(null).contains(
268 (SequenceI) node.element()))
270 g.setColor(Color.gray);
272 g.fillRect(xend + 10, ypos - charHeight / 2, charWidth, charHeight);
273 g.setColor(Color.white);
276 g.drawString(name, xend + 10, ypos + fm.getDescent());
277 g.setColor(Color.black);
281 drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
283 drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
286 float height = node.height;
287 float dist = node.dist;
289 int xstart = (int) ((height - dist) * scale) + offx;
290 int xend = (int) (height * scale) + offx;
291 int ypos = (int) (node.ycount * chunk) + offy;
293 g.setColor(((SequenceNode) node).color.darker());
295 // Draw horizontal line
296 g.drawLine(xstart, ypos, xend, ypos);
297 if (node == highlightNode)
299 g.fillRect(xend - 3, ypos - 3, 6, 6);
303 g.fillRect(xend - 2, ypos - 2, 4, 4);
306 int ystart = (int) (((SequenceNode) node.left()).ycount * chunk)
308 int yend = (int) (((SequenceNode) node.right()).ycount * chunk)
311 Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
312 nodeHash.put(node, pos);
314 g.drawLine((int) (height * scale) + offx, ystart,
315 (int) (height * scale) + offx, yend);
317 String nodeLabel = "";
319 if (showDistances && (node.dist > 0))
321 nodeLabel = new Format("%-.2f").form(node.dist);
324 if (showBootstrap && node.bootstrap > -1)
328 nodeLabel = nodeLabel + " : ";
331 nodeLabel = nodeLabel + String.valueOf(node.bootstrap);
334 if (!nodeLabel.equals(""))
336 g.drawString(nodeLabel, xstart + 2, ypos - 2);
349 * @return DOCUMENT ME!
351 public Object findElement(int x, int y)
353 Enumeration keys = nameHash.keys();
355 while (keys.hasMoreElements())
357 Object ob = keys.nextElement();
358 Rectangle rect = (Rectangle) nameHash.get(ob);
360 if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
361 && (y <= (rect.y + rect.height)))
367 keys = nodeHash.keys();
369 while (keys.hasMoreElements())
371 Object ob = keys.nextElement();
372 Rectangle rect = (Rectangle) nodeHash.get(ob);
374 if ((x >= rect.x) && (x <= (rect.x + rect.width)) && (y >= rect.y)
375 && (y <= (rect.y + rect.height)))
390 public void pickNodes(Rectangle pickBox)
392 int width = getWidth();
393 int height = getHeight();
395 SequenceNode top = tree.getTopNode();
397 float wscale = (float) ((width * .8) - (offx * 2))
398 / tree.getMaxHeight();
402 top.count = ((SequenceNode) top.left()).count
403 + ((SequenceNode) top.right()).count;
406 float chunk = (float) (height - (offy)) / top.count;
408 pickNode(pickBox, top, chunk, wscale, width, offx, offy);
429 public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
430 float scale, int width, int offx, int offy)
437 if ((node.left() == null) && (node.right() == null))
439 float height = node.height;
440 float dist = node.dist;
442 int xstart = (int) ((height - dist) * scale) + offx;
443 int xend = (int) (height * scale) + offx;
445 int ypos = (int) (node.ycount * chunk) + offy;
447 if (pickBox.contains(new Point(xend, ypos)))
449 if (node.element() instanceof SequenceI)
451 SequenceI seq = (SequenceI) node.element();
452 SequenceGroup sg = av.getSelectionGroup();
456 sg.addOrRemove(seq, true);
463 pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
465 pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
478 public void setColor(SequenceNode node, Color c)
485 if ((node.left() == null) && (node.right() == null)) // TODO: internal node
489 if (node.element() instanceof SequenceI)
491 AlignmentPanel[] aps = getAssociatedPanels();
494 for (int a = 0; a < aps.length; a++)
496 aps[a].av.setSequenceColour((SequenceI) node.element(), c);
504 setColor((SequenceNode) node.left(), c);
505 setColor((SequenceNode) node.right(), c);
514 Thread thread = new Thread(this);
518 // put printing in a thread to avoid painting problems
521 PrinterJob printJob = PrinterJob.getPrinterJob();
522 PageFormat pf = printJob.pageDialog(printJob.defaultPage());
524 printJob.setPrintable(this, pf);
526 if (printJob.printDialog())
531 } catch (Exception PrintException)
533 PrintException.printStackTrace();
548 * @return DOCUMENT ME!
550 * @throws PrinterException
553 public int print(Graphics pg, PageFormat pf, int pi)
554 throws PrinterException
557 pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
559 int pwidth = (int) pf.getImageableWidth();
560 int pheight = (int) pf.getImageableHeight();
562 int noPages = getHeight() / pheight;
566 return Printable.NO_SUCH_PAGE;
569 if (pwidth > getWidth())
576 if (pheight > getHeight())
578 pheight = getHeight();
585 FontMetrics fm = pg.getFontMetrics(font);
586 int height = fm.getHeight() * nameHash.size();
587 pg.translate(0, -pi * pheight);
588 pg.setClip(0, pi * pheight, pwidth, (pi * pheight) + pheight);
590 // translate number of pages,
591 // height is screen size as this is the
592 // non overlapping text size
596 draw(pg, pwidth, pheight);
598 return Printable.PAGE_EXISTS;
607 public void paintComponent(Graphics g)
609 super.paintComponent(g);
614 g.drawString(MessageManager.getString("label.calculating_tree")
615 + "....", 20, getHeight() / 2);
619 fm = g.getFontMetrics(font);
621 if (nameHash.size() == 0)
627 || (!fitToWindow && (scrollPane.getHeight() > ((fm
628 .getHeight() * nameHash.size()) + offy))))
630 draw(g, scrollPane.getWidth(), scrollPane.getHeight());
631 setPreferredSize(null);
635 setPreferredSize(new Dimension(scrollPane.getWidth(),
636 fm.getHeight() * nameHash.size()));
637 draw(g, scrollPane.getWidth(), fm.getHeight() * nameHash.size());
640 scrollPane.revalidate();
650 public void setFont(Font font)
666 public void draw(Graphics g1, int width, int height)
668 Graphics2D g2 = (Graphics2D) g1;
669 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
670 RenderingHints.VALUE_ANTIALIAS_ON);
671 g2.setColor(Color.white);
672 g2.fillRect(0, 0, width, height);
675 if (longestName == null || tree == null)
677 g2.drawString("Calculating tree.", 20, 20);
679 offy = font.getSize() + 10;
681 fm = g2.getFontMetrics(font);
683 labelLength = fm.stringWidth(longestName) + 20; // 20 allows for scrollbar
685 float wscale = (float) (width - labelLength - (offx * 2))
686 / tree.getMaxHeight();
688 SequenceNode top = tree.getTopNode();
692 top.count = ((SequenceNode) top.left()).count
693 + ((SequenceNode) top.right()).count;
696 float chunk = (float) (height - (offy)) / top.count;
698 drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
702 if (av.getCurrentTree() == tree)
704 g2.setColor(Color.red);
708 g2.setColor(Color.gray);
711 int x = (int) ((threshold * (float) (getWidth() - labelLength - (2 * offx))) + offx);
713 g2.drawLine(x, 0, x, getHeight());
723 public void mouseReleased(MouseEvent e)
733 public void mouseEntered(MouseEvent e)
743 public void mouseExited(MouseEvent e)
753 public void mouseClicked(MouseEvent evt)
755 if (highlightNode != null)
757 if (SwingUtilities.isRightMouseButton(evt))
759 Color col = JColorChooser.showDialog(this,
760 MessageManager.getString("label.select_subtree_colour"), highlightNode.color);
763 setColor(highlightNode, col);
766 else if (evt.getClickCount() > 1)
768 tree.swapNodes(highlightNode);
769 tree.reCount(tree.getTopNode());
770 tree.findHeight(tree.getTopNode());
774 Vector leaves = new Vector();
775 tree.findLeaves(highlightNode, leaves);
777 for (int i = 0; i < leaves.size(); i++)
779 SequenceI seq = (SequenceI) ((SequenceNode) leaves.elementAt(i))
781 treeSelectionChanged(seq);
786 PaintRefresher.Refresh(tp, av.getSequenceSetId());
791 public void mouseMoved(MouseEvent evt)
793 av.setCurrentTree(tree);
795 Object ob = findElement(evt.getX(), evt.getY());
797 if (ob instanceof SequenceNode)
799 highlightNode = (SequenceNode) ob;
800 this.setToolTipText("<html>"
801 + MessageManager.getString("label.highlightnode"));
807 if (highlightNode != null)
809 highlightNode = null;
810 setToolTipText(null);
816 public void mouseDragged(MouseEvent ect)
826 public void mousePressed(MouseEvent e)
828 av.setCurrentTree(tree);
833 Object ob = findElement(x, y);
835 if (ob instanceof SequenceI)
837 treeSelectionChanged((Sequence) ob);
838 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
843 else if (!(ob instanceof SequenceNode))
846 if (tree.getMaxHeight() != 0)
848 threshold = (float) (x - offx)
849 / (float) (getWidth() - labelLength - (2 * offx));
851 tree.getGroups().removeAllElements();
852 tree.groupNodes(tree.getTopNode(), threshold);
853 setColor(tree.getTopNode(), Color.black);
855 AlignmentPanel[] aps = getAssociatedPanels();
857 for (int a = 0; a < aps.length; a++)
859 aps[a].av.setSelectionGroup(null);
860 aps[a].av.getAlignment().deleteAllGroups();
861 aps[a].av.clearSequenceColours();
866 PaintRefresher.Refresh(tp, ap.av.getSequenceSetId());
874 AlignmentPanel[] aps = getAssociatedPanels();
875 for (int i = 0; i < tree.getGroups().size(); i++)
877 Color col = new Color((int) (Math.random() * 255),
878 (int) (Math.random() * 255), (int) (Math.random() * 255));
879 setColor((SequenceNode) tree.getGroups().elementAt(i), col.brighter());
881 Vector l = tree.findLeaves(
882 (SequenceNode) tree.getGroups().elementAt(i), new Vector());
884 Vector sequences = new Vector();
886 for (int j = 0; j < l.size(); j++)
888 SequenceI s1 = (SequenceI) ((SequenceNode) l.elementAt(j))
891 if (!sequences.contains(s1))
893 sequences.addElement(s1);
897 ColourSchemeI cs = null;
898 SequenceGroup sg = new SequenceGroup(sequences, null, cs, true, true,
899 false, 0, av.getAlignment().getWidth() - 1);
901 if (av.getGlobalColourScheme() != null)
903 if (av.getGlobalColourScheme() instanceof UserColourScheme)
905 cs = new UserColourScheme(
906 ((UserColourScheme) av.getGlobalColourScheme())
912 cs = ColourSchemeProperty.getColour(sg, ColourSchemeProperty
913 .getColourName(av.getGlobalColourScheme()));
915 // cs is null if shading is an annotationColourGradient
918 cs.setThreshold(av.getGlobalColourScheme().getThreshold(),
919 av.getIgnoreGapsConsensus());
923 // sg.recalcConservation();
924 sg.setName("JTreeGroup:" + sg.hashCode());
926 for (int a = 0; a < aps.length; a++)
928 if (aps[a].av.getGlobalColourScheme() != null
929 && aps[a].av.getGlobalColourScheme().conservationApplied())
931 Conservation c = new Conservation("Group",
932 ResidueProperties.propHash, 3, sg.getSequences(null),
933 sg.getStartRes(), sg.getEndRes());
936 c.verdict(false, aps[a].av.getConsPercGaps());
937 sg.cs.setConservation(c);
940 aps[a].av.getAlignment().addGroup(new SequenceGroup(sg));
943 // notify the panel to redo any group specific stuff.
944 for (int a = 0; a < aps.length; a++)
946 aps[a].updateAnnotation();
947 // TODO: JAL-868 - need to ensure view colour change message is broadcast
948 // to any Jmols listening in
959 public void setShowDistances(boolean state)
961 this.showDistances = state;
971 public void setShowBootstrap(boolean state)
973 this.showBootstrap = state;
983 public void setMarkPlaceholders(boolean state)
985 this.markPlaceholders = state;
989 AlignmentPanel[] getAssociatedPanels()
993 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
997 return new AlignmentPanel[]