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);
181 for (SequenceI seq : parentAvport.getAlignment().getSequencesArray())
183 if (!sequencesBoundToNodes.containsKey(seq))
185 parentAvport.hideSequence(new SequenceI[] { seq });
194 Rectangle visibleView = treeView.getVisibleArea();
196 for (TreeNodeI node : treeView.getTree().getRoot()
197 .getAllDescendants())
199 if (!(node.getXcoord() > visibleView.getMinX()
200 && node.getXcoord() < visibleView.getMaxX()
201 && node.getYcoord() > visibleView.getMinY()
202 && node.getYcoord() < visibleView.getMaxY()))
205 .hideSequence(new SequenceI[]
206 { nodesBoundToSequences.get(node) });
217 public void mouseClicked(MouseEvent e)
219 SwingUtilities.invokeLater(new Runnable() {
223 * invokeLater so that this always runs after Forester's mouseClicked
227 final TreeNodeI node = treeView.findNode(e.getX(),
231 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
232 // selection if shift
235 parentAvport.setSelectionGroup(null);
238 showNodeSelectionOnAlign(node);
243 partitionTree(e.getX());
245 treeView.notifyPaintRefresher(parentAvport.getSequenceSetId(),
258 public void mousePressed(final MouseEvent e)
263 public void mouseReleased(MouseEvent e)
268 public void mouseEntered(MouseEvent e)
273 public void mouseExited(MouseEvent e)
279 public void selection(final SequenceGroup seqsel,
280 final ColumnSelection colsel, final HiddenColumns hidden,
281 final SelectionSource source)
283 if (source == parentAvport) // check if source is alignment from where the
286 treeView.setMatchingNodes(
287 new HashSet<Long>(seqsel.getSequences().size()));
290 for (SequenceI selectedSequence : seqsel.getSequences())
292 TreeNodeI matchingNode = sequencesBoundToNodes
293 .get(selectedSequence);
294 if (matchingNode != null)
296 treeView.addToMatchingNodes(matchingNode);
299 // if (!matchingNode.getBranchData().isHasBranchColor())
301 // // Color foundNodesColour = treeView.getTreeColorSet()
302 // // .getFoundColor0();
303 // // matchingNode.getBranchData()
304 // // .setBranchColor(new BranchColor(foundNodesColour));
319 * Partially refactored from TreeCanvas
322 public void partitionTree(final int x)
324 TreeI tree = treeView.getTree();
328 // should be calculated on each partition as the tree can theoretically
329 // change in the meantime
330 TreeNodeI furthestNode = tree.getFurthestNode();
331 furthestNodeX = furthestNode.getXcoord();
332 rootX = tree.getRoot().getXcoord();
334 // don't bother if 0 distance tree or clicked x lies outside of tree
335 // if (furthestNodeX != rootX && !(x > furthestNodeX))
337 float threshold = (x - rootX) / (furthestNodeX - rootX);
338 List<TreeNodeI> foundNodes = getNodesAboveThreshold(
348 public List<TreeNodeI> getNodesAboveThreshold(float threshold,
352 List<TreeNodeI> nodesAboveThreshold = new ArrayList<>();
354 parentAvport.setSelectionGroup(null);
355 parentAvport.getAlignment().deleteAllGroups();
356 parentAvport.clearSequenceColours();
357 if (parentAvport.getCodingComplement() != null)
359 parentAvport.getCodingComplement().setSelectionGroup(null);
360 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
361 parentAvport.getCodingComplement().clearSequenceColours();
365 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
367 return nodesAboveThreshold;
372 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
380 private List<TreeNodeI> colourNodesAboveThreshold(
381 List<TreeNodeI> nodeList, float threshold,
385 for (TreeNodeI childNode : node.getDirectChildren())
387 childNode.setBranchColor(Color.black);
388 float nodeCutoff = (childNode.getXcoord() - rootX)
389 / (furthestNodeX - rootX);
391 if (nodeCutoff > threshold)
393 nodeList.add(childNode);
395 Color randomColour = new Color((int) (Math.random() * 255),
396 (int) (Math.random() * 255), (int) (Math.random() * 255));
397 childNode.setBranchColor(randomColour);
399 List<SequenceI> groupSeqs = new ArrayList<>();
400 SequenceI seq = nodesBoundToSequences.get(childNode);
404 parentAvport.setSequenceColour(seq, randomColour);
407 List<TreeNodeI> descendantNodes = childNode
408 .getAllDescendants();
410 for (TreeNodeI descNode : descendantNodes)
412 seq = nodesBoundToSequences.get(descNode);
416 parentAvport.setSequenceColour(seq, randomColour);
419 descNode.setBranchColor(randomColour);
422 if (groupSeqs != null)
425 groupThresholdSequences(groupSeqs, randomColour);
430 colourNodesAboveThreshold(nodeList, threshold, childNode);
434 for (AlignmentPanel associatedPanel : getAssociatedPanels())
437 associatedPanel.updateAnnotation();
439 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
440 .getCodingComplement();
441 if (codingComplement != null)
444 ((AlignViewport) codingComplement).getAlignPanel()
453 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
456 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
457 true, true, false, 0,
458 parentAvport.getAlignment().getWidth() - 1);
460 ColourSchemeI cs = null;
461 if (parentAvport.getGlobalColourScheme() != null)
463 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
465 cs = new UserColourScheme(
466 ((UserColourScheme) parentAvport.getGlobalColourScheme())
471 cs = ColourSchemeProperty.getColourScheme(treeGroup,
472 ColourSchemeProperty.getColourName(
473 parentAvport.getGlobalColourScheme()));
477 treeGroup.setColourScheme(cs);
478 treeGroup.getGroupColourScheme().setThreshold(
479 parentAvport.getResidueShading().getThreshold(),
480 parentAvport.isIgnoreGapsConsensus());
482 treeGroup.setName("Tree Group " + nrTreeGroups);
483 treeGroup.setIdColour(groupColour);
485 for (AlignmentPanel associatedPanel : getAssociatedPanels())
487 AlignViewportI altViewport = associatedPanel
490 if (altViewport.getGlobalColourScheme() != null
491 && altViewport.getResidueShading()
492 .conservationApplied())
494 Conservation conserv = new Conservation(treeGroup.getName(),
495 treeGroup.getSequences(null), treeGroup.getStartRes(),
496 treeGroup.getEndRes());
498 conserv.verdict(false, altViewport.getConsPercGaps());
499 treeGroup.getGroupColourScheme().setConservation(conserv);
502 altViewport.getAlignment().addGroup(treeGroup);
503 // TODO can we push all of the below into AlignViewportI?
504 final AlignViewportI codingComplement = altViewport
505 .getCodingComplement();
506 if (codingComplement != null)
508 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
509 parentAvport, codingComplement);
510 if (mappedGroup.getSequences().size() > 0)
512 codingComplement.getAlignment().addGroup(mappedGroup);
513 for (SequenceI seq : mappedGroup.getSequences())
515 codingComplement.setSequenceColour(seq, groupColour.brighter());
526 public void showNodeSelectionOnAlign(final TreeNodeI node)
529 if (node.isInternal())
531 showMatchingChildSequences(node);
536 showMatchingSequence(node);
547 public void showMatchingSequence(final TreeNodeI nodeToMatch)
549 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
550 if (matchingSequence != null)
552 long nodeId = nodeToMatch.getId();
553 addOrRemoveInCollection(treeView.getMatchingNodesIds(), nodeId);
554 treeSelectionChanged(matchingSequence);
555 parentAvport.sendSelection();
561 public void showMatchingChildSequences(final TreeNodeI parentNode)
563 // redundancy here, Forester already iterates through tree to get all
565 List<TreeNodeI> childNodes = parentNode.getAllDescendants();
568 for (TreeNodeI childNode : childNodes)
570 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
571 if (matchingSequence != null)
573 long nodeId = childNode.getId();
574 addOrRemoveInCollection(treeView.getMatchingNodesIds(), nodeId);
576 treeSelectionChanged(matchingSequence);
581 parentAvport.sendSelection();
588 public void treeSelectionChanged(final SequenceI sequence)
590 if (!parentAvport.isClosed()) // alignment view could be closed
592 SequenceGroup selected = parentAvport.getSelectionGroup();
594 if (selected == null)
596 selected = new SequenceGroup();
597 parentAvport.setSelectionGroup(selected);
600 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
601 selected.addOrRemove(sequence, true);
607 public void sortByTree_actionPerformed()
610 // if (applyToAllViews)
612 final ArrayList<CommandI> commands = new ArrayList<>();
613 for (AlignmentPanel ap : PaintRefresher
614 .getAssociatedPanels(parentAvport.getSequenceSetId()))
616 commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
617 ap.alignFrame.addHistoryItem(new CommandI()
621 public void undoCommand(AlignmentI[] views)
623 for (CommandI tsort : commands)
625 tsort.undoCommand(views);
632 return commands.size();
636 public String getDescription()
638 return "Tree Sort (many views)";
642 public void doCommand(AlignmentI[] views)
645 for (CommandI tsort : commands)
647 tsort.doCommand(views);
652 ap.alignFrame.updateEditMenuBar();
657 // alignPanel.alignFrame.addHistoryItem(sortAlignmentIn(alignPanel));
663 public CommandI sortAlignmentIn(AlignmentPanel ap)
665 AlignmentViewport viewport = ap.av;
666 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
669 AlignmentSorter.sortByTree(viewport.getAlignment(),
670 nodesBoundToSequences,
673 undo = new OrderCommand("Tree Sort", oldOrder,
674 viewport.getAlignment());
676 ap.paintAlignment(true, false);
679 } catch (Exception e)
681 System.err.println(e.getMessage());
695 public static <E> void addOrRemoveInCollection(Collection<Long> collection,
698 if (collection.contains(nodeId))
700 collection.remove(nodeId);
704 collection.add(nodeId);
709 public AlignmentViewport getParentAvport()
714 public void setParentAvport(final AlignmentViewport parentAvport)
716 this.parentAvport = parentAvport;
719 public AlignmentPanel[] getAssociatedPanels()
721 return PaintRefresher
722 .getAssociatedPanels(parentAvport.getSequenceSetId());
726 public Map<SequenceI, TreeNodeI> getAlignmentWithNodes()
728 return sequencesBoundToNodes;
732 public Map<TreeNodeI, SequenceI> getNodesWithAlignment()
734 return nodesBoundToSequences;
738 public void hideCollapsedSequences_actionPerformed()
740 parentAvport.showAllHiddenSeqs();
742 for (TreeNodeI node : treeView.getTree().getAllNodes())
744 if (node.isCollapsed())
746 SequenceI seqToHide = nodesBoundToSequences.get(node);
747 if (seqToHide != null)
749 parentAvport.hideSequence(new SequenceI[] { seqToHide });