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.event.ActionEvent;
29 import java.awt.event.InputEvent;
30 import java.awt.event.MouseEvent;
31 import java.util.ArrayList;
32 import java.util.HashSet;
33 import java.util.List;
37 import javax.swing.JTabbedPane;
38 import javax.swing.SwingUtilities;
39 import javax.swing.event.ChangeEvent;
40 import javax.swing.event.ChangeListener;
41 import javax.swing.event.InternalFrameAdapter;
42 import javax.swing.event.InternalFrameEvent;
44 import org.forester.archaeopteryx.MainFrame;
45 import org.forester.phylogeny.Phylogeny;
46 import org.forester.phylogeny.PhylogenyMethods;
47 import org.forester.phylogeny.PhylogenyNode;
48 import org.forester.phylogeny.data.BranchColor;
51 * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
52 * it originates from, meaning that selecting sequences in the tree viewer also
53 * selects them in the alignment view and vice versa.
55 * @author kjvanderheide
58 public final class JalviewBinding
59 implements ExternalTreeViewerBindingI<PhylogenyNode>
61 private final MainFrame aptxFrame;
63 private org.forester.archaeopteryx.TreePanel treeView;
65 private AlignmentViewport parentAvport;
67 private final JTabbedPane treeTabs;
69 private final StructureSelectionManager ssm;
71 private AlignmentPanel[] associatedPanels;
73 private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
75 private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
79 private float furthestNodeX;
81 private int nrTreeGroups = 0;
83 private boolean applyToAllViews = false;
87 * @param archaeopteryx
89 * @param jalviewAlignmentViewport
90 * alignment viewport from which the tree was calculated.
92 * @param alignMappedToNodes
93 * map with sequences used to calculate the tree and matching tree
94 * nodes as key, value pair respectively.
96 * @param nodesMappedToAlign
97 * map with tree nodes and matching sequences used to calculate the
98 * tree as key, value pair respectively.
100 public JalviewBinding(final MainFrame archaeopteryx,
101 final AlignmentViewport jalviewAlignmentViewport,
102 final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
103 final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
106 if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1)
108 JvOptionPane.showMessageDialog(Desktop.desktop,
109 MessageManager.getString("label.tabs_detected_archaeopteryx"),
110 MessageManager.getString("label.problem_reading_tree_file"),
111 JvOptionPane.WARNING_MESSAGE);
115 // deal with/prohibit null values here as that will cause problems
116 aptxFrame = archaeopteryx;
117 parentAvport = jalviewAlignmentViewport;
118 sequencesBoundToNodes = alignMappedToNodes;
119 nodesBoundToSequences = nodesMappedToAlign;
121 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
122 treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
123 ssm = parentAvport.getStructureSelectionManager();
125 ssm.addSelectionListener(this);
126 treeView.addMouseListener(this);
128 PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
129 associatedPanels = PaintRefresher
130 .getAssociatedPanels(parentAvport.getSequenceSetId());
132 aptxFrame.addInternalFrameListener(new InternalFrameAdapter()
136 public void internalFrameClosed(InternalFrameEvent e)
138 AptxInit.getAllAptxFrames().remove(aptxFrame);
139 ssm.removeSelectionListener(JalviewBinding.this);
144 treeTabs.addChangeListener(new ChangeListener()
148 public void stateChanged(ChangeEvent e)
151 SwingUtilities.invokeLater(new Runnable()
156 * Resend the selection to the tree view when tabs get switched, this
157 * has to be buried in invokeLater as Forester first resets the tree
158 * view on switching tabs, without invokeLater this would get called
159 * before Forester resets which would nullify the selection.
163 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
164 parentAvport.sendSelection();
165 // PaintRefresher.Refresh(treeView,
166 // parentAvport.getSequenceSetId());
178 public void actionPerformed(ActionEvent e)
180 if (treeView.isCurrentTreeIsSubtree())
182 LoadedTreeSequenceAssociation bindAptxNodes = new LoadedTreeSequenceAssociation(
183 parentAvport.getAlignment().getSequencesArray(),
184 treeView.getPhylogeny());
185 bindAptxNodes.associateLeavesToSequences();
186 sequencesBoundToNodes = bindAptxNodes.getAlignmentWithNodes();
187 nodesBoundToSequences = bindAptxNodes.getNodesWithAlignment();
188 AptxInit.bindNodesToJalviewSequences(aptxFrame, parentAvport,
189 sequencesBoundToNodes, nodesBoundToSequences);
192 for (SequenceI seq : parentAvport.getAlignment().getSequencesArray())
194 if (!sequencesBoundToNodes.containsKey(seq))
196 parentAvport.hideSequence(new SequenceI[] { seq });
206 public void mouseClicked(MouseEvent e)
208 SwingUtilities.invokeLater(new Runnable() {
212 * invokeLater so that this always runs after Forester's mouseClicked
218 final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
221 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
222 // selection if shift
225 parentAvport.setSelectionGroup(null);
228 showNodeSelectionOnAlign(node);
233 partitionTree(e.getX());
236 PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
248 public void mousePressed(final MouseEvent e)
253 public void mouseReleased(MouseEvent e)
258 public void mouseEntered(MouseEvent e)
263 public void mouseExited(MouseEvent e)
269 public void selection(final SequenceGroup seqsel,
270 final ColumnSelection colsel, final HiddenColumns hidden,
271 final SelectionSource source)
273 if (source == parentAvport) // check if source is alignment from where the
276 treeView.setFoundNodes0(
277 new HashSet<Long>(seqsel.getSequences().size()));
280 for (SequenceI selectedSequence : seqsel.getSequences())
282 PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
283 if (matchingNode != null)
285 treeView.getFoundNodes0().add(matchingNode.getId());
288 if (!matchingNode.getBranchData().isHasBranchColor())
290 // Color foundNodesColour = treeView.getTreeColorSet()
291 // .getFoundColor0();
292 // matchingNode.getBranchData()
293 // .setBranchColor(new BranchColor(foundNodesColour));
308 * Partially refactored from TreeCanvas
310 public void partitionTree(final int x)
312 Phylogeny tree = treeView.getPhylogeny();
316 // should be calculated on each partition as the tree can theoretically
317 // change in the meantime
318 PhylogenyNode furthestNode = PhylogenyMethods
319 .calculateNodeWithMaxDistanceToRoot(tree);
320 furthestNodeX = furthestNode.getXcoord();
321 rootX = tree.getRoot().getXcoord();
324 // don't bother if 0 distance tree or clicked x lies outside of tree
325 if (furthestNodeX != rootX && !(x > furthestNodeX))
327 float threshold = (x - rootX) / (furthestNodeX - rootX);
328 List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
337 public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
341 List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
343 parentAvport.setSelectionGroup(null);
344 parentAvport.getAlignment().deleteAllGroups();
345 parentAvport.clearSequenceColours();
346 if (parentAvport.getCodingComplement() != null)
348 parentAvport.getCodingComplement().setSelectionGroup(null);
349 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
350 parentAvport.getCodingComplement().clearSequenceColours();
354 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
356 return nodesAboveThreshold;
361 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
369 private List<PhylogenyNode> colourNodesAboveThreshold(
370 List<PhylogenyNode> nodeList, double threshold,
374 for (PhylogenyNode childNode : node.getDescendants())
376 childNode.getBranchData()
377 .setBranchColor(new BranchColor(Color.black));
378 float nodeCutoff = (childNode.getXcoord() - rootX)
379 / (furthestNodeX - rootX);
381 if (nodeCutoff > threshold)
383 nodeList.add(childNode);
385 Color randomColour = new Color((int) (Math.random() * 255),
386 (int) (Math.random() * 255), (int) (Math.random() * 255));
387 childNode.getBranchData()
388 .setBranchColor(new BranchColor(randomColour));
390 List<SequenceI> groupSeqs = new ArrayList<>();
391 SequenceI seq = nodesBoundToSequences.get(childNode);
395 parentAvport.setSequenceColour(seq, randomColour);
398 List<PhylogenyNode> descendantNodes = PhylogenyMethods
399 .getAllDescendants(childNode);
401 for (PhylogenyNode descNode : descendantNodes)
403 seq = nodesBoundToSequences.get(descNode);
407 parentAvport.setSequenceColour(seq, randomColour);
410 descNode.getBranchData()
411 .setBranchColor(new BranchColor(randomColour));
414 if (groupSeqs != null)
417 groupThresholdSequences(groupSeqs, randomColour);
422 colourNodesAboveThreshold(nodeList, threshold, childNode);
427 for (AlignmentPanel associatedPanel : associatedPanels) {
429 associatedPanel.updateAnnotation();
431 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
432 .getCodingComplement();
433 if (codingComplement != null)
436 ((AlignViewport) codingComplement).getAlignPanel()
445 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
448 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
449 true, true, false, 0,
450 parentAvport.getAlignment().getWidth() - 1);
452 ColourSchemeI cs = null;
453 if (parentAvport.getGlobalColourScheme() != null)
455 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
457 cs = new UserColourScheme(
458 ((UserColourScheme) parentAvport.getGlobalColourScheme())
463 cs = ColourSchemeProperty.getColourScheme(treeGroup,
464 ColourSchemeProperty.getColourName(
465 parentAvport.getGlobalColourScheme()));
469 treeGroup.setColourScheme(cs);
470 treeGroup.getGroupColourScheme().setThreshold(
471 parentAvport.getResidueShading().getThreshold(),
472 parentAvport.isIgnoreGapsConsensus());
474 treeGroup.setName("Tree Group " + nrTreeGroups);
475 treeGroup.setIdColour(groupColour);
477 for (AlignmentPanel associatedPanel : associatedPanels)
479 AlignViewportI altViewport = associatedPanel
482 if (altViewport.getGlobalColourScheme() != null
483 && altViewport.getResidueShading()
484 .conservationApplied())
486 Conservation conserv = new Conservation(treeGroup.getName(),
487 treeGroup.getSequences(null), treeGroup.getStartRes(),
488 treeGroup.getEndRes());
490 conserv.verdict(false, altViewport.getConsPercGaps());
491 treeGroup.getGroupColourScheme().setConservation(conserv);
494 altViewport.getAlignment().addGroup(treeGroup);
495 // TODO can we push all of the below into AlignViewportI?
496 final AlignViewportI codingComplement = altViewport
497 .getCodingComplement();
498 if (codingComplement != null)
500 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
501 parentAvport, codingComplement);
502 if (mappedGroup.getSequences().size() > 0)
504 codingComplement.getAlignment().addGroup(mappedGroup);
505 for (SequenceI seq : mappedGroup.getSequences())
507 codingComplement.setSequenceColour(seq, groupColour.brighter());
517 * may or may not need an extra repaint on the alignment view (check what kira
521 public void showNodeSelectionOnAlign(final PhylogenyNode node)
524 if (node.isInternal())
526 showMatchingChildSequences(node);
531 showMatchingSequence(node);
542 public void showMatchingSequence(final PhylogenyNode nodeToMatch)
544 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
545 if (matchingSequence != null)
547 long nodeId = nodeToMatch.getId();
548 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
549 treeSelectionChanged(matchingSequence);
550 parentAvport.sendSelection();
556 public void showMatchingChildSequences(final PhylogenyNode parentNode)
558 // redundancy here, Forester already iterates through tree to get all
560 List<PhylogenyNode> childNodes = PhylogenyMethods
561 .getAllDescendants(parentNode);
564 for (PhylogenyNode childNode : childNodes)
566 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
568 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
569 if (matchingSequence != null)
571 long nodeId = childNode.getId();
572 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
574 treeSelectionChanged(matchingSequence);
579 parentAvport.sendSelection();
585 * Refactored from TreeCanvas.
588 * of the node selected in the tree viewer.
591 public void treeSelectionChanged(final SequenceI sequence)
593 if (!parentAvport.isClosed()) // alignment view could be closed
595 SequenceGroup selected = parentAvport.getSelectionGroup();
597 if (selected == null)
599 selected = new SequenceGroup();
600 parentAvport.setSelectionGroup(selected);
603 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
604 selected.addOrRemove(sequence, true);
610 public void sortByTree_actionPerformed()
613 // if (treeCanvas.applyToAllViews)
615 // final ArrayList<CommandI> commands = new ArrayList<>();
616 // for (AlignmentPanel ap : PaintRefresher
617 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
619 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
621 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
625 // public void undoCommand(AlignmentI[] views)
627 // for (CommandI tsort : commands)
629 // tsort.undoCommand(views);
634 // public int getSize()
636 // return commands.size();
640 // public String getDescription()
642 // return "Tree Sort (many views)";
646 // public void doCommand(AlignmentI[] views)
649 // for (CommandI tsort : commands)
651 // tsort.doCommand(views);
655 // for (AlignmentPanel ap : PaintRefresher
656 // .getAssociatedPanels(av.getSequenceSetId()))
658 // // ensure all the alignFrames refresh their GI after adding an undo item
659 // ap.alignFrame.updateEditMenuBar();
664 // treeCanvas.ap.alignFrame
665 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
670 public CommandI sortAlignmentIn(AlignmentPanel ap)
672 // TODO: move to alignment view controller
674 AlignmentViewport viewport = ap.av;
675 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
678 AlignmentSorter.sortByTree(viewport.getAlignment(),
679 nodesBoundToSequences,
680 treeView.getPhylogeny());
682 undo = new OrderCommand("Tree Sort", oldOrder,
683 viewport.getAlignment());
685 ap.paintAlignment(true, false);
688 } catch (Exception e)
690 System.err.println(e.getMessage());
702 * @param objectToCheck
704 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
706 if (set.contains(objectToCheck))
708 set.remove(objectToCheck);
712 set.add(objectToCheck);
717 public AlignmentViewport getParentAvport()
722 public void setParentAvport(final AlignmentViewport parentAvport)
724 this.parentAvport = parentAvport;
727 public AlignmentPanel[] getAssociatedPanels()
729 return associatedPanels;
732 public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
734 this.associatedPanels = associatedPanels;