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.associateLeavesToSequences();
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.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(
352 public List<TreeNodeI> getNodesAboveThreshold(float threshold,
356 List<TreeNodeI> nodesAboveThreshold = new ArrayList<>();
358 parentAvport.setSelectionGroup(null);
359 parentAvport.getAlignment().deleteAllGroups();
360 parentAvport.clearSequenceColours();
361 if (parentAvport.getCodingComplement() != null)
363 parentAvport.getCodingComplement().setSelectionGroup(null);
364 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
365 parentAvport.getCodingComplement().clearSequenceColours();
369 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
371 return nodesAboveThreshold;
376 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
384 private List<TreeNodeI> colourNodesAboveThreshold(
385 List<TreeNodeI> nodeList, float threshold,
389 for (TreeNodeI childNode : node.getDirectChildren())
391 childNode.setBranchColor(Color.black);
392 float nodeCutoff = (childNode.getXcoord() - rootX)
393 / (furthestNodeX - rootX);
395 if (nodeCutoff > threshold)
397 nodeList.add(childNode);
399 Color randomColour = new Color((int) (Math.random() * 255),
400 (int) (Math.random() * 255), (int) (Math.random() * 255));
401 childNode.setBranchColor(randomColour);
403 List<SequenceI> groupSeqs = new ArrayList<>();
404 SequenceI seq = nodesBoundToSequences.get(childNode);
408 parentAvport.setSequenceColour(seq, randomColour);
411 List<TreeNodeI> descendantNodes = childNode
412 .getAllDescendants();
414 for (TreeNodeI descNode : descendantNodes)
416 seq = nodesBoundToSequences.get(descNode);
420 parentAvport.setSequenceColour(seq, randomColour);
423 descNode.setBranchColor(randomColour);
426 if (groupSeqs != null)
429 groupThresholdSequences(groupSeqs, randomColour);
434 colourNodesAboveThreshold(nodeList, threshold, childNode);
438 for (AlignmentPanel associatedPanel : getAssociatedPanels())
441 associatedPanel.updateAnnotation();
443 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
444 .getCodingComplement();
445 if (codingComplement != null)
448 ((AlignViewport) codingComplement).getAlignPanel()
457 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
460 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
461 true, true, false, 0,
462 parentAvport.getAlignment().getWidth() - 1);
464 ColourSchemeI cs = null;
465 if (parentAvport.getGlobalColourScheme() != null)
467 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
469 cs = new UserColourScheme(
470 ((UserColourScheme) parentAvport.getGlobalColourScheme())
475 cs = ColourSchemeProperty.getColourScheme(treeGroup,
476 ColourSchemeProperty.getColourName(
477 parentAvport.getGlobalColourScheme()));
481 treeGroup.setColourScheme(cs);
482 treeGroup.getGroupColourScheme().setThreshold(
483 parentAvport.getResidueShading().getThreshold(),
484 parentAvport.isIgnoreGapsConsensus());
486 treeGroup.setName("Tree Group " + nrTreeGroups);
487 treeGroup.setIdColour(groupColour);
489 for (AlignmentPanel associatedPanel : getAssociatedPanels())
491 AlignViewportI altViewport = associatedPanel
494 if (altViewport.getGlobalColourScheme() != null
495 && altViewport.getResidueShading()
496 .conservationApplied())
498 Conservation conserv = new Conservation(treeGroup.getName(),
499 treeGroup.getSequences(null), treeGroup.getStartRes(),
500 treeGroup.getEndRes());
502 conserv.verdict(false, altViewport.getConsPercGaps());
503 treeGroup.getGroupColourScheme().setConservation(conserv);
506 altViewport.getAlignment().addGroup(treeGroup);
507 // TODO can we push all of the below into AlignViewportI?
508 final AlignViewportI codingComplement = altViewport
509 .getCodingComplement();
510 if (codingComplement != null)
512 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
513 parentAvport, codingComplement);
514 if (mappedGroup.getSequences().size() > 0)
516 codingComplement.getAlignment().addGroup(mappedGroup);
517 for (SequenceI seq : mappedGroup.getSequences())
519 codingComplement.setSequenceColour(seq, groupColour.brighter());
530 public void showNodeSelectionOnAlign(final TreeNodeI node)
533 if (node.isInternal())
535 showMatchingChildSequences(node);
540 showMatchingSequence(node);
551 public void showMatchingSequence(final TreeNodeI nodeToMatch)
553 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
554 if (matchingSequence != null)
556 long nodeId = nodeToMatch.getId();
557 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
558 treeSelectionChanged(matchingSequence);
559 parentAvport.sendSelection();
565 public void showMatchingChildSequences(final TreeNodeI parentNode)
567 // redundancy here, Forester already iterates through tree to get all
569 List<TreeNodeI> childNodes = parentNode.getAllDescendants();
572 for (TreeNodeI childNode : childNodes)
574 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
576 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
577 if (matchingSequence != null)
579 long nodeId = childNode.getId();
580 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
582 treeSelectionChanged(matchingSequence);
587 parentAvport.sendSelection();
593 * Refactored from TreeCanvas.
596 * of the node selected in the tree viewer.
599 public void treeSelectionChanged(final SequenceI sequence)
601 if (!parentAvport.isClosed()) // alignment view could be closed
603 SequenceGroup selected = parentAvport.getSelectionGroup();
605 if (selected == null)
607 selected = new SequenceGroup();
608 parentAvport.setSelectionGroup(selected);
611 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
612 selected.addOrRemove(sequence, true);
618 public void sortByTree_actionPerformed()
621 // if (applyToAllViews)
623 final ArrayList<CommandI> commands = new ArrayList<>();
624 for (AlignmentPanel ap : PaintRefresher
625 .getAssociatedPanels(parentAvport.getSequenceSetId()))
627 commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
628 ap.alignFrame.addHistoryItem(new CommandI()
632 public void undoCommand(AlignmentI[] views)
634 for (CommandI tsort : commands)
636 tsort.undoCommand(views);
643 return commands.size();
647 public String getDescription()
649 return "Tree Sort (many views)";
653 public void doCommand(AlignmentI[] views)
656 for (CommandI tsort : commands)
658 tsort.doCommand(views);
663 ap.alignFrame.updateEditMenuBar();
668 // alignPanel.alignFrame.addHistoryItem(sortAlignmentIn(alignPanel));
674 public CommandI sortAlignmentIn(AlignmentPanel ap)
676 AlignmentViewport viewport = ap.av;
677 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
680 AlignmentSorter.sortByTree(viewport.getAlignment(),
681 nodesBoundToSequences,
684 undo = new OrderCommand("Tree Sort", oldOrder,
685 viewport.getAlignment());
687 ap.paintAlignment(true, false);
690 } catch (Exception e)
692 System.err.println(e.getMessage());
704 * @param objectToCheck
706 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
708 if (set.contains(objectToCheck))
710 set.remove(objectToCheck);
714 set.add(objectToCheck);
719 public AlignmentViewport getParentAvport()
724 public void setParentAvport(final AlignmentViewport parentAvport)
726 this.parentAvport = parentAvport;
729 public AlignmentPanel[] getAssociatedPanels()
731 return PaintRefresher
732 .getAssociatedPanels(parentAvport.getSequenceSetId());