1 package jalview.ext.treeviewer;
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.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.Rectangle;
29 import java.awt.event.ActionEvent;
30 import java.awt.event.InputEvent;
31 import java.awt.event.MouseEvent;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.HashSet;
35 import java.util.List;
38 import javax.swing.SwingUtilities;
39 import javax.swing.event.InternalFrameAdapter;
40 import javax.swing.event.InternalFrameEvent;
43 * Class for binding the tree viewer to the Jalview alignment that it originates
44 * from, meaning that selecting sequences in the tree viewer also selects them
45 * in the alignment view and vice versa.
47 * @author kjvanderheide
50 public final class JalviewBinding
51 implements TreeViewerBindingI
53 private final TreeFrameI aptxFrame;
55 private TreePanelI treeView;
57 private AlignmentViewport parentAvport;
59 private final StructureSelectionManager ssm;
61 private Map<SequenceI, TreeNodeI> sequencesBoundToNodes;
63 private Map<TreeNodeI, SequenceI> nodesBoundToSequences;
67 private float furthestNodeX;
69 private int nrTreeGroups = 0;
71 private boolean applyToAllViews = false;
75 * @param archaeopteryx
77 * @param jalviewAlignmentViewport
78 * alignment viewport from which the tree was calculated.
80 * @param alignMappedToNodes
81 * map with sequences used to calculate the tree and matching tree
82 * nodes as key, value pair respectively.
84 * @param nodesMappedToAlign
85 * map with tree nodes and matching sequences used to calculate the
86 * tree as key, value pair respectively.
88 public JalviewBinding(final TreeFrameI archaeopteryx,
89 final AlignmentViewport jalviewAlignmentViewport,
90 final Map<SequenceI, TreeNodeI> alignMappedToNodes,
91 final Map<TreeNodeI, SequenceI> nodesMappedToAlign)
94 if (archaeopteryx.getNumberOfTrees() > 1)
96 JvOptionPane.showMessageDialog(Desktop.desktop,
97 MessageManager.getString("label.tabs_detected_archaeopteryx"),
98 MessageManager.getString("label.problem_reading_tree_file"),
99 JvOptionPane.WARNING_MESSAGE);
103 // deal with/prohibit null values here as that will cause problems
104 aptxFrame = archaeopteryx;
105 parentAvport = jalviewAlignmentViewport;
106 sequencesBoundToNodes = alignMappedToNodes;
107 nodesBoundToSequences = nodesMappedToAlign;
109 treeView = archaeopteryx.getTreePanel();
110 ssm = parentAvport.getStructureSelectionManager();
112 aptxFrame.setViewBinding(this);
113 ssm.addSelectionListener(this);
114 treeView.addMouseListener(this);
115 treeView.registerWithPaintRefresher(
116 parentAvport.getSequenceSetId());
118 aptxFrame.addFrameListener(new InternalFrameAdapter()
122 public void internalFrameClosed(InternalFrameEvent e)
124 TreeViewerUtils.getActiveTreeViews().remove(aptxFrame);
125 ssm.removeSelectionListener(JalviewBinding.this);
130 // treeTabs.addChangeListener(new ChangeListener()
134 // public void stateChanged(ChangeEvent e)
137 // SwingUtilities.invokeLater(new Runnable()
142 // * Resend the selection to the tree view when tabs get switched, this
143 // * has to be buried in invokeLater as Forester first resets the tree
144 // * view on switching tabs, without invokeLater this would get called
145 // * before Forester resets which would nullify the selection.
149 // treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
150 // parentAvport.sendSelection();
151 // // PaintRefresher.Refresh(treeView,
152 // // parentAvport.getSequenceSetId());
164 public void actionPerformed(ActionEvent e)
166 // reset hidden sequences first
167 parentAvport.showAllHiddenSeqs();
169 if (treeView.showingSubTree())
171 LoadedTreeSequenceAssociation bindAptxNodes = new LoadedTreeSequenceAssociation(
172 parentAvport.getAlignment().getSequencesArray(),
174 bindAptxNodes.associateNodesToSequences();
175 sequencesBoundToNodes = bindAptxNodes.getAlignmentWithNodes();
176 nodesBoundToSequences = bindAptxNodes.getNodesWithAlignment();
177 TreeViewerUtils.associateNodesWithJalviewSequences(aptxFrame,
178 parentAvport, sequencesBoundToNodes, nodesBoundToSequences);
180 for (SequenceI seq : parentAvport.getAlignment().getSequencesArray())
182 if (!sequencesBoundToNodes.containsKey(seq))
184 parentAvport.hideSequence(new SequenceI[] { seq });
192 Rectangle visibleView = treeView.getVisibleArea();
194 for (TreeNodeI node : treeView.getTree().getRoot()
195 .getAllDescendants())
197 if (!(node.getXcoord() > visibleView.getMinX()
198 && node.getXcoord() < visibleView.getMaxX()
199 && node.getYcoord() > visibleView.getMinY()
200 && node.getYcoord() < visibleView.getMaxY()))
203 .hideSequence(new SequenceI[]
204 { nodesBoundToSequences.get(node) });
215 public void mouseClicked(MouseEvent e)
217 SwingUtilities.invokeLater(new Runnable() {
221 * invokeLater so that this always runs after Forester's mouseClicked
225 final TreeNodeI node = treeView.findNode(e.getX(),
229 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
230 // selection if shift
233 parentAvport.setSelectionGroup(null);
236 showNodeSelectionOnAlign(node);
241 partitionTree(e.getX());
243 treeView.notifyPaintRefresher(parentAvport.getSequenceSetId(),
256 public void mousePressed(final MouseEvent e)
261 public void mouseReleased(MouseEvent e)
266 public void mouseEntered(MouseEvent e)
271 public void mouseExited(MouseEvent e)
277 public void selection(final SequenceGroup seqsel,
278 final ColumnSelection colsel, final HiddenColumns hidden,
279 final SelectionSource source)
281 if (source == parentAvport) // check if source is alignment from where the
284 treeView.setMatchingNodes(
285 new HashSet<Long>(seqsel.getSequences().size()));
288 for (SequenceI selectedSequence : seqsel.getSequences())
290 TreeNodeI matchingNode = sequencesBoundToNodes
291 .get(selectedSequence);
292 if (matchingNode != null)
294 treeView.addToMatchingNodes(matchingNode);
297 // if (!matchingNode.getBranchData().isHasBranchColor())
299 // // Color foundNodesColour = treeView.getTreeColorSet()
300 // // .getFoundColor0();
301 // // matchingNode.getBranchData()
302 // // .setBranchColor(new BranchColor(foundNodesColour));
317 * Partially refactored from TreeCanvas
320 public void partitionTree(final int x)
322 TreeI tree = treeView.getTree();
326 // should be calculated on each partition as the tree can theoretically
327 // change in the meantime
328 TreeNodeI furthestNode = tree.getFurthestNode();
329 furthestNodeX = furthestNode.getXcoord();
330 rootX = tree.getRoot().getXcoord();
332 // don't bother if 0 distance tree or clicked x lies outside of tree
333 // if (furthestNodeX != rootX && !(x > furthestNodeX))
335 float threshold = (x - rootX) / (furthestNodeX - rootX);
336 List<TreeNodeI> foundNodes = getNodesAboveThreshold(
346 public List<TreeNodeI> getNodesAboveThreshold(float threshold,
350 List<TreeNodeI> nodesAboveThreshold = new ArrayList<>();
352 parentAvport.setSelectionGroup(null);
353 parentAvport.getAlignment().deleteAllGroups();
354 parentAvport.clearSequenceColours();
355 if (parentAvport.getCodingComplement() != null)
357 parentAvport.getCodingComplement().setSelectionGroup(null);
358 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
359 parentAvport.getCodingComplement().clearSequenceColours();
363 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
365 return nodesAboveThreshold;
370 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
378 private List<TreeNodeI> colourNodesAboveThreshold(
379 List<TreeNodeI> nodeList, float threshold,
383 for (TreeNodeI childNode : node.getDirectChildren())
385 childNode.setBranchColor(Color.black);
386 float nodeCutoff = (childNode.getXcoord() - rootX)
387 / (furthestNodeX - rootX);
389 if (nodeCutoff > threshold)
391 nodeList.add(childNode);
393 Color randomColour = new Color((int) (Math.random() * 255),
394 (int) (Math.random() * 255), (int) (Math.random() * 255));
395 childNode.setBranchColor(randomColour);
397 List<SequenceI> groupSeqs = new ArrayList<>();
398 SequenceI seq = nodesBoundToSequences.get(childNode);
402 parentAvport.setSequenceColour(seq, randomColour);
405 List<TreeNodeI> descendantNodes = childNode
406 .getAllDescendants();
408 for (TreeNodeI descNode : descendantNodes)
410 seq = nodesBoundToSequences.get(descNode);
414 parentAvport.setSequenceColour(seq, randomColour);
417 descNode.setBranchColor(randomColour);
420 if (groupSeqs != null)
423 groupThresholdSequences(groupSeqs, randomColour);
428 colourNodesAboveThreshold(nodeList, threshold, childNode);
432 for (AlignmentPanel associatedPanel : getAssociatedPanels())
435 associatedPanel.updateAnnotation();
437 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
438 .getCodingComplement();
439 if (codingComplement != null)
442 ((AlignViewport) codingComplement).getAlignPanel()
451 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
454 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
455 true, true, false, 0,
456 parentAvport.getAlignment().getWidth() - 1);
458 ColourSchemeI cs = null;
459 if (parentAvport.getGlobalColourScheme() != null)
461 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
463 cs = new UserColourScheme(
464 ((UserColourScheme) parentAvport.getGlobalColourScheme())
469 cs = ColourSchemeProperty.getColourScheme(treeGroup,
470 ColourSchemeProperty.getColourName(
471 parentAvport.getGlobalColourScheme()));
475 treeGroup.setColourScheme(cs);
476 treeGroup.getGroupColourScheme().setThreshold(
477 parentAvport.getResidueShading().getThreshold(),
478 parentAvport.isIgnoreGapsConsensus());
480 treeGroup.setName("Tree Group " + nrTreeGroups);
481 treeGroup.setIdColour(groupColour);
483 for (AlignmentPanel associatedPanel : getAssociatedPanels())
485 AlignViewportI altViewport = associatedPanel
488 if (altViewport.getGlobalColourScheme() != null
489 && altViewport.getResidueShading()
490 .conservationApplied())
492 Conservation conserv = new Conservation(treeGroup.getName(),
493 treeGroup.getSequences(null), treeGroup.getStartRes(),
494 treeGroup.getEndRes());
496 conserv.verdict(false, altViewport.getConsPercGaps());
497 treeGroup.getGroupColourScheme().setConservation(conserv);
500 altViewport.getAlignment().addGroup(treeGroup);
501 // TODO can we push all of the below into AlignViewportI?
502 final AlignViewportI codingComplement = altViewport
503 .getCodingComplement();
504 if (codingComplement != null)
506 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
507 parentAvport, codingComplement);
508 if (mappedGroup.getSequences().size() > 0)
510 codingComplement.getAlignment().addGroup(mappedGroup);
511 for (SequenceI seq : mappedGroup.getSequences())
513 codingComplement.setSequenceColour(seq, groupColour.brighter());
524 public void showNodeSelectionOnAlign(final TreeNodeI node)
527 if (node.isInternal())
529 showMatchingChildSequences(node);
534 showMatchingSequence(node);
545 public void showMatchingSequence(final TreeNodeI nodeToMatch)
547 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
548 if (matchingSequence != null)
550 long nodeId = nodeToMatch.getId();
551 addOrRemoveInCollection(treeView.getMatchingNodesIds(), nodeId);
552 treeSelectionChanged(matchingSequence);
553 parentAvport.sendSelection();
559 public void showMatchingChildSequences(final TreeNodeI parentNode)
561 // redundancy here, Forester already iterates through tree to get all
563 List<TreeNodeI> childNodes = parentNode.getAllDescendants();
566 for (TreeNodeI childNode : childNodes)
568 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
569 if (matchingSequence != null)
571 long nodeId = childNode.getId();
572 addOrRemoveInCollection(treeView.getMatchingNodesIds(), nodeId);
574 treeSelectionChanged(matchingSequence);
579 parentAvport.sendSelection();
586 public void treeSelectionChanged(final SequenceI sequence)
588 if (!parentAvport.isClosed()) // alignment view could be closed
590 SequenceGroup selected = parentAvport.getSelectionGroup();
592 if (selected == null)
594 selected = new SequenceGroup();
595 parentAvport.setSelectionGroup(selected);
598 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
599 selected.addOrRemove(sequence, true);
605 public void sortByTree_actionPerformed()
608 // if (applyToAllViews)
610 final ArrayList<CommandI> commands = new ArrayList<>();
611 for (AlignmentPanel ap : PaintRefresher
612 .getAssociatedPanels(parentAvport.getSequenceSetId()))
614 commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
615 ap.alignFrame.addHistoryItem(new CommandI()
619 public void undoCommand(AlignmentI[] views)
621 for (CommandI tsort : commands)
623 tsort.undoCommand(views);
630 return commands.size();
634 public String getDescription()
636 return "Tree Sort (many views)";
640 public void doCommand(AlignmentI[] views)
643 for (CommandI tsort : commands)
645 tsort.doCommand(views);
650 ap.alignFrame.updateEditMenuBar();
655 // alignPanel.alignFrame.addHistoryItem(sortAlignmentIn(alignPanel));
661 public CommandI sortAlignmentIn(AlignmentPanel ap)
663 AlignmentViewport viewport = ap.av;
664 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
667 AlignmentSorter.sortByTree(viewport.getAlignment(),
668 nodesBoundToSequences,
671 undo = new OrderCommand("Tree Sort", oldOrder,
672 viewport.getAlignment());
674 ap.paintAlignment(true, false);
677 } catch (Exception e)
679 System.err.println(e.getMessage());
693 public static <E> void addOrRemoveInCollection(Collection<Long> collection,
696 if (collection.contains(nodeId))
698 collection.remove(nodeId);
702 collection.add(nodeId);
707 public AlignmentViewport getParentAvport()
712 public void setParentAvport(final AlignmentViewport parentAvport)
714 this.parentAvport = parentAvport;
717 public AlignmentPanel[] getAssociatedPanels()
719 return PaintRefresher
720 .getAssociatedPanels(parentAvport.getSequenceSetId());
724 public Map<SequenceI, TreeNodeI> getAlignmentWithNodes()
726 return sequencesBoundToNodes;
730 public Map<TreeNodeI, SequenceI> getNodesWithAlignment()
732 return nodesBoundToSequences;