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.TreeFrameI;
13 import jalview.ext.treeviewer.TreeI;
14 import jalview.ext.treeviewer.TreeNodeI;
15 import jalview.ext.treeviewer.TreePanelI;
16 import jalview.ext.treeviewer.TreeViewerBindingI;
17 import jalview.ext.treeviewer.TreeViewerUtils;
18 import jalview.ext.treeviewer.LoadedTreeSequenceAssociation;
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(
355 public List<TreeNodeI> getNodesAboveThreshold(double threshold,
359 List<TreeNodeI> nodesAboveThreshold = new ArrayList<>();
361 parentAvport.setSelectionGroup(null);
362 parentAvport.getAlignment().deleteAllGroups();
363 parentAvport.clearSequenceColours();
364 if (parentAvport.getCodingComplement() != null)
366 parentAvport.getCodingComplement().setSelectionGroup(null);
367 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
368 parentAvport.getCodingComplement().clearSequenceColours();
372 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
374 return nodesAboveThreshold;
379 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
387 private List<TreeNodeI> colourNodesAboveThreshold(
388 List<TreeNodeI> nodeList, double threshold,
392 for (TreeNodeI childNode : node.getDirectChildren())
394 childNode.setBranchColor(Color.black);
395 float nodeCutoff = (childNode.getXcoord() - rootX)
396 / (furthestNodeX - rootX);
398 if (nodeCutoff > threshold)
400 nodeList.add(childNode);
402 Color randomColour = new Color((int) (Math.random() * 255),
403 (int) (Math.random() * 255), (int) (Math.random() * 255));
404 childNode.setBranchColor(randomColour);
406 List<SequenceI> groupSeqs = new ArrayList<>();
407 SequenceI seq = nodesBoundToSequences.get(childNode);
411 parentAvport.setSequenceColour(seq, randomColour);
414 List<TreeNodeI> descendantNodes = childNode
415 .getAllDescendants();
417 for (TreeNodeI descNode : descendantNodes)
419 seq = nodesBoundToSequences.get(descNode);
423 parentAvport.setSequenceColour(seq, randomColour);
426 descNode.setBranchColor(randomColour);
429 if (groupSeqs != null)
432 groupThresholdSequences(groupSeqs, randomColour);
437 colourNodesAboveThreshold(nodeList, threshold, childNode);
442 for (AlignmentPanel associatedPanel : associatedPanels) {
444 associatedPanel.updateAnnotation();
446 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
447 .getCodingComplement();
448 if (codingComplement != null)
451 ((AlignViewport) codingComplement).getAlignPanel()
460 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
463 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
464 true, true, false, 0,
465 parentAvport.getAlignment().getWidth() - 1);
467 ColourSchemeI cs = null;
468 if (parentAvport.getGlobalColourScheme() != null)
470 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
472 cs = new UserColourScheme(
473 ((UserColourScheme) parentAvport.getGlobalColourScheme())
478 cs = ColourSchemeProperty.getColourScheme(treeGroup,
479 ColourSchemeProperty.getColourName(
480 parentAvport.getGlobalColourScheme()));
484 treeGroup.setColourScheme(cs);
485 treeGroup.getGroupColourScheme().setThreshold(
486 parentAvport.getResidueShading().getThreshold(),
487 parentAvport.isIgnoreGapsConsensus());
489 treeGroup.setName("Tree Group " + nrTreeGroups);
490 treeGroup.setIdColour(groupColour);
492 for (AlignmentPanel associatedPanel : associatedPanels)
494 AlignViewportI altViewport = associatedPanel
497 if (altViewport.getGlobalColourScheme() != null
498 && altViewport.getResidueShading()
499 .conservationApplied())
501 Conservation conserv = new Conservation(treeGroup.getName(),
502 treeGroup.getSequences(null), treeGroup.getStartRes(),
503 treeGroup.getEndRes());
505 conserv.verdict(false, altViewport.getConsPercGaps());
506 treeGroup.getGroupColourScheme().setConservation(conserv);
509 altViewport.getAlignment().addGroup(treeGroup);
510 // TODO can we push all of the below into AlignViewportI?
511 final AlignViewportI codingComplement = altViewport
512 .getCodingComplement();
513 if (codingComplement != null)
515 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
516 parentAvport, codingComplement);
517 if (mappedGroup.getSequences().size() > 0)
519 codingComplement.getAlignment().addGroup(mappedGroup);
520 for (SequenceI seq : mappedGroup.getSequences())
522 codingComplement.setSequenceColour(seq, groupColour.brighter());
532 * may or may not need an extra repaint on the alignment view (check what kira
536 public void showNodeSelectionOnAlign(final TreeNodeI node)
539 if (node.isInternal())
541 showMatchingChildSequences(node);
546 showMatchingSequence(node);
557 public void showMatchingSequence(final TreeNodeI nodeToMatch)
559 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
560 if (matchingSequence != null)
562 long nodeId = nodeToMatch.getId();
563 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
564 treeSelectionChanged(matchingSequence);
565 parentAvport.sendSelection();
571 public void showMatchingChildSequences(final TreeNodeI parentNode)
573 // redundancy here, Forester already iterates through tree to get all
575 List<TreeNodeI> childNodes = parentNode.getAllDescendants();
578 for (TreeNodeI childNode : childNodes)
580 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
582 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
583 if (matchingSequence != null)
585 long nodeId = childNode.getId();
586 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
588 treeSelectionChanged(matchingSequence);
593 parentAvport.sendSelection();
599 * Refactored from TreeCanvas.
602 * of the node selected in the tree viewer.
605 public void treeSelectionChanged(final SequenceI sequence)
607 if (!parentAvport.isClosed()) // alignment view could be closed
609 SequenceGroup selected = parentAvport.getSelectionGroup();
611 if (selected == null)
613 selected = new SequenceGroup();
614 parentAvport.setSelectionGroup(selected);
617 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
618 selected.addOrRemove(sequence, true);
624 public void sortByTree_actionPerformed()// modify for Aptx
627 // if (treeCanvas.applyToAllViews)
629 // final ArrayList<CommandI> commands = new ArrayList<>();
630 // for (AlignmentPanel ap : PaintRefresher
631 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
633 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
635 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
639 // public void undoCommand(AlignmentI[] views)
641 // for (CommandI tsort : commands)
643 // tsort.undoCommand(views);
648 // public int getSize()
650 // return commands.size();
654 // public String getDescription()
656 // return "Tree Sort (many views)";
660 // public void doCommand(AlignmentI[] views)
663 // for (CommandI tsort : commands)
665 // tsort.doCommand(views);
669 // for (AlignmentPanel ap : PaintRefresher
670 // .getAssociatedPanels(av.getSequenceSetId()))
672 // // ensure all the alignFrames refresh their GI after adding an undo item
673 // ap.alignFrame.updateEditMenuBar();
678 // treeCanvas.ap.alignFrame
679 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
685 public CommandI sortAlignmentIn(AlignmentPanel ap)
687 // TODO: move to alignment view controller
689 AlignmentViewport viewport = ap.av;
690 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
693 AlignmentSorter.sortByTree(viewport.getAlignment(),
694 nodesBoundToSequences,
697 undo = new OrderCommand("Tree Sort", oldOrder,
698 viewport.getAlignment());
700 ap.paintAlignment(true, false);
703 } catch (Exception e)
705 System.err.println(e.getMessage());
717 * @param objectToCheck
719 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
721 if (set.contains(objectToCheck))
723 set.remove(objectToCheck);
727 set.add(objectToCheck);
732 public AlignmentViewport getParentAvport()
737 public void setParentAvport(final AlignmentViewport parentAvport)
739 this.parentAvport = parentAvport;
742 public AlignmentPanel[] getAssociatedPanels()
744 return associatedPanels;
747 public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
749 this.associatedPanels = associatedPanels;