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 // aptxFrame.actionPerformed(e);
185 public void mouseClicked(MouseEvent e)
187 SwingUtilities.invokeLater(new Runnable() {
191 * invokeLater so that this always runs after Forester's mouseClicked
195 final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
198 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
199 // selection if shift
202 parentAvport.setSelectionGroup(null);
205 showNodeSelectionOnAlign(node);
210 partitionTree(e.getX());
212 PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
224 public void mousePressed(final MouseEvent e)
229 public void mouseReleased(MouseEvent e)
234 public void mouseEntered(MouseEvent e)
239 public void mouseExited(MouseEvent e)
245 public void selection(final SequenceGroup seqsel,
246 final ColumnSelection colsel, final HiddenColumns hidden,
247 final SelectionSource source)
249 if (source == parentAvport) // check if source is alignment from where the
252 treeView.setFoundNodes0(
253 new HashSet<Long>(seqsel.getSequences().size()));
256 for (SequenceI selectedSequence : seqsel.getSequences())
258 PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
259 if (matchingNode != null)
261 treeView.getFoundNodes0().add(matchingNode.getId());
264 if (!matchingNode.getBranchData().isHasBranchColor())
266 // Color foundNodesColour = treeView.getTreeColorSet()
267 // .getFoundColor0();
268 // matchingNode.getBranchData()
269 // .setBranchColor(new BranchColor(foundNodesColour));
284 * Partially refactored from TreeCanvas
286 public void partitionTree(final int x)
288 Phylogeny tree = treeView.getPhylogeny();
292 // should be calculated on each partition as the tree can theoretically
293 // change in the meantime
294 PhylogenyNode furthestNode = PhylogenyMethods
295 .calculateNodeWithMaxDistanceToRoot(tree);
296 furthestNodeX = furthestNode.getXcoord();
297 rootX = tree.getRoot().getXcoord();
299 // don't bother if 0 distance tree or clicked x lies outside of tree
300 if (furthestNodeX != rootX && !(x > furthestNodeX))
302 float threshold = (x - rootX) / (furthestNodeX - rootX);
303 List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
312 public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
316 List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
318 parentAvport.setSelectionGroup(null);
319 parentAvport.getAlignment().deleteAllGroups();
320 parentAvport.clearSequenceColours();
321 if (parentAvport.getCodingComplement() != null)
323 parentAvport.getCodingComplement().setSelectionGroup(null);
324 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
325 parentAvport.getCodingComplement().clearSequenceColours();
329 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
331 return nodesAboveThreshold;
336 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
344 private List<PhylogenyNode> colourNodesAboveThreshold(
345 List<PhylogenyNode> nodeList, double threshold,
349 for (PhylogenyNode childNode : node.getDescendants())
351 childNode.getBranchData()
352 .setBranchColor(new BranchColor(Color.black));
353 float nodeCutoff = (childNode.getXcoord() - rootX)
354 / (furthestNodeX - rootX);
356 if (nodeCutoff > threshold)
358 nodeList.add(childNode);
360 Color randomColour = new Color((int) (Math.random() * 255),
361 (int) (Math.random() * 255), (int) (Math.random() * 255));
362 childNode.getBranchData()
363 .setBranchColor(new BranchColor(randomColour));
365 List<SequenceI> groupSeqs = new ArrayList<>();
366 SequenceI seq = nodesBoundToSequences.get(childNode);
370 parentAvport.setSequenceColour(seq, randomColour);
373 List<PhylogenyNode> descendantNodes = PhylogenyMethods
374 .getAllDescendants(childNode);
376 for (PhylogenyNode descNode : descendantNodes)
378 seq = nodesBoundToSequences.get(descNode);
382 parentAvport.setSequenceColour(seq, randomColour);
385 descNode.getBranchData()
386 .setBranchColor(new BranchColor(randomColour));
389 if (groupSeqs != null)
392 groupThresholdSequences(groupSeqs, randomColour);
397 colourNodesAboveThreshold(nodeList, threshold, childNode);
402 for (AlignmentPanel associatedPanel : associatedPanels) {
404 associatedPanel.updateAnnotation();
406 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
407 .getCodingComplement();
408 if (codingComplement != null)
411 ((AlignViewport) codingComplement).getAlignPanel()
420 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
423 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
424 true, true, false, 0,
425 parentAvport.getAlignment().getWidth() - 1);
427 ColourSchemeI cs = null;
428 if (parentAvport.getGlobalColourScheme() != null)
430 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
432 cs = new UserColourScheme(
433 ((UserColourScheme) parentAvport.getGlobalColourScheme())
438 cs = ColourSchemeProperty.getColourScheme(treeGroup,
439 ColourSchemeProperty.getColourName(
440 parentAvport.getGlobalColourScheme()));
444 treeGroup.setColourScheme(cs);
445 treeGroup.getGroupColourScheme().setThreshold(
446 parentAvport.getResidueShading().getThreshold(),
447 parentAvport.isIgnoreGapsConsensus());
449 treeGroup.setName("Tree Group " + nrTreeGroups);
450 treeGroup.setIdColour(groupColour);
452 for (AlignmentPanel associatedPanel : associatedPanels)
454 AlignViewportI altViewport = associatedPanel
457 if (altViewport.getGlobalColourScheme() != null
458 && altViewport.getResidueShading()
459 .conservationApplied())
461 Conservation conserv = new Conservation(treeGroup.getName(),
462 treeGroup.getSequences(null), treeGroup.getStartRes(),
463 treeGroup.getEndRes());
465 conserv.verdict(false, altViewport.getConsPercGaps());
466 treeGroup.getGroupColourScheme().setConservation(conserv);
469 altViewport.getAlignment().addGroup(treeGroup);
470 // TODO can we push all of the below into AlignViewportI?
471 final AlignViewportI codingComplement = altViewport
472 .getCodingComplement();
473 if (codingComplement != null)
475 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
476 parentAvport, codingComplement);
477 if (mappedGroup.getSequences().size() > 0)
479 codingComplement.getAlignment().addGroup(mappedGroup);
480 for (SequenceI seq : mappedGroup.getSequences())
482 codingComplement.setSequenceColour(seq, groupColour.brighter());
492 * may or may not need an extra repaint on the alignment view (check what kira
496 public void showNodeSelectionOnAlign(final PhylogenyNode node)
499 if (node.isInternal())
501 showMatchingChildSequences(node);
506 showMatchingSequence(node);
517 public void showMatchingSequence(final PhylogenyNode nodeToMatch)
519 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
520 if (matchingSequence != null)
522 long nodeId = nodeToMatch.getId();
523 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
524 treeSelectionChanged(matchingSequence);
525 parentAvport.sendSelection();
531 public void showMatchingChildSequences(final PhylogenyNode parentNode)
533 // redundancy here, Forester already iterates through tree to get all
535 List<PhylogenyNode> childNodes = PhylogenyMethods
536 .getAllDescendants(parentNode);
539 for (PhylogenyNode childNode : childNodes)
541 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
543 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
544 if (matchingSequence != null)
546 long nodeId = childNode.getId();
547 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
549 treeSelectionChanged(matchingSequence);
554 parentAvport.sendSelection();
560 * Refactored from TreeCanvas.
563 * of the node selected in the tree viewer.
566 public void treeSelectionChanged(final SequenceI sequence)
568 if (!parentAvport.isClosed()) // alignment view could be closed
570 SequenceGroup selected = parentAvport.getSelectionGroup();
572 if (selected == null)
574 selected = new SequenceGroup();
575 parentAvport.setSelectionGroup(selected);
578 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
579 selected.addOrRemove(sequence, true);
585 public void sortByTree_actionPerformed()// modify for Aptx
588 // if (treeCanvas.applyToAllViews)
590 // final ArrayList<CommandI> commands = new ArrayList<>();
591 // for (AlignmentPanel ap : PaintRefresher
592 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
594 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
596 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
600 // public void undoCommand(AlignmentI[] views)
602 // for (CommandI tsort : commands)
604 // tsort.undoCommand(views);
609 // public int getSize()
611 // return commands.size();
615 // public String getDescription()
617 // return "Tree Sort (many views)";
621 // public void doCommand(AlignmentI[] views)
624 // for (CommandI tsort : commands)
626 // tsort.doCommand(views);
630 // for (AlignmentPanel ap : PaintRefresher
631 // .getAssociatedPanels(av.getSequenceSetId()))
633 // // ensure all the alignFrames refresh their GI after adding an undo item
634 // ap.alignFrame.updateEditMenuBar();
639 // treeCanvas.ap.alignFrame
640 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
645 public CommandI sortAlignmentIn(AlignmentPanel ap)
647 // TODO: move to alignment view controller
649 AlignmentViewport viewport = ap.av;
650 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
653 AlignmentSorter.sortByTree(viewport.getAlignment(),
654 nodesBoundToSequences,
655 treeView.getPhylogeny());
657 undo = new OrderCommand("Tree Sort", oldOrder,
658 viewport.getAlignment());
660 ap.paintAlignment(true, false);
663 } catch (Exception e)
665 System.err.println(e.getMessage());
677 * @param objectToCheck
679 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
681 if (set.contains(objectToCheck))
683 set.remove(objectToCheck);
687 set.add(objectToCheck);
692 public AlignmentViewport getParentAvport()
697 public void setParentAvport(final AlignmentViewport parentAvport)
699 this.parentAvport = parentAvport;
702 public AlignmentPanel[] getAssociatedPanels()
704 return associatedPanels;
707 public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
709 this.associatedPanels = associatedPanels;