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 aptxFrame.setViewBinding(this);
120 ssm.addSelectionListener(this);
121 treeView.addMouseListener(this);
122 treeView.registerWithPaintRefresher(
123 parentAvport.getSequenceSetId());
125 aptxFrame.addFrameListener(new InternalFrameAdapter()
129 public void internalFrameClosed(InternalFrameEvent e)
131 TreeViewerUtils.getActiveTreeViews().remove(aptxFrame);
132 ssm.removeSelectionListener(JalviewBinding.this);
137 // treeTabs.addChangeListener(new ChangeListener()
141 // public void stateChanged(ChangeEvent e)
144 // SwingUtilities.invokeLater(new Runnable()
149 // * Resend the selection to the tree view when tabs get switched, this
150 // * has to be buried in invokeLater as Forester first resets the tree
151 // * view on switching tabs, without invokeLater this would get called
152 // * before Forester resets which would nullify the selection.
156 // treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
157 // parentAvport.sendSelection();
158 // // PaintRefresher.Refresh(treeView,
159 // // parentAvport.getSequenceSetId());
171 public void actionPerformed(ActionEvent e)
173 // reset hidden sequences first
174 parentAvport.showAllHiddenSeqs();
176 if (treeView.showingSubTree())
178 LoadedTreeSequenceAssociation bindAptxNodes = new LoadedTreeSequenceAssociation(
179 parentAvport.getAlignment().getSequencesArray(),
181 bindAptxNodes.associateNodesToSequences();
182 sequencesBoundToNodes = bindAptxNodes.getAlignmentWithNodes();
183 nodesBoundToSequences = bindAptxNodes.getNodesWithAlignment();
184 TreeViewerUtils.associateNodesWithJalviewSequences(aptxFrame,
185 parentAvport, 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.addToMatchingNodes(matchingNode);
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
327 public void partitionTree(final int x)
329 TreeI tree = treeView.getTree();
333 // should be calculated on each partition as the tree can theoretically
334 // change in the meantime
335 TreeNodeI furthestNode = tree.getFurthestNode();
336 furthestNodeX = furthestNode.getXcoord();
337 rootX = tree.getRoot().getXcoord();
339 // don't bother if 0 distance tree or clicked x lies outside of tree
340 // if (furthestNodeX != rootX && !(x > furthestNodeX))
342 float threshold = (x - rootX) / (furthestNodeX - rootX);
343 List<TreeNodeI> foundNodes = getNodesAboveThreshold(
353 public List<TreeNodeI> getNodesAboveThreshold(float threshold,
357 List<TreeNodeI> nodesAboveThreshold = new ArrayList<>();
359 parentAvport.setSelectionGroup(null);
360 parentAvport.getAlignment().deleteAllGroups();
361 parentAvport.clearSequenceColours();
362 if (parentAvport.getCodingComplement() != null)
364 parentAvport.getCodingComplement().setSelectionGroup(null);
365 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
366 parentAvport.getCodingComplement().clearSequenceColours();
370 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
372 return nodesAboveThreshold;
377 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
385 private List<TreeNodeI> colourNodesAboveThreshold(
386 List<TreeNodeI> nodeList, float threshold,
390 for (TreeNodeI childNode : node.getDirectChildren())
392 childNode.setBranchColor(Color.black);
393 float nodeCutoff = (childNode.getXcoord() - rootX)
394 / (furthestNodeX - rootX);
396 if (nodeCutoff > threshold)
398 nodeList.add(childNode);
400 Color randomColour = new Color((int) (Math.random() * 255),
401 (int) (Math.random() * 255), (int) (Math.random() * 255));
402 childNode.setBranchColor(randomColour);
404 List<SequenceI> groupSeqs = new ArrayList<>();
405 SequenceI seq = nodesBoundToSequences.get(childNode);
409 parentAvport.setSequenceColour(seq, randomColour);
412 List<TreeNodeI> descendantNodes = childNode
413 .getAllDescendants();
415 for (TreeNodeI descNode : descendantNodes)
417 seq = nodesBoundToSequences.get(descNode);
421 parentAvport.setSequenceColour(seq, randomColour);
424 descNode.setBranchColor(randomColour);
427 if (groupSeqs != null)
430 groupThresholdSequences(groupSeqs, randomColour);
435 colourNodesAboveThreshold(nodeList, threshold, childNode);
439 for (AlignmentPanel associatedPanel : getAssociatedPanels())
442 associatedPanel.updateAnnotation();
444 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
445 .getCodingComplement();
446 if (codingComplement != null)
449 ((AlignViewport) codingComplement).getAlignPanel()
458 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
461 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
462 true, true, false, 0,
463 parentAvport.getAlignment().getWidth() - 1);
465 ColourSchemeI cs = null;
466 if (parentAvport.getGlobalColourScheme() != null)
468 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
470 cs = new UserColourScheme(
471 ((UserColourScheme) parentAvport.getGlobalColourScheme())
476 cs = ColourSchemeProperty.getColourScheme(treeGroup,
477 ColourSchemeProperty.getColourName(
478 parentAvport.getGlobalColourScheme()));
482 treeGroup.setColourScheme(cs);
483 treeGroup.getGroupColourScheme().setThreshold(
484 parentAvport.getResidueShading().getThreshold(),
485 parentAvport.isIgnoreGapsConsensus());
487 treeGroup.setName("Tree Group " + nrTreeGroups);
488 treeGroup.setIdColour(groupColour);
490 for (AlignmentPanel associatedPanel : getAssociatedPanels())
492 AlignViewportI altViewport = associatedPanel
495 if (altViewport.getGlobalColourScheme() != null
496 && altViewport.getResidueShading()
497 .conservationApplied())
499 Conservation conserv = new Conservation(treeGroup.getName(),
500 treeGroup.getSequences(null), treeGroup.getStartRes(),
501 treeGroup.getEndRes());
503 conserv.verdict(false, altViewport.getConsPercGaps());
504 treeGroup.getGroupColourScheme().setConservation(conserv);
507 altViewport.getAlignment().addGroup(treeGroup);
508 // TODO can we push all of the below into AlignViewportI?
509 final AlignViewportI codingComplement = altViewport
510 .getCodingComplement();
511 if (codingComplement != null)
513 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
514 parentAvport, codingComplement);
515 if (mappedGroup.getSequences().size() > 0)
517 codingComplement.getAlignment().addGroup(mappedGroup);
518 for (SequenceI seq : mappedGroup.getSequences())
520 codingComplement.setSequenceColour(seq, groupColour.brighter());
531 public void showNodeSelectionOnAlign(final TreeNodeI node)
534 if (node.isInternal())
536 showMatchingChildSequences(node);
541 showMatchingSequence(node);
552 public void showMatchingSequence(final TreeNodeI nodeToMatch)
554 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
555 if (matchingSequence != null)
557 long nodeId = nodeToMatch.getId();
558 addOrRemoveInSet(treeView.getMatchingNodesIds(), nodeId);
559 treeSelectionChanged(matchingSequence);
560 parentAvport.sendSelection();
566 public void showMatchingChildSequences(final TreeNodeI parentNode)
568 // redundancy here, Forester already iterates through tree to get all
570 List<TreeNodeI> childNodes = parentNode.getAllDescendants();
573 for (TreeNodeI childNode : childNodes)
575 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
576 if (matchingSequence != null)
578 long nodeId = childNode.getId();
579 addOrRemoveInSet(treeView.getMatchingNodesIds(), nodeId);
581 treeSelectionChanged(matchingSequence);
586 parentAvport.sendSelection();
593 public void treeSelectionChanged(final SequenceI sequence)
595 if (!parentAvport.isClosed()) // alignment view could be closed
597 SequenceGroup selected = parentAvport.getSelectionGroup();
599 if (selected == null)
601 selected = new SequenceGroup();
602 parentAvport.setSelectionGroup(selected);
605 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
606 selected.addOrRemove(sequence, true);
612 public void sortByTree_actionPerformed()
615 // if (applyToAllViews)
617 final ArrayList<CommandI> commands = new ArrayList<>();
618 for (AlignmentPanel ap : PaintRefresher
619 .getAssociatedPanels(parentAvport.getSequenceSetId()))
621 commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
622 ap.alignFrame.addHistoryItem(new CommandI()
626 public void undoCommand(AlignmentI[] views)
628 for (CommandI tsort : commands)
630 tsort.undoCommand(views);
637 return commands.size();
641 public String getDescription()
643 return "Tree Sort (many views)";
647 public void doCommand(AlignmentI[] views)
650 for (CommandI tsort : commands)
652 tsort.doCommand(views);
657 ap.alignFrame.updateEditMenuBar();
662 // alignPanel.alignFrame.addHistoryItem(sortAlignmentIn(alignPanel));
668 public CommandI sortAlignmentIn(AlignmentPanel ap)
670 AlignmentViewport viewport = ap.av;
671 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
674 AlignmentSorter.sortByTree(viewport.getAlignment(),
675 nodesBoundToSequences,
678 undo = new OrderCommand("Tree Sort", oldOrder,
679 viewport.getAlignment());
681 ap.paintAlignment(true, false);
684 } catch (Exception e)
686 System.err.println(e.getMessage());
698 * @param objectToCheck
700 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
702 if (set.contains(objectToCheck))
704 set.remove(objectToCheck);
708 set.add(objectToCheck);
713 public AlignmentViewport getParentAvport()
718 public void setParentAvport(final AlignmentViewport parentAvport)
720 this.parentAvport = parentAvport;
723 public AlignmentPanel[] getAssociatedPanels()
725 return PaintRefresher
726 .getAssociatedPanels(parentAvport.getSequenceSetId());
730 public Map<SequenceI, TreeNodeI> getAlignmentWithNodes()
732 return sequencesBoundToNodes;
736 public Map<TreeNodeI, SequenceI> getNodesWithAlignment()
738 return nodesBoundToSequences;