1 package jalview.ext.archaeopteryx;
3 import jalview.analysis.Conservation;
4 import jalview.api.AlignViewportI;
5 import jalview.datamodel.ColumnSelection;
6 import jalview.datamodel.HiddenColumns;
7 import jalview.datamodel.SequenceGroup;
8 import jalview.datamodel.SequenceI;
9 import jalview.ext.treeviewer.ExternalTreeViewerBindingI;
10 import jalview.gui.AlignViewport;
11 import jalview.gui.Desktop;
12 import jalview.gui.JvOptionPane;
13 import jalview.gui.PaintRefresher;
14 import jalview.schemes.ColourSchemeI;
15 import jalview.schemes.ColourSchemeProperty;
16 import jalview.schemes.UserColourScheme;
17 import jalview.structure.SelectionSource;
18 import jalview.structure.StructureSelectionManager;
19 import jalview.util.MappingUtils;
20 import jalview.util.MessageManager;
21 import jalview.viewmodel.AlignmentViewport;
23 import java.awt.Color;
24 import java.awt.Graphics;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.InputEvent;
27 import java.awt.event.MouseEvent;
28 import java.util.ArrayList;
29 import java.util.HashSet;
30 import java.util.List;
34 import javax.swing.JTabbedPane;
35 import javax.swing.SwingUtilities;
36 import javax.swing.event.ChangeEvent;
37 import javax.swing.event.ChangeListener;
39 import org.forester.archaeopteryx.MainFrame;
40 import org.forester.archaeopteryx.TreePanelUtil;
41 import org.forester.phylogeny.Phylogeny;
42 import org.forester.phylogeny.PhylogenyMethods;
43 import org.forester.phylogeny.PhylogenyNode;
44 import org.forester.phylogeny.data.BranchColor;
47 * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
48 * it originates from, meaning that selecting sequences in the tree viewer also
49 * selects them in the alignment view and vice versa.
51 * @author kjvanderheide
54 public final class JalviewBinding
55 implements ExternalTreeViewerBindingI<PhylogenyNode>
57 private org.forester.archaeopteryx.TreePanel treeView;
59 private AlignmentViewport parentAvport;
61 private final JTabbedPane treeTabs;
63 private final StructureSelectionManager ssm;
65 private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
67 private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
71 private float furthestNodeX;
75 * @param archaeopteryx
77 * @param jalviewAlignmentViewport
78 * alignment viewport from which the tree was calculated.
80 * @param alignMappedToNodes
81 * map with sequences used to calculate the tree and matching tree
82 * nodes as key, value pair respectively.
84 * @param nodesMappedToAlign
85 * map with tree nodes and matching sequences used to calculate the
86 * tree as key, value pair respectively.
88 public JalviewBinding(final MainFrame archaeopteryx,
89 final AlignmentViewport jalviewAlignmentViewport,
90 final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
91 final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
94 if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1)
96 JvOptionPane.showMessageDialog(Desktop.desktop,
97 MessageManager.getString("label.tabs_detected_archaeopteryx"),
98 MessageManager.getString("label.problem_reading_tree_file"),
99 JvOptionPane.WARNING_MESSAGE);
103 // deal with/prohibit null values here as that will cause problems
104 parentAvport = jalviewAlignmentViewport;
105 sequencesBoundToNodes = alignMappedToNodes;
106 nodesBoundToSequences = nodesMappedToAlign;
108 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
109 treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
110 ssm = parentAvport.getStructureSelectionManager();
112 ssm.addSelectionListener(this);
113 treeView.addMouseListener(this);
114 PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
117 treeTabs.addChangeListener(new ChangeListener()
121 public void stateChanged(ChangeEvent e)
124 SwingUtilities.invokeLater(new Runnable()
129 * Resend the selection to the tree view when tabs get switched, this
130 * has to be buried in invokeLater as Forester first resets the tree
131 * view on switching tabs, without invokeLater this would get called
132 * before Forester resets which would nullify the selection.
136 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
137 parentAvport.sendSelection();
138 // PaintRefresher.Refresh(treeView,
139 // parentAvport.getSequenceSetId());
151 public void actionPerformed(ActionEvent e)
156 public void mouseClicked(MouseEvent e)
158 SwingUtilities.invokeLater(new Runnable() {
162 * invokeLater so that this always runs after Forester's mouseClicked
166 final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
169 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
170 // selection if shift
173 parentAvport.setSelectionGroup(null);
176 showNodeSelectionOnAlign(node);
181 partitionTree(e.getX());
183 PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
194 public void mousePressed(final MouseEvent e)
199 public void mouseReleased(MouseEvent e)
204 public void mouseEntered(MouseEvent e)
209 public void mouseExited(MouseEvent e)
215 public void selection(final SequenceGroup seqsel,
216 final ColumnSelection colsel, final HiddenColumns hidden,
217 final SelectionSource source)
219 if (source == parentAvport) // check if source is alignment from where the
222 treeView.setFoundNodes0(
223 new HashSet<Long>(seqsel.getSequences().size()));
225 for (SequenceI selectedSequence : seqsel.getSequences())
227 PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
228 if (matchingNode != null)
230 treeView.getFoundNodes0().add(matchingNode.getId());
233 if (!matchingNode.getBranchData().isHasBranchColor())
235 Color foundNodesColour = treeView.getTreeColorSet()
237 matchingNode.getBranchData()
238 .setBranchColor(new BranchColor(foundNodesColour));
253 * Partially refactored from TreeCanvas
255 public void partitionTree(final int x)
257 Phylogeny tree = treeView.getPhylogeny();
261 // should be calculated on each partition as the tree can theoretically
262 // change in the meantime
263 PhylogenyNode furthestNode = PhylogenyMethods
264 .calculateNodeWithMaxDistanceToRoot(tree);
265 furthestNodeX = furthestNode.getXcoord();
266 rootX = tree.getRoot().getXcoord();
268 if (furthestNodeX != rootX && !(x < rootX || x > furthestNodeX)) // don't
272 // clicked x lies outside
275 Graphics g = treeView.getGraphics();
276 int panelHeight = treeView.getHeight();
277 g.drawLine(x, 0, x, panelHeight);
279 float threshold = (x - rootX) / (furthestNodeX - rootX);
280 List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
289 public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
293 List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
295 parentAvport.setSelectionGroup(null);
296 parentAvport.getAlignment().deleteAllGroups();
297 parentAvport.clearSequenceColours();
298 if (parentAvport.getCodingComplement() != null)
300 parentAvport.getCodingComplement().setSelectionGroup(null);
301 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
302 parentAvport.getCodingComplement().clearSequenceColours();
306 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
308 return nodesAboveThreshold;
313 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
321 private List<PhylogenyNode> colourNodesAboveThreshold(
322 List<PhylogenyNode> nodeList, double threshold,
325 // could also use PhylogenyMethods.getAllDescendants
326 for (PhylogenyNode childNode : node.getDescendants())
328 childNode.getBranchData()
329 .setBranchColor(new BranchColor(Color.black));
330 float nodeCutoff = (childNode.getXcoord() - rootX)
331 / (furthestNodeX - rootX);
333 if (nodeCutoff > threshold)
335 nodeList.add(childNode);
337 Color randomColor = new Color((int) (Math.random() * 255),
338 (int) (Math.random() * 255), (int) (Math.random() * 255));
339 TreePanelUtil.colorizeSubtree(childNode,
340 new BranchColor(randomColor));
341 List<PhylogenyNode> descendantNodes = childNode
342 .getAllExternalDescendants();
343 List<SequenceI> descendantSeqs = new ArrayList<>(); // .forEach instead?
344 for (PhylogenyNode descNode : descendantNodes)
346 descendantSeqs.add(nodesBoundToSequences.get(descNode));
349 SequenceGroup sg = new SequenceGroup(descendantSeqs, null, null,
350 true, true, false, 0,
351 parentAvport.getAlignment().getWidth() - 1);
353 ColourSchemeI cs = null;
354 if (parentAvport.getGlobalColourScheme() != null)
356 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
358 cs = new UserColourScheme(
359 ((UserColourScheme) parentAvport.getGlobalColourScheme())
364 cs = ColourSchemeProperty.getColourScheme(sg, ColourSchemeProperty
365 .getColourName(parentAvport.getGlobalColourScheme()));
368 sg.setColourScheme(cs);
369 sg.getGroupColourScheme().setThreshold(
370 parentAvport.getResidueShading().getThreshold(),
371 parentAvport.isIgnoreGapsConsensus());
372 // sg.recalcConservation();
373 sg.setName("Tree Group:" + sg.hashCode());
374 sg.setIdColour(randomColor);
376 if (parentAvport.getGlobalColourScheme() != null
377 && parentAvport.getResidueShading().conservationApplied())
379 Conservation c = new Conservation("Group", sg.getSequences(null),
380 sg.getStartRes(), sg.getEndRes());
382 c.verdict(false, parentAvport.getConsPercGaps());
383 sg.cs.setConservation(c);
386 parentAvport.getAlignment().addGroup(new SequenceGroup(sg));
387 // TODO can we push all of the below into AlignViewportI?
388 final AlignViewportI codingComplement = parentAvport
389 .getCodingComplement();
390 if (codingComplement != null)
392 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
395 if (mappedGroup.getSequences().size() > 0)
397 codingComplement.getAlignment().addGroup(mappedGroup);
398 for (SequenceI seq : mappedGroup.getSequences())
400 codingComplement.setSequenceColour(seq,
401 randomColor.brighter());
411 colourNodesAboveThreshold(nodeList, threshold,
417 ((AlignViewport) parentAvport).getAlignPanel().updateAnnotation();
419 final AlignViewportI codingComplement = parentAvport
420 .getCodingComplement();
421 if (codingComplement != null)
423 ((AlignViewport) codingComplement).getAlignPanel().updateAnnotation();
432 // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
433 // double treeHeight)
435 // List<PhylogenyNode> groups = new ArrayList<>();
436 // _groupNodes(groups, root, threshold, treeHeight);
437 // System.out.println(groups);
441 // protected void _groupNodes(List<PhylogenyNode> groups, PhylogenyNode nd,
442 // float threshold, double treeHeight)
449 // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold)
455 // for (PhylogenyNode childNode : nd.getDescendants())
457 // _groupNodes(groups, childNode, threshold, treeHeight);
465 * may or may not need an extra repaint on the alignment view (check what kira
469 public void showNodeSelectionOnAlign(final PhylogenyNode node)
472 if (node.isInternal())
474 showMatchingChildSequences(node);
479 showMatchingSequence(node);
490 public void showMatchingSequence(final PhylogenyNode nodeToMatch)
492 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
493 if (matchingSequence != null)
495 long nodeId = nodeToMatch.getId();
496 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
497 treeSelectionChanged(matchingSequence);
498 parentAvport.sendSelection();
504 public void showMatchingChildSequences(final PhylogenyNode parentNode)
506 List<PhylogenyNode> childNodes = PhylogenyMethods
507 .getAllDescendants(parentNode);
510 for (PhylogenyNode childNode : childNodes)
512 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
514 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
515 if (matchingSequence != null)
517 long nodeId = childNode.getId();
518 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
520 treeSelectionChanged(matchingSequence);
525 parentAvport.sendSelection();
531 * Refactored from TreeCanvas.
534 * of the node selected in the tree viewer.
537 public void treeSelectionChanged(final SequenceI sequence)
539 if (!parentAvport.isClosed()) // alignment view could be closed
541 SequenceGroup selected = parentAvport.getSelectionGroup();
543 if (selected == null)
545 selected = new SequenceGroup();
546 parentAvport.setSelectionGroup(selected);
549 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
550 selected.addOrRemove(sequence, true);
554 public void sortByTree_actionPerformed() {
555 // parentAvport.mirrorCommand(command, undo, ssm, source);
558 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
564 * sort the associated alignment view by the current tree.
569 // public void sortByTree_actionPerformed()// modify for Aptx
572 // // if (treeCanvas.applyToAllViews)
574 // final ArrayList<CommandI> commands = new ArrayList<>();
575 // for (AlignmentPanel ap : PaintRefresher
576 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
578 // commands.add(sortAlignmentIn(ap.parentAvport.getAlignPanel()));
580 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
584 // public void undoCommand(AlignmentI[] views)
586 // for (CommandI tsort : commands)
588 // tsort.undoCommand(views);
593 // public int getSize()
595 // return commands.size();
599 // public String getDescription()
601 // return "Tree Sort (many views)";
605 // public void doCommand(AlignmentI[] views)
608 // for (CommandI tsort : commands)
610 // tsort.doCommand(views);
614 // for (AlignmentPanel ap : PaintRefresher
615 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
617 // // ensure all the alignFrames refresh their GI after adding an undo item
618 // ap.alignFrame.updateEditMenuBar();
623 // treeCanvas.ap.alignFrame
624 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
633 * @param objectToCheck
635 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
637 if (set.contains(objectToCheck))
639 set.remove(objectToCheck);
643 set.add(objectToCheck);
648 public AlignmentViewport getParentAvport()
653 public void setParentAvport(final AlignmentViewport parentAvport)
655 this.parentAvport = parentAvport;