1 package jalview.ext.archaeopteryx;
3 import jalview.analysis.AlignmentSorter;
4 import jalview.analysis.Conservation;
5 import jalview.api.AlignViewportI;
6 import jalview.commands.CommandI;
7 import jalview.commands.OrderCommand;
8 import jalview.datamodel.ColumnSelection;
9 import jalview.datamodel.HiddenColumns;
10 import jalview.datamodel.SequenceGroup;
11 import jalview.datamodel.SequenceI;
12 import jalview.ext.treeviewer.ExternalTreeViewerBindingI;
13 import jalview.gui.AlignViewport;
14 import jalview.gui.AlignmentPanel;
15 import jalview.gui.Desktop;
16 import jalview.gui.JvOptionPane;
17 import jalview.gui.PaintRefresher;
18 import jalview.schemes.ColourSchemeI;
19 import jalview.schemes.ColourSchemeProperty;
20 import jalview.schemes.UserColourScheme;
21 import jalview.structure.SelectionSource;
22 import jalview.structure.StructureSelectionManager;
23 import jalview.util.MappingUtils;
24 import jalview.util.MessageManager;
25 import jalview.viewmodel.AlignmentViewport;
27 import java.awt.Color;
28 import java.awt.Rectangle;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.InputEvent;
31 import java.awt.event.MouseEvent;
32 import java.util.ArrayList;
33 import java.util.HashSet;
34 import java.util.List;
38 import javax.swing.JTabbedPane;
39 import javax.swing.SwingUtilities;
40 import javax.swing.event.ChangeEvent;
41 import javax.swing.event.ChangeListener;
42 import javax.swing.event.InternalFrameAdapter;
43 import javax.swing.event.InternalFrameEvent;
45 import org.forester.archaeopteryx.MainFrame;
46 import org.forester.phylogeny.Phylogeny;
47 import org.forester.phylogeny.PhylogenyMethods;
48 import org.forester.phylogeny.PhylogenyNode;
49 import org.forester.phylogeny.data.BranchColor;
52 * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
53 * it originates from, meaning that selecting sequences in the tree viewer also
54 * selects them in the alignment view and vice versa.
56 * @author kjvanderheide
59 public final class JalviewBinding
60 implements ExternalTreeViewerBindingI<PhylogenyNode>
62 private final MainFrame aptxFrame;
64 private org.forester.archaeopteryx.TreePanel treeView;
66 private AlignmentViewport parentAvport;
68 private final JTabbedPane treeTabs;
70 private final StructureSelectionManager ssm;
72 private AlignmentPanel[] associatedPanels;
74 private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
76 private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
80 private float furthestNodeX;
82 private int nrTreeGroups = 0;
84 private boolean applyToAllViews = false;
88 * @param archaeopteryx
90 * @param jalviewAlignmentViewport
91 * alignment viewport from which the tree was calculated.
93 * @param alignMappedToNodes
94 * map with sequences used to calculate the tree and matching tree
95 * nodes as key, value pair respectively.
97 * @param nodesMappedToAlign
98 * map with tree nodes and matching sequences used to calculate the
99 * tree as key, value pair respectively.
101 public JalviewBinding(final MainFrame archaeopteryx,
102 final AlignmentViewport jalviewAlignmentViewport,
103 final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
104 final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
107 if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1)
109 JvOptionPane.showMessageDialog(Desktop.desktop,
110 MessageManager.getString("label.tabs_detected_archaeopteryx"),
111 MessageManager.getString("label.problem_reading_tree_file"),
112 JvOptionPane.WARNING_MESSAGE);
116 // deal with/prohibit null values here as that will cause problems
117 aptxFrame = archaeopteryx;
118 parentAvport = jalviewAlignmentViewport;
119 sequencesBoundToNodes = alignMappedToNodes;
120 nodesBoundToSequences = nodesMappedToAlign;
122 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
123 treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
124 ssm = parentAvport.getStructureSelectionManager();
126 ssm.addSelectionListener(this);
127 treeView.addMouseListener(this);
129 PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
130 associatedPanels = PaintRefresher
131 .getAssociatedPanels(parentAvport.getSequenceSetId());
133 aptxFrame.addInternalFrameListener(new InternalFrameAdapter()
137 public void internalFrameClosed(InternalFrameEvent e)
139 AptxInit.getAllAptxFrames().remove(aptxFrame);
140 ssm.removeSelectionListener(JalviewBinding.this);
145 treeTabs.addChangeListener(new ChangeListener()
149 public void stateChanged(ChangeEvent e)
152 SwingUtilities.invokeLater(new Runnable()
157 * Resend the selection to the tree view when tabs get switched, this
158 * has to be buried in invokeLater as Forester first resets the tree
159 * view on switching tabs, without invokeLater this would get called
160 * before Forester resets which would nullify the selection.
164 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
165 parentAvport.sendSelection();
166 // PaintRefresher.Refresh(treeView,
167 // parentAvport.getSequenceSetId());
179 public void actionPerformed(ActionEvent e)
181 // reset hidden sequences first
182 parentAvport.showAllHiddenSeqs();
184 if (treeView.isCurrentTreeIsSubtree())
186 LoadedTreeSequenceAssociation bindAptxNodes = new LoadedTreeSequenceAssociation(
187 parentAvport.getAlignment().getSequencesArray(),
188 treeView.getPhylogeny());
189 bindAptxNodes.associateLeavesToSequences();
190 sequencesBoundToNodes = bindAptxNodes.getAlignmentWithNodes();
191 nodesBoundToSequences = bindAptxNodes.getNodesWithAlignment();
192 AptxInit.bindNodesToJalviewSequences(aptxFrame, parentAvport,
193 sequencesBoundToNodes, nodesBoundToSequences);
196 for (SequenceI seq : parentAvport.getAlignment().getSequencesArray())
198 if (!sequencesBoundToNodes.containsKey(seq))
200 parentAvport.hideSequence(new SequenceI[] { seq });
208 Rectangle visibleView = treeView.getVisibleRect();
210 for (PhylogenyNode node : PhylogenyMethods.getAllDescendants(treeView.getPhylogeny().getRoot())) {
211 if (!(node.getXcoord() > visibleView.getMinX()
212 && node.getXcoord() < visibleView.getMaxX()
213 && node.getYcoord() > visibleView.getMinY()
214 && node.getYcoord() < visibleView.getMaxY()))
217 .hideSequence(new SequenceI[]
218 { nodesBoundToSequences.get(node) });
229 public void mouseClicked(MouseEvent e)
231 SwingUtilities.invokeLater(new Runnable() {
235 * invokeLater so that this always runs after Forester's mouseClicked
241 final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
244 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
245 // selection if shift
248 parentAvport.setSelectionGroup(null);
251 showNodeSelectionOnAlign(node);
256 partitionTree(e.getX());
259 PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
271 public void mousePressed(final MouseEvent e)
276 public void mouseReleased(MouseEvent e)
281 public void mouseEntered(MouseEvent e)
286 public void mouseExited(MouseEvent e)
292 public void selection(final SequenceGroup seqsel,
293 final ColumnSelection colsel, final HiddenColumns hidden,
294 final SelectionSource source)
296 if (source == parentAvport) // check if source is alignment from where the
299 treeView.setFoundNodes0(
300 new HashSet<Long>(seqsel.getSequences().size()));
303 for (SequenceI selectedSequence : seqsel.getSequences())
305 PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
306 if (matchingNode != null)
308 treeView.getFoundNodes0().add(matchingNode.getId());
311 if (!matchingNode.getBranchData().isHasBranchColor())
313 // Color foundNodesColour = treeView.getTreeColorSet()
314 // .getFoundColor0();
315 // matchingNode.getBranchData()
316 // .setBranchColor(new BranchColor(foundNodesColour));
331 * Partially refactored from TreeCanvas
333 public void partitionTree(final int x)
335 Phylogeny tree = treeView.getPhylogeny();
339 // should be calculated on each partition as the tree can theoretically
340 // change in the meantime
341 PhylogenyNode furthestNode = PhylogenyMethods
342 .calculateNodeWithMaxDistanceToRoot(tree);
343 furthestNodeX = furthestNode.getXcoord();
344 rootX = tree.getRoot().getXcoord();
347 // don't bother if 0 distance tree or clicked x lies outside of tree
348 if (furthestNodeX != rootX && !(x > furthestNodeX))
350 float threshold = (x - rootX) / (furthestNodeX - rootX);
351 List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
360 public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
364 List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
366 parentAvport.setSelectionGroup(null);
367 parentAvport.getAlignment().deleteAllGroups();
368 parentAvport.clearSequenceColours();
369 if (parentAvport.getCodingComplement() != null)
371 parentAvport.getCodingComplement().setSelectionGroup(null);
372 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
373 parentAvport.getCodingComplement().clearSequenceColours();
377 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
379 return nodesAboveThreshold;
384 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
392 private List<PhylogenyNode> colourNodesAboveThreshold(
393 List<PhylogenyNode> nodeList, double threshold,
397 for (PhylogenyNode childNode : node.getDescendants())
399 childNode.getBranchData()
400 .setBranchColor(new BranchColor(Color.black));
401 float nodeCutoff = (childNode.getXcoord() - rootX)
402 / (furthestNodeX - rootX);
404 if (nodeCutoff > threshold)
406 nodeList.add(childNode);
408 Color randomColour = new Color((int) (Math.random() * 255),
409 (int) (Math.random() * 255), (int) (Math.random() * 255));
410 childNode.getBranchData()
411 .setBranchColor(new BranchColor(randomColour));
413 List<SequenceI> groupSeqs = new ArrayList<>();
414 SequenceI seq = nodesBoundToSequences.get(childNode);
418 parentAvport.setSequenceColour(seq, randomColour);
421 List<PhylogenyNode> descendantNodes = PhylogenyMethods
422 .getAllDescendants(childNode);
424 for (PhylogenyNode descNode : descendantNodes)
426 seq = nodesBoundToSequences.get(descNode);
430 parentAvport.setSequenceColour(seq, randomColour);
433 descNode.getBranchData()
434 .setBranchColor(new BranchColor(randomColour));
437 if (groupSeqs != null)
440 groupThresholdSequences(groupSeqs, randomColour);
445 colourNodesAboveThreshold(nodeList, threshold, childNode);
450 for (AlignmentPanel associatedPanel : associatedPanels) {
452 associatedPanel.updateAnnotation();
454 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
455 .getCodingComplement();
456 if (codingComplement != null)
459 ((AlignViewport) codingComplement).getAlignPanel()
468 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
471 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
472 true, true, false, 0,
473 parentAvport.getAlignment().getWidth() - 1);
475 ColourSchemeI cs = null;
476 if (parentAvport.getGlobalColourScheme() != null)
478 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
480 cs = new UserColourScheme(
481 ((UserColourScheme) parentAvport.getGlobalColourScheme())
486 cs = ColourSchemeProperty.getColourScheme(treeGroup,
487 ColourSchemeProperty.getColourName(
488 parentAvport.getGlobalColourScheme()));
492 treeGroup.setColourScheme(cs);
493 treeGroup.getGroupColourScheme().setThreshold(
494 parentAvport.getResidueShading().getThreshold(),
495 parentAvport.isIgnoreGapsConsensus());
497 treeGroup.setName("Tree Group " + nrTreeGroups);
498 treeGroup.setIdColour(groupColour);
500 for (AlignmentPanel associatedPanel : associatedPanels)
502 AlignViewportI altViewport = associatedPanel
505 if (altViewport.getGlobalColourScheme() != null
506 && altViewport.getResidueShading()
507 .conservationApplied())
509 Conservation conserv = new Conservation(treeGroup.getName(),
510 treeGroup.getSequences(null), treeGroup.getStartRes(),
511 treeGroup.getEndRes());
513 conserv.verdict(false, altViewport.getConsPercGaps());
514 treeGroup.getGroupColourScheme().setConservation(conserv);
517 altViewport.getAlignment().addGroup(treeGroup);
518 // TODO can we push all of the below into AlignViewportI?
519 final AlignViewportI codingComplement = altViewport
520 .getCodingComplement();
521 if (codingComplement != null)
523 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
524 parentAvport, codingComplement);
525 if (mappedGroup.getSequences().size() > 0)
527 codingComplement.getAlignment().addGroup(mappedGroup);
528 for (SequenceI seq : mappedGroup.getSequences())
530 codingComplement.setSequenceColour(seq, groupColour.brighter());
540 * may or may not need an extra repaint on the alignment view (check what kira
544 public void showNodeSelectionOnAlign(final PhylogenyNode node)
547 if (node.isInternal())
549 showMatchingChildSequences(node);
554 showMatchingSequence(node);
565 public void showMatchingSequence(final PhylogenyNode nodeToMatch)
567 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
568 if (matchingSequence != null)
570 long nodeId = nodeToMatch.getId();
571 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
572 treeSelectionChanged(matchingSequence);
573 parentAvport.sendSelection();
579 public void showMatchingChildSequences(final PhylogenyNode parentNode)
581 // redundancy here, Forester already iterates through tree to get all
583 List<PhylogenyNode> childNodes = PhylogenyMethods
584 .getAllDescendants(parentNode);
587 for (PhylogenyNode childNode : childNodes)
589 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
591 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
592 if (matchingSequence != null)
594 long nodeId = childNode.getId();
595 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
597 treeSelectionChanged(matchingSequence);
602 parentAvport.sendSelection();
608 * Refactored from TreeCanvas.
611 * of the node selected in the tree viewer.
614 public void treeSelectionChanged(final SequenceI sequence)
616 if (!parentAvport.isClosed()) // alignment view could be closed
618 SequenceGroup selected = parentAvport.getSelectionGroup();
620 if (selected == null)
622 selected = new SequenceGroup();
623 parentAvport.setSelectionGroup(selected);
626 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
627 selected.addOrRemove(sequence, true);
633 public void sortByTree_actionPerformed()
636 // if (treeCanvas.applyToAllViews)
638 // final ArrayList<CommandI> commands = new ArrayList<>();
639 // for (AlignmentPanel ap : PaintRefresher
640 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
642 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
644 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
648 // public void undoCommand(AlignmentI[] views)
650 // for (CommandI tsort : commands)
652 // tsort.undoCommand(views);
657 // public int getSize()
659 // return commands.size();
663 // public String getDescription()
665 // return "Tree Sort (many views)";
669 // public void doCommand(AlignmentI[] views)
672 // for (CommandI tsort : commands)
674 // tsort.doCommand(views);
678 // for (AlignmentPanel ap : PaintRefresher
679 // .getAssociatedPanels(av.getSequenceSetId()))
681 // // ensure all the alignFrames refresh their GI after adding an undo item
682 // ap.alignFrame.updateEditMenuBar();
687 // treeCanvas.ap.alignFrame
688 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
693 public CommandI sortAlignmentIn(AlignmentPanel ap)
695 // TODO: move to alignment view controller
697 AlignmentViewport viewport = ap.av;
698 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
701 AlignmentSorter.sortByTree(viewport.getAlignment(),
702 nodesBoundToSequences,
703 treeView.getPhylogeny());
705 undo = new OrderCommand("Tree Sort", oldOrder,
706 viewport.getAlignment());
708 ap.paintAlignment(true, false);
711 } catch (Exception e)
713 System.err.println(e.getMessage());
725 * @param objectToCheck
727 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
729 if (set.contains(objectToCheck))
731 set.remove(objectToCheck);
735 set.add(objectToCheck);
740 public AlignmentViewport getParentAvport()
745 public void setParentAvport(final AlignmentViewport parentAvport)
747 this.parentAvport = parentAvport;
750 public AlignmentPanel[] getAssociatedPanels()
752 return associatedPanels;
755 public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
757 this.associatedPanels = associatedPanels;