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.LoadedTreeSequenceAssociation;
13 import jalview.ext.treeviewer.TreeFrameI;
14 import jalview.ext.treeviewer.TreeI;
15 import jalview.ext.treeviewer.TreeNodeI;
16 import jalview.ext.treeviewer.TreePanelI;
17 import jalview.ext.treeviewer.TreeViewerBindingI;
18 import jalview.ext.treeviewer.TreeViewerUtils;
19 import jalview.gui.AlignViewport;
20 import jalview.gui.AlignmentPanel;
21 import jalview.gui.Desktop;
22 import jalview.gui.JvOptionPane;
23 import jalview.gui.PaintRefresher;
24 import jalview.schemes.ColourSchemeI;
25 import jalview.schemes.ColourSchemeProperty;
26 import jalview.schemes.UserColourScheme;
27 import jalview.structure.SelectionSource;
28 import jalview.structure.StructureSelectionManager;
29 import jalview.util.MappingUtils;
30 import jalview.util.MessageManager;
31 import jalview.viewmodel.AlignmentViewport;
33 import java.awt.Color;
34 import java.awt.Rectangle;
35 import java.awt.event.ActionEvent;
36 import java.awt.event.InputEvent;
37 import java.awt.event.MouseEvent;
38 import java.util.ArrayList;
39 import java.util.HashSet;
40 import java.util.List;
44 import javax.swing.SwingUtilities;
45 import javax.swing.event.InternalFrameAdapter;
46 import javax.swing.event.InternalFrameEvent;
49 * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
50 * it originates from, meaning that selecting sequences in the tree viewer also
51 * selects them in the alignment view and vice versa.
53 * @author kjvanderheide
56 public final class JalviewBinding
57 implements TreeViewerBindingI
59 private final TreeFrameI aptxFrame;
61 private TreePanelI treeView;
63 private AlignmentViewport parentAvport;
65 private final StructureSelectionManager ssm;
67 private AlignmentPanel[] associatedPanels;
69 private Map<SequenceI, TreeNodeI> sequencesBoundToNodes;
71 private Map<TreeNodeI, SequenceI> nodesBoundToSequences;
75 private float furthestNodeX;
77 private int nrTreeGroups = 0;
79 private boolean applyToAllViews = false;
83 * @param archaeopteryx
85 * @param jalviewAlignmentViewport
86 * alignment viewport from which the tree was calculated.
88 * @param alignMappedToNodes
89 * map with sequences used to calculate the tree and matching tree
90 * nodes as key, value pair respectively.
92 * @param nodesMappedToAlign
93 * map with tree nodes and matching sequences used to calculate the
94 * tree as key, value pair respectively.
96 public JalviewBinding(final TreeFrameI archaeopteryx,
97 final AlignmentViewport jalviewAlignmentViewport,
98 final Map<SequenceI, TreeNodeI> alignMappedToNodes,
99 final Map<TreeNodeI, SequenceI> nodesMappedToAlign)
102 if (archaeopteryx.getNumberOfTrees() > 1)
104 JvOptionPane.showMessageDialog(Desktop.desktop,
105 MessageManager.getString("label.tabs_detected_archaeopteryx"),
106 MessageManager.getString("label.problem_reading_tree_file"),
107 JvOptionPane.WARNING_MESSAGE);
111 // deal with/prohibit null values here as that will cause problems
112 aptxFrame = archaeopteryx;
113 parentAvport = jalviewAlignmentViewport;
114 sequencesBoundToNodes = alignMappedToNodes;
115 nodesBoundToSequences = nodesMappedToAlign;
117 treeView = archaeopteryx.getTreePanel();
118 ssm = parentAvport.getStructureSelectionManager();
120 ssm.addSelectionListener(this);
121 treeView.addMouseListener(this);
122 treeView.registerWithPaintRefresher(
123 parentAvport.getSequenceSetId());
124 associatedPanels = PaintRefresher
125 .getAssociatedPanels(parentAvport.getSequenceSetId());
127 aptxFrame.addFrameListener(new InternalFrameAdapter()
131 public void internalFrameClosed(InternalFrameEvent e)
133 TreeViewerUtils.getActiveTreeViews().remove(aptxFrame);
134 ssm.removeSelectionListener(JalviewBinding.this);
139 // treeTabs.addChangeListener(new ChangeListener()
143 // public void stateChanged(ChangeEvent e)
146 // SwingUtilities.invokeLater(new Runnable()
151 // * Resend the selection to the tree view when tabs get switched, this
152 // * has to be buried in invokeLater as Forester first resets the tree
153 // * view on switching tabs, without invokeLater this would get called
154 // * before Forester resets which would nullify the selection.
158 // treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
159 // parentAvport.sendSelection();
160 // // PaintRefresher.Refresh(treeView,
161 // // parentAvport.getSequenceSetId());
173 public void actionPerformed(ActionEvent e)
175 // reset hidden sequences first
176 parentAvport.showAllHiddenSeqs();
178 if (treeView.showingSubTree())
180 LoadedTreeSequenceAssociation bindAptxNodes = new LoadedTreeSequenceAssociation(
181 parentAvport.getAlignment().getSequencesArray(),
183 bindAptxNodes.associateLeavesToSequences();
184 sequencesBoundToNodes = bindAptxNodes.getAlignmentWithNodes();
185 nodesBoundToSequences = bindAptxNodes.getNodesWithAlignment();
186 TreeViewerUtils.associateNodesWithJalviewSequences(aptxFrame, parentAvport,
187 sequencesBoundToNodes, nodesBoundToSequences);
190 for (SequenceI seq : parentAvport.getAlignment().getSequencesArray())
192 if (!sequencesBoundToNodes.containsKey(seq))
194 parentAvport.hideSequence(new SequenceI[] { seq });
202 Rectangle visibleView = treeView.getVisibleArea();
204 for (TreeNodeI node : treeView.getTree().getRoot()
205 .getAllDescendants())
207 if (!(node.getXcoord() > visibleView.getMinX()
208 && node.getXcoord() < visibleView.getMaxX()
209 && node.getYcoord() > visibleView.getMinY()
210 && node.getYcoord() < visibleView.getMaxY()))
213 .hideSequence(new SequenceI[]
214 { nodesBoundToSequences.get(node) });
225 public void mouseClicked(MouseEvent e)
227 SwingUtilities.invokeLater(new Runnable() {
231 * invokeLater so that this always runs after Forester's mouseClicked
235 final TreeNodeI node = treeView.findNode(e.getX(),
239 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
240 // selection if shift
243 parentAvport.setSelectionGroup(null);
246 showNodeSelectionOnAlign(node);
251 partitionTree(e.getX());
253 treeView.notifyPaintRefresher(parentAvport.getSequenceSetId(),
266 public void mousePressed(final MouseEvent e)
271 public void mouseReleased(MouseEvent e)
276 public void mouseEntered(MouseEvent e)
281 public void mouseExited(MouseEvent e)
287 public void selection(final SequenceGroup seqsel,
288 final ColumnSelection colsel, final HiddenColumns hidden,
289 final SelectionSource source)
291 if (source == parentAvport) // check if source is alignment from where the
294 treeView.setMatchingNodes(
295 new HashSet<Long>(seqsel.getSequences().size()));
298 for (SequenceI selectedSequence : seqsel.getSequences())
300 TreeNodeI matchingNode = sequencesBoundToNodes
301 .get(selectedSequence);
302 if (matchingNode != null)
304 treeView.getMatchingNodes().add(matchingNode.getId());
307 // if (!matchingNode.getBranchData().isHasBranchColor())
309 // // Color foundNodesColour = treeView.getTreeColorSet()
310 // // .getFoundColor0();
311 // // matchingNode.getBranchData()
312 // // .setBranchColor(new BranchColor(foundNodesColour));
327 * Partially refactored from TreeCanvas
329 public void partitionTree(final int x)
331 TreeI tree = treeView.getTree();
335 // should be calculated on each partition as the tree can theoretically
336 // change in the meantime
337 TreeNodeI furthestNode = tree.getFurthestNode();
338 furthestNodeX = furthestNode.getXcoord();
339 rootX = tree.getRoot().getXcoord();
341 // don't bother if 0 distance tree or clicked x lies outside of tree
342 if (furthestNodeX != rootX && !(x > furthestNodeX))
344 float threshold = (x - rootX) / (furthestNodeX - rootX);
345 List<TreeNodeI> foundNodes = getNodesAboveThreshold(
352 // clear previous colours?
359 public List<TreeNodeI> getNodesAboveThreshold(double threshold,
363 List<TreeNodeI> nodesAboveThreshold = new ArrayList<>();
365 parentAvport.setSelectionGroup(null);
366 parentAvport.getAlignment().deleteAllGroups();
367 parentAvport.clearSequenceColours();
368 if (parentAvport.getCodingComplement() != null)
370 parentAvport.getCodingComplement().setSelectionGroup(null);
371 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
372 parentAvport.getCodingComplement().clearSequenceColours();
376 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
378 return nodesAboveThreshold;
383 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
391 private List<TreeNodeI> colourNodesAboveThreshold(
392 List<TreeNodeI> nodeList, double threshold,
396 for (TreeNodeI childNode : node.getDirectChildren())
398 childNode.setBranchColor(Color.black);
399 float nodeCutoff = (childNode.getXcoord() - rootX)
400 / (furthestNodeX - rootX);
402 if (nodeCutoff > threshold)
404 nodeList.add(childNode);
406 Color randomColour = new Color((int) (Math.random() * 255),
407 (int) (Math.random() * 255), (int) (Math.random() * 255));
408 childNode.setBranchColor(randomColour);
410 List<SequenceI> groupSeqs = new ArrayList<>();
411 SequenceI seq = nodesBoundToSequences.get(childNode);
415 parentAvport.setSequenceColour(seq, randomColour);
418 List<TreeNodeI> descendantNodes = childNode
419 .getAllDescendants();
421 for (TreeNodeI descNode : descendantNodes)
423 seq = nodesBoundToSequences.get(descNode);
427 parentAvport.setSequenceColour(seq, randomColour);
430 descNode.setBranchColor(randomColour);
433 if (groupSeqs != null)
436 groupThresholdSequences(groupSeqs, randomColour);
441 colourNodesAboveThreshold(nodeList, threshold, childNode);
446 for (AlignmentPanel associatedPanel : associatedPanels) {
448 associatedPanel.updateAnnotation();
450 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
451 .getCodingComplement();
452 if (codingComplement != null)
455 ((AlignViewport) codingComplement).getAlignPanel()
464 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
467 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
468 true, true, false, 0,
469 parentAvport.getAlignment().getWidth() - 1);
471 ColourSchemeI cs = null;
472 if (parentAvport.getGlobalColourScheme() != null)
474 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
476 cs = new UserColourScheme(
477 ((UserColourScheme) parentAvport.getGlobalColourScheme())
482 cs = ColourSchemeProperty.getColourScheme(treeGroup,
483 ColourSchemeProperty.getColourName(
484 parentAvport.getGlobalColourScheme()));
488 treeGroup.setColourScheme(cs);
489 treeGroup.getGroupColourScheme().setThreshold(
490 parentAvport.getResidueShading().getThreshold(),
491 parentAvport.isIgnoreGapsConsensus());
493 treeGroup.setName("Tree Group " + nrTreeGroups);
494 treeGroup.setIdColour(groupColour);
496 for (AlignmentPanel associatedPanel : associatedPanels)
498 AlignViewportI altViewport = associatedPanel
501 if (altViewport.getGlobalColourScheme() != null
502 && altViewport.getResidueShading()
503 .conservationApplied())
505 Conservation conserv = new Conservation(treeGroup.getName(),
506 treeGroup.getSequences(null), treeGroup.getStartRes(),
507 treeGroup.getEndRes());
509 conserv.verdict(false, altViewport.getConsPercGaps());
510 treeGroup.getGroupColourScheme().setConservation(conserv);
513 altViewport.getAlignment().addGroup(treeGroup);
514 // TODO can we push all of the below into AlignViewportI?
515 final AlignViewportI codingComplement = altViewport
516 .getCodingComplement();
517 if (codingComplement != null)
519 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
520 parentAvport, codingComplement);
521 if (mappedGroup.getSequences().size() > 0)
523 codingComplement.getAlignment().addGroup(mappedGroup);
524 for (SequenceI seq : mappedGroup.getSequences())
526 codingComplement.setSequenceColour(seq, groupColour.brighter());
536 * may or may not need an extra repaint on the alignment view (check what kira
540 public void showNodeSelectionOnAlign(final TreeNodeI node)
543 if (node.isInternal())
545 showMatchingChildSequences(node);
550 showMatchingSequence(node);
561 public void showMatchingSequence(final TreeNodeI nodeToMatch)
563 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
564 if (matchingSequence != null)
566 long nodeId = nodeToMatch.getId();
567 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
568 treeSelectionChanged(matchingSequence);
569 parentAvport.sendSelection();
575 public void showMatchingChildSequences(final TreeNodeI parentNode)
577 // redundancy here, Forester already iterates through tree to get all
579 List<TreeNodeI> childNodes = parentNode.getAllDescendants();
582 for (TreeNodeI childNode : childNodes)
584 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
586 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
587 if (matchingSequence != null)
589 long nodeId = childNode.getId();
590 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
592 treeSelectionChanged(matchingSequence);
597 parentAvport.sendSelection();
603 * Refactored from TreeCanvas.
606 * of the node selected in the tree viewer.
609 public void treeSelectionChanged(final SequenceI sequence)
611 if (!parentAvport.isClosed()) // alignment view could be closed
613 SequenceGroup selected = parentAvport.getSelectionGroup();
615 if (selected == null)
617 selected = new SequenceGroup();
618 parentAvport.setSelectionGroup(selected);
621 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
622 selected.addOrRemove(sequence, true);
628 public void sortByTree_actionPerformed()// modify for Aptx
631 // if (treeCanvas.applyToAllViews)
633 // final ArrayList<CommandI> commands = new ArrayList<>();
634 // for (AlignmentPanel ap : PaintRefresher
635 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
637 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
639 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
643 // public void undoCommand(AlignmentI[] views)
645 // for (CommandI tsort : commands)
647 // tsort.undoCommand(views);
652 // public int getSize()
654 // return commands.size();
658 // public String getDescription()
660 // return "Tree Sort (many views)";
664 // public void doCommand(AlignmentI[] views)
667 // for (CommandI tsort : commands)
669 // tsort.doCommand(views);
673 // for (AlignmentPanel ap : PaintRefresher
674 // .getAssociatedPanels(av.getSequenceSetId()))
676 // // ensure all the alignFrames refresh their GI after adding an undo item
677 // ap.alignFrame.updateEditMenuBar();
682 // treeCanvas.ap.alignFrame
683 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
689 public CommandI sortAlignmentIn(AlignmentPanel ap)
691 // TODO: move to alignment view controller
693 AlignmentViewport viewport = ap.av;
694 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
697 AlignmentSorter.sortByTree(viewport.getAlignment(),
698 nodesBoundToSequences,
701 undo = new OrderCommand("Tree Sort", oldOrder,
702 viewport.getAlignment());
704 ap.paintAlignment(true, false);
707 } catch (Exception e)
709 System.err.println(e.getMessage());
721 * @param objectToCheck
723 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
725 if (set.contains(objectToCheck))
727 set.remove(objectToCheck);
731 set.add(objectToCheck);
736 public AlignmentViewport getParentAvport()
741 public void setParentAvport(final AlignmentViewport parentAvport)
743 this.parentAvport = parentAvport;
746 public AlignmentPanel[] getAssociatedPanels()
748 return associatedPanels;
751 public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
753 this.associatedPanels = associatedPanels;