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.AlignmentI;
9 import jalview.datamodel.ColumnSelection;
10 import jalview.datamodel.HiddenColumns;
11 import jalview.datamodel.SequenceGroup;
12 import jalview.datamodel.SequenceI;
13 import jalview.ext.treeviewer.LoadedTreeSequenceAssociation;
14 import jalview.ext.treeviewer.TreeFrameI;
15 import jalview.ext.treeviewer.TreeI;
16 import jalview.ext.treeviewer.TreeNodeI;
17 import jalview.ext.treeviewer.TreePanelI;
18 import jalview.ext.treeviewer.TreeViewerBindingI;
19 import jalview.ext.treeviewer.TreeViewerUtils;
20 import jalview.gui.AlignViewport;
21 import jalview.gui.AlignmentPanel;
22 import jalview.gui.Desktop;
23 import jalview.gui.JvOptionPane;
24 import jalview.gui.PaintRefresher;
25 import jalview.schemes.ColourSchemeI;
26 import jalview.schemes.ColourSchemeProperty;
27 import jalview.schemes.UserColourScheme;
28 import jalview.structure.SelectionSource;
29 import jalview.structure.StructureSelectionManager;
30 import jalview.util.MappingUtils;
31 import jalview.util.MessageManager;
32 import jalview.viewmodel.AlignmentViewport;
34 import java.awt.Color;
35 import java.awt.Rectangle;
36 import java.awt.event.ActionEvent;
37 import java.awt.event.InputEvent;
38 import java.awt.event.MouseEvent;
39 import java.util.ArrayList;
40 import java.util.HashSet;
41 import java.util.List;
45 import javax.swing.SwingUtilities;
46 import javax.swing.event.InternalFrameAdapter;
47 import javax.swing.event.InternalFrameEvent;
50 * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
51 * it originates from, meaning that selecting sequences in the tree viewer also
52 * selects them in the alignment view and vice versa.
54 * @author kjvanderheide
57 public final class JalviewBinding
58 implements TreeViewerBindingI
60 private final TreeFrameI aptxFrame;
62 private TreePanelI treeView;
64 private AlignmentViewport parentAvport;
66 private final StructureSelectionManager ssm;
68 private Map<SequenceI, TreeNodeI> sequencesBoundToNodes;
70 private Map<TreeNodeI, SequenceI> nodesBoundToSequences;
74 private float furthestNodeX;
76 private int nrTreeGroups = 0;
78 private boolean applyToAllViews = false;
82 * @param archaeopteryx
84 * @param jalviewAlignmentViewport
85 * alignment viewport from which the tree was calculated.
87 * @param alignMappedToNodes
88 * map with sequences used to calculate the tree and matching tree
89 * nodes as key, value pair respectively.
91 * @param nodesMappedToAlign
92 * map with tree nodes and matching sequences used to calculate the
93 * tree as key, value pair respectively.
95 public JalviewBinding(final TreeFrameI archaeopteryx,
96 final AlignmentViewport jalviewAlignmentViewport,
97 final Map<SequenceI, TreeNodeI> alignMappedToNodes,
98 final Map<TreeNodeI, SequenceI> nodesMappedToAlign)
101 if (archaeopteryx.getNumberOfTrees() > 1)
103 JvOptionPane.showMessageDialog(Desktop.desktop,
104 MessageManager.getString("label.tabs_detected_archaeopteryx"),
105 MessageManager.getString("label.problem_reading_tree_file"),
106 JvOptionPane.WARNING_MESSAGE);
110 // deal with/prohibit null values here as that will cause problems
111 aptxFrame = archaeopteryx;
112 parentAvport = jalviewAlignmentViewport;
113 sequencesBoundToNodes = alignMappedToNodes;
114 nodesBoundToSequences = nodesMappedToAlign;
116 treeView = archaeopteryx.getTreePanel();
117 ssm = parentAvport.getStructureSelectionManager();
119 ssm.addSelectionListener(this);
120 treeView.addMouseListener(this);
121 treeView.registerWithPaintRefresher(
122 parentAvport.getSequenceSetId());
124 aptxFrame.addFrameListener(new InternalFrameAdapter()
128 public void internalFrameClosed(InternalFrameEvent e)
130 TreeViewerUtils.getActiveTreeViews().remove(aptxFrame);
131 ssm.removeSelectionListener(JalviewBinding.this);
136 // treeTabs.addChangeListener(new ChangeListener()
140 // public void stateChanged(ChangeEvent e)
143 // SwingUtilities.invokeLater(new Runnable()
148 // * Resend the selection to the tree view when tabs get switched, this
149 // * has to be buried in invokeLater as Forester first resets the tree
150 // * view on switching tabs, without invokeLater this would get called
151 // * before Forester resets which would nullify the selection.
155 // treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
156 // parentAvport.sendSelection();
157 // // PaintRefresher.Refresh(treeView,
158 // // parentAvport.getSequenceSetId());
170 public void actionPerformed(ActionEvent e)
172 // reset hidden sequences first
173 parentAvport.showAllHiddenSeqs();
175 if (treeView.showingSubTree())
177 LoadedTreeSequenceAssociation bindAptxNodes = new LoadedTreeSequenceAssociation(
178 parentAvport.getAlignment().getSequencesArray(),
180 bindAptxNodes.associateLeavesToSequences();
181 sequencesBoundToNodes = bindAptxNodes.getAlignmentWithNodes();
182 nodesBoundToSequences = bindAptxNodes.getNodesWithAlignment();
183 TreeViewerUtils.associateNodesWithJalviewSequences(aptxFrame, parentAvport,
184 sequencesBoundToNodes, nodesBoundToSequences);
187 for (SequenceI seq : parentAvport.getAlignment().getSequencesArray())
189 if (!sequencesBoundToNodes.containsKey(seq))
191 parentAvport.hideSequence(new SequenceI[] { seq });
199 Rectangle visibleView = treeView.getVisibleArea();
201 for (TreeNodeI node : treeView.getTree().getRoot()
202 .getAllDescendants())
204 if (!(node.getXcoord() > visibleView.getMinX()
205 && node.getXcoord() < visibleView.getMaxX()
206 && node.getYcoord() > visibleView.getMinY()
207 && node.getYcoord() < visibleView.getMaxY()))
210 .hideSequence(new SequenceI[]
211 { nodesBoundToSequences.get(node) });
222 public void mouseClicked(MouseEvent e)
224 SwingUtilities.invokeLater(new Runnable() {
228 * invokeLater so that this always runs after Forester's mouseClicked
232 final TreeNodeI node = treeView.findNode(e.getX(),
236 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
237 // selection if shift
240 parentAvport.setSelectionGroup(null);
243 showNodeSelectionOnAlign(node);
248 partitionTree(e.getX());
250 treeView.notifyPaintRefresher(parentAvport.getSequenceSetId(),
263 public void mousePressed(final MouseEvent e)
268 public void mouseReleased(MouseEvent e)
273 public void mouseEntered(MouseEvent e)
278 public void mouseExited(MouseEvent e)
284 public void selection(final SequenceGroup seqsel,
285 final ColumnSelection colsel, final HiddenColumns hidden,
286 final SelectionSource source)
288 if (source == parentAvport) // check if source is alignment from where the
291 treeView.setMatchingNodes(
292 new HashSet<Long>(seqsel.getSequences().size()));
295 for (SequenceI selectedSequence : seqsel.getSequences())
297 TreeNodeI matchingNode = sequencesBoundToNodes
298 .get(selectedSequence);
299 if (matchingNode != null)
301 treeView.getMatchingNodes().add(matchingNode.getId());
304 // if (!matchingNode.getBranchData().isHasBranchColor())
306 // // Color foundNodesColour = treeView.getTreeColorSet()
307 // // .getFoundColor0();
308 // // matchingNode.getBranchData()
309 // // .setBranchColor(new BranchColor(foundNodesColour));
324 * Partially refactored from TreeCanvas
326 public void partitionTree(final int x)
328 TreeI tree = treeView.getTree();
332 // should be calculated on each partition as the tree can theoretically
333 // change in the meantime
334 TreeNodeI furthestNode = tree.getFurthestNode();
335 furthestNodeX = furthestNode.getXcoord();
336 rootX = tree.getRoot().getXcoord();
338 // don't bother if 0 distance tree or clicked x lies outside of tree
339 if (furthestNodeX != rootX && !(x > furthestNodeX))
341 float threshold = (x - rootX) / (furthestNodeX - rootX);
342 List<TreeNodeI> foundNodes = getNodesAboveThreshold(
349 // clear previous colours?
356 public List<TreeNodeI> getNodesAboveThreshold(float threshold,
360 List<TreeNodeI> nodesAboveThreshold = new ArrayList<>();
362 parentAvport.setSelectionGroup(null);
363 parentAvport.getAlignment().deleteAllGroups();
364 parentAvport.clearSequenceColours();
365 if (parentAvport.getCodingComplement() != null)
367 parentAvport.getCodingComplement().setSelectionGroup(null);
368 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
369 parentAvport.getCodingComplement().clearSequenceColours();
373 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
375 return nodesAboveThreshold;
380 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
388 private List<TreeNodeI> colourNodesAboveThreshold(
389 List<TreeNodeI> nodeList, float threshold,
393 for (TreeNodeI childNode : node.getDirectChildren())
395 childNode.setBranchColor(Color.black);
396 float nodeCutoff = (childNode.getXcoord() - rootX)
397 / (furthestNodeX - rootX);
399 if (nodeCutoff > threshold)
401 nodeList.add(childNode);
403 Color randomColour = new Color((int) (Math.random() * 255),
404 (int) (Math.random() * 255), (int) (Math.random() * 255));
405 childNode.setBranchColor(randomColour);
407 List<SequenceI> groupSeqs = new ArrayList<>();
408 SequenceI seq = nodesBoundToSequences.get(childNode);
412 parentAvport.setSequenceColour(seq, randomColour);
415 List<TreeNodeI> descendantNodes = childNode
416 .getAllDescendants();
418 for (TreeNodeI descNode : descendantNodes)
420 seq = nodesBoundToSequences.get(descNode);
424 parentAvport.setSequenceColour(seq, randomColour);
427 descNode.setBranchColor(randomColour);
430 if (groupSeqs != null)
433 groupThresholdSequences(groupSeqs, randomColour);
438 colourNodesAboveThreshold(nodeList, threshold, childNode);
442 for (AlignmentPanel associatedPanel : getAssociatedPanels())
445 associatedPanel.updateAnnotation();
447 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
448 .getCodingComplement();
449 if (codingComplement != null)
452 ((AlignViewport) codingComplement).getAlignPanel()
461 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
464 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
465 true, true, false, 0,
466 parentAvport.getAlignment().getWidth() - 1);
468 ColourSchemeI cs = null;
469 if (parentAvport.getGlobalColourScheme() != null)
471 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
473 cs = new UserColourScheme(
474 ((UserColourScheme) parentAvport.getGlobalColourScheme())
479 cs = ColourSchemeProperty.getColourScheme(treeGroup,
480 ColourSchemeProperty.getColourName(
481 parentAvport.getGlobalColourScheme()));
485 treeGroup.setColourScheme(cs);
486 treeGroup.getGroupColourScheme().setThreshold(
487 parentAvport.getResidueShading().getThreshold(),
488 parentAvport.isIgnoreGapsConsensus());
490 treeGroup.setName("Tree Group " + nrTreeGroups);
491 treeGroup.setIdColour(groupColour);
493 for (AlignmentPanel associatedPanel : getAssociatedPanels())
495 AlignViewportI altViewport = associatedPanel
498 if (altViewport.getGlobalColourScheme() != null
499 && altViewport.getResidueShading()
500 .conservationApplied())
502 Conservation conserv = new Conservation(treeGroup.getName(),
503 treeGroup.getSequences(null), treeGroup.getStartRes(),
504 treeGroup.getEndRes());
506 conserv.verdict(false, altViewport.getConsPercGaps());
507 treeGroup.getGroupColourScheme().setConservation(conserv);
510 altViewport.getAlignment().addGroup(treeGroup);
511 // TODO can we push all of the below into AlignViewportI?
512 final AlignViewportI codingComplement = altViewport
513 .getCodingComplement();
514 if (codingComplement != null)
516 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
517 parentAvport, codingComplement);
518 if (mappedGroup.getSequences().size() > 0)
520 codingComplement.getAlignment().addGroup(mappedGroup);
521 for (SequenceI seq : mappedGroup.getSequences())
523 codingComplement.setSequenceColour(seq, groupColour.brighter());
533 * may or may not need an extra repaint on the alignment view (check what kira
537 public void showNodeSelectionOnAlign(final TreeNodeI node)
540 if (node.isInternal())
542 showMatchingChildSequences(node);
547 showMatchingSequence(node);
558 public void showMatchingSequence(final TreeNodeI nodeToMatch)
560 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
561 if (matchingSequence != null)
563 long nodeId = nodeToMatch.getId();
564 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
565 treeSelectionChanged(matchingSequence);
566 parentAvport.sendSelection();
572 public void showMatchingChildSequences(final TreeNodeI parentNode)
574 // redundancy here, Forester already iterates through tree to get all
576 List<TreeNodeI> childNodes = parentNode.getAllDescendants();
579 for (TreeNodeI childNode : childNodes)
581 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
583 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
584 if (matchingSequence != null)
586 long nodeId = childNode.getId();
587 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
589 treeSelectionChanged(matchingSequence);
594 parentAvport.sendSelection();
600 * Refactored from TreeCanvas.
603 * of the node selected in the tree viewer.
606 public void treeSelectionChanged(final SequenceI sequence)
608 if (!parentAvport.isClosed()) // alignment view could be closed
610 SequenceGroup selected = parentAvport.getSelectionGroup();
612 if (selected == null)
614 selected = new SequenceGroup();
615 parentAvport.setSelectionGroup(selected);
618 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
619 selected.addOrRemove(sequence, true);
625 public void sortByTree_actionPerformed()
628 // if (applyToAllViews)
630 final ArrayList<CommandI> commands = new ArrayList<>();
631 for (AlignmentPanel ap : PaintRefresher
632 .getAssociatedPanels(parentAvport.getSequenceSetId()))
634 commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
635 ap.alignFrame.addHistoryItem(new CommandI()
639 public void undoCommand(AlignmentI[] views)
641 for (CommandI tsort : commands)
643 tsort.undoCommand(views);
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);
670 ap.alignFrame.updateEditMenuBar();
675 // alignPanel.alignFrame.addHistoryItem(sortAlignmentIn(alignPanel));
681 public CommandI sortAlignmentIn(AlignmentPanel ap)
683 // TODO: move to alignment view controller
685 AlignmentViewport viewport = ap.av;
686 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
689 AlignmentSorter.sortByTree(viewport.getAlignment(),
690 nodesBoundToSequences,
693 undo = new OrderCommand("Tree Sort", oldOrder,
694 viewport.getAlignment());
696 ap.paintAlignment(true, false);
699 } catch (Exception e)
701 System.err.println(e.getMessage());
713 * @param objectToCheck
715 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
717 if (set.contains(objectToCheck))
719 set.remove(objectToCheck);
723 set.add(objectToCheck);
728 public AlignmentViewport getParentAvport()
733 public void setParentAvport(final AlignmentViewport parentAvport)
735 this.parentAvport = parentAvport;
738 public AlignmentPanel[] getAssociatedPanels()
740 return PaintRefresher
741 .getAssociatedPanels(parentAvport.getSequenceSetId());