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 if (furthestNodeX != rootX && !(x < rootX || x > furthestNodeX)) // don't
303 // clicked x lies outside
307 float threshold = (x - rootX) / (furthestNodeX - rootX);
308 List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
317 public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
321 List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
323 parentAvport.setSelectionGroup(null);
324 parentAvport.getAlignment().deleteAllGroups();
325 parentAvport.clearSequenceColours();
326 if (parentAvport.getCodingComplement() != null)
328 parentAvport.getCodingComplement().setSelectionGroup(null);
329 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
330 parentAvport.getCodingComplement().clearSequenceColours();
334 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
336 return nodesAboveThreshold;
341 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
349 private List<PhylogenyNode> colourNodesAboveThreshold(
350 List<PhylogenyNode> nodeList, double threshold,
354 for (PhylogenyNode childNode : node.getDescendants())
356 childNode.getBranchData()
357 .setBranchColor(new BranchColor(Color.black));
358 float nodeCutoff = (childNode.getXcoord() - rootX)
359 / (furthestNodeX - rootX);
361 if (nodeCutoff > threshold)
363 nodeList.add(childNode);
365 Color randomColour = new Color((int) (Math.random() * 255),
366 (int) (Math.random() * 255), (int) (Math.random() * 255));
367 childNode.getBranchData()
368 .setBranchColor(new BranchColor(randomColour));
370 List<SequenceI> groupSeqs = new ArrayList<>();
371 SequenceI seq = nodesBoundToSequences.get(childNode);
375 parentAvport.setSequenceColour(seq, randomColour);
378 List<PhylogenyNode> descendantNodes = PhylogenyMethods
379 .getAllDescendants(childNode);
381 for (PhylogenyNode descNode : descendantNodes)
383 seq = nodesBoundToSequences.get(descNode);
387 parentAvport.setSequenceColour(seq, randomColour);
390 descNode.getBranchData()
391 .setBranchColor(new BranchColor(randomColour));
394 if (groupSeqs != null)
397 groupThresholdSequences(groupSeqs, randomColour);
402 colourNodesAboveThreshold(nodeList, threshold, childNode);
407 for (AlignmentPanel associatedPanel : associatedPanels) {
409 associatedPanel.updateAnnotation();
411 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
412 .getCodingComplement();
413 if (codingComplement != null)
416 ((AlignViewport) codingComplement).getAlignPanel()
425 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
428 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
429 true, true, false, 0,
430 parentAvport.getAlignment().getWidth() - 1);
432 ColourSchemeI cs = null;
433 if (parentAvport.getGlobalColourScheme() != null)
435 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
437 cs = new UserColourScheme(
438 ((UserColourScheme) parentAvport.getGlobalColourScheme())
443 cs = ColourSchemeProperty.getColourScheme(treeGroup,
444 ColourSchemeProperty.getColourName(
445 parentAvport.getGlobalColourScheme()));
449 treeGroup.setColourScheme(cs);
450 treeGroup.getGroupColourScheme().setThreshold(
451 parentAvport.getResidueShading().getThreshold(),
452 parentAvport.isIgnoreGapsConsensus());
454 treeGroup.setName("Tree Group " + nrTreeGroups);
455 treeGroup.setIdColour(groupColour);
457 for (AlignmentPanel associatedPanel : associatedPanels)
459 AlignViewportI altViewport = associatedPanel
462 if (altViewport.getGlobalColourScheme() != null
463 && altViewport.getResidueShading()
464 .conservationApplied())
466 Conservation conserv = new Conservation(treeGroup.getName(),
467 treeGroup.getSequences(null), treeGroup.getStartRes(),
468 treeGroup.getEndRes());
470 conserv.verdict(false, altViewport.getConsPercGaps());
471 treeGroup.getGroupColourScheme().setConservation(conserv);
474 altViewport.getAlignment().addGroup(treeGroup);
475 // TODO can we push all of the below into AlignViewportI?
476 final AlignViewportI codingComplement = altViewport
477 .getCodingComplement();
478 if (codingComplement != null)
480 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
481 parentAvport, codingComplement);
482 if (mappedGroup.getSequences().size() > 0)
484 codingComplement.getAlignment().addGroup(mappedGroup);
485 for (SequenceI seq : mappedGroup.getSequences())
487 codingComplement.setSequenceColour(seq, groupColour.brighter());
497 * may or may not need an extra repaint on the alignment view (check what kira
501 public void showNodeSelectionOnAlign(final PhylogenyNode node)
504 if (node.isInternal())
506 showMatchingChildSequences(node);
511 showMatchingSequence(node);
522 public void showMatchingSequence(final PhylogenyNode nodeToMatch)
524 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
525 if (matchingSequence != null)
527 long nodeId = nodeToMatch.getId();
528 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
529 treeSelectionChanged(matchingSequence);
530 parentAvport.sendSelection();
536 public void showMatchingChildSequences(final PhylogenyNode parentNode)
538 // redundancy here, Forester already iterates through tree to get all
540 List<PhylogenyNode> childNodes = PhylogenyMethods
541 .getAllDescendants(parentNode);
544 for (PhylogenyNode childNode : childNodes)
546 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
548 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
549 if (matchingSequence != null)
551 long nodeId = childNode.getId();
552 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
554 treeSelectionChanged(matchingSequence);
559 parentAvport.sendSelection();
565 * Refactored from TreeCanvas.
568 * of the node selected in the tree viewer.
571 public void treeSelectionChanged(final SequenceI sequence)
573 if (!parentAvport.isClosed()) // alignment view could be closed
575 SequenceGroup selected = parentAvport.getSelectionGroup();
577 if (selected == null)
579 selected = new SequenceGroup();
580 parentAvport.setSelectionGroup(selected);
583 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
584 selected.addOrRemove(sequence, true);
590 public void sortByTree_actionPerformed()// modify for Aptx
593 // if (treeCanvas.applyToAllViews)
595 // final ArrayList<CommandI> commands = new ArrayList<>();
596 // for (AlignmentPanel ap : PaintRefresher
597 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
599 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
601 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
605 // public void undoCommand(AlignmentI[] views)
607 // for (CommandI tsort : commands)
609 // tsort.undoCommand(views);
614 // public int getSize()
616 // return commands.size();
620 // public String getDescription()
622 // return "Tree Sort (many views)";
626 // public void doCommand(AlignmentI[] views)
629 // for (CommandI tsort : commands)
631 // tsort.doCommand(views);
635 // for (AlignmentPanel ap : PaintRefresher
636 // .getAssociatedPanels(av.getSequenceSetId()))
638 // // ensure all the alignFrames refresh their GI after adding an undo item
639 // ap.alignFrame.updateEditMenuBar();
644 // treeCanvas.ap.alignFrame
645 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
650 public CommandI sortAlignmentIn(AlignmentPanel ap)
652 // TODO: move to alignment view controller
654 AlignmentViewport viewport = ap.av;
655 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
658 AlignmentSorter.sortByTree(viewport.getAlignment(),
659 nodesBoundToSequences,
660 treeView.getPhylogeny());
662 undo = new OrderCommand("Tree Sort", oldOrder,
663 viewport.getAlignment());
665 ap.paintAlignment(true, false);
668 } catch (Exception e)
670 System.err.println(e.getMessage());
682 * @param objectToCheck
684 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
686 if (set.contains(objectToCheck))
688 set.remove(objectToCheck);
692 set.add(objectToCheck);
697 public AlignmentViewport getParentAvport()
702 public void setParentAvport(final AlignmentViewport parentAvport)
704 this.parentAvport = parentAvport;
707 public AlignmentPanel[] getAssociatedPanels()
709 return associatedPanels;
712 public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
714 this.associatedPanels = associatedPanels;