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.ColumnSelection;
9 import jalview.datamodel.HiddenColumns;
10 import jalview.datamodel.SequenceGroup;
11 import jalview.datamodel.SequenceI;
12 import jalview.ext.treeviewer.ExternalTreeFrame;
13 import jalview.ext.treeviewer.ExternalTreeI;
14 import jalview.ext.treeviewer.ExternalTreeNodeI;
15 import jalview.ext.treeviewer.ExternalTreePanel;
16 import jalview.ext.treeviewer.ExternalTreeViewerBindingI;
17 import jalview.ext.treeviewer.LoadedTreeSequenceAssociation;
18 import jalview.gui.AlignViewport;
19 import jalview.gui.AlignmentPanel;
20 import jalview.gui.Desktop;
21 import jalview.gui.JvOptionPane;
22 import jalview.gui.PaintRefresher;
23 import jalview.schemes.ColourSchemeI;
24 import jalview.schemes.ColourSchemeProperty;
25 import jalview.schemes.UserColourScheme;
26 import jalview.structure.SelectionSource;
27 import jalview.structure.StructureSelectionManager;
28 import jalview.util.MappingUtils;
29 import jalview.util.MessageManager;
30 import jalview.viewmodel.AlignmentViewport;
32 import java.awt.Color;
33 import java.awt.Rectangle;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.InputEvent;
36 import java.awt.event.MouseEvent;
37 import java.util.ArrayList;
38 import java.util.HashSet;
39 import java.util.List;
43 import javax.swing.SwingUtilities;
44 import javax.swing.event.InternalFrameAdapter;
45 import javax.swing.event.InternalFrameEvent;
48 * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
49 * it originates from, meaning that selecting sequences in the tree viewer also
50 * selects them in the alignment view and vice versa.
52 * @author kjvanderheide
55 public final class JalviewBinding
56 implements ExternalTreeViewerBindingI
58 private final ExternalTreeFrame aptxFrame;
60 private ExternalTreePanel treeView;
62 private AlignmentViewport parentAvport;
64 private final StructureSelectionManager ssm;
66 private AlignmentPanel[] associatedPanels;
68 private Map<SequenceI, ExternalTreeNodeI> sequencesBoundToNodes;
70 private Map<ExternalTreeNodeI, 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 ExternalTreeFrame archaeopteryx,
96 final AlignmentViewport jalviewAlignmentViewport,
97 final Map<SequenceI, ExternalTreeNodeI> alignMappedToNodes,
98 final Map<ExternalTreeNodeI, 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());
123 associatedPanels = PaintRefresher
124 .getAssociatedPanels(parentAvport.getSequenceSetId());
126 aptxFrame.addFrameListener(new InternalFrameAdapter()
130 public void internalFrameClosed(InternalFrameEvent e)
132 AptxInit.getAllAptxFrames().remove(aptxFrame);
133 ssm.removeSelectionListener(JalviewBinding.this);
138 // treeTabs.addChangeListener(new ChangeListener()
142 // public void stateChanged(ChangeEvent e)
145 // SwingUtilities.invokeLater(new Runnable()
150 // * Resend the selection to the tree view when tabs get switched, this
151 // * has to be buried in invokeLater as Forester first resets the tree
152 // * view on switching tabs, without invokeLater this would get called
153 // * before Forester resets which would nullify the selection.
157 // treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
158 // parentAvport.sendSelection();
159 // // PaintRefresher.Refresh(treeView,
160 // // parentAvport.getSequenceSetId());
172 public void actionPerformed(ActionEvent e)
174 // reset hidden sequences first
175 parentAvport.showAllHiddenSeqs();
177 if (treeView.showingSubTree())
179 LoadedTreeSequenceAssociation bindAptxNodes = new LoadedTreeSequenceAssociation(
180 parentAvport.getAlignment().getSequencesArray(),
182 bindAptxNodes.associateLeavesToSequences();
183 sequencesBoundToNodes = bindAptxNodes.getAlignmentWithNodes();
184 nodesBoundToSequences = bindAptxNodes.getNodesWithAlignment();
185 AptxInit.bindNodesToJalviewSequences(aptxFrame, parentAvport,
186 sequencesBoundToNodes, nodesBoundToSequences);
189 for (SequenceI seq : parentAvport.getAlignment().getSequencesArray())
191 if (!sequencesBoundToNodes.containsKey(seq))
193 parentAvport.hideSequence(new SequenceI[] { seq });
201 Rectangle visibleView = treeView.getVisibleArea();
203 for (ExternalTreeNodeI node : treeView.getTree().getRoot()
204 .getAllDescendants())
206 if (!(node.getXcoord() > visibleView.getMinX()
207 && node.getXcoord() < visibleView.getMaxX()
208 && node.getYcoord() > visibleView.getMinY()
209 && node.getYcoord() < visibleView.getMaxY()))
212 .hideSequence(new SequenceI[]
213 { nodesBoundToSequences.get(node) });
224 public void mouseClicked(MouseEvent e)
226 SwingUtilities.invokeLater(new Runnable() {
230 * invokeLater so that this always runs after Forester's mouseClicked
234 final ExternalTreeNodeI node = treeView.findNode(e.getX(),
238 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
239 // selection if shift
242 parentAvport.setSelectionGroup(null);
245 showNodeSelectionOnAlign(node);
250 partitionTree(e.getX());
252 treeView.notifyPaintRefresher(parentAvport.getSequenceSetId(),
265 public void mousePressed(final MouseEvent e)
270 public void mouseReleased(MouseEvent e)
275 public void mouseEntered(MouseEvent e)
280 public void mouseExited(MouseEvent e)
286 public void selection(final SequenceGroup seqsel,
287 final ColumnSelection colsel, final HiddenColumns hidden,
288 final SelectionSource source)
290 if (source == parentAvport) // check if source is alignment from where the
293 treeView.setMatchingNodes(
294 new HashSet<Long>(seqsel.getSequences().size()));
297 for (SequenceI selectedSequence : seqsel.getSequences())
299 ExternalTreeNodeI matchingNode = sequencesBoundToNodes
300 .get(selectedSequence);
301 if (matchingNode != null)
303 treeView.getMatchingNodes().add(matchingNode.getId());
306 // if (!matchingNode.getBranchData().isHasBranchColor())
308 // // Color foundNodesColour = treeView.getTreeColorSet()
309 // // .getFoundColor0();
310 // // matchingNode.getBranchData()
311 // // .setBranchColor(new BranchColor(foundNodesColour));
326 * Partially refactored from TreeCanvas
328 public void partitionTree(final int x)
330 ExternalTreeI tree = treeView.getTree();
334 // should be calculated on each partition as the tree can theoretically
335 // change in the meantime
336 ExternalTreeNodeI furthestNode = tree.getFurthestNode();
337 furthestNodeX = furthestNode.getXcoord();
338 rootX = tree.getRoot().getXcoord();
340 // don't bother if 0 distance tree or clicked x lies outside of tree
341 if (furthestNodeX != rootX && !(x > furthestNodeX))
343 float threshold = (x - rootX) / (furthestNodeX - rootX);
344 List<ExternalTreeNodeI> foundNodes = getNodesAboveThreshold(
354 public List<ExternalTreeNodeI> getNodesAboveThreshold(double threshold,
355 ExternalTreeNodeI node)
358 List<ExternalTreeNodeI> nodesAboveThreshold = new ArrayList<>();
360 parentAvport.setSelectionGroup(null);
361 parentAvport.getAlignment().deleteAllGroups();
362 parentAvport.clearSequenceColours();
363 if (parentAvport.getCodingComplement() != null)
365 parentAvport.getCodingComplement().setSelectionGroup(null);
366 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
367 parentAvport.getCodingComplement().clearSequenceColours();
371 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
373 return nodesAboveThreshold;
378 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
386 private List<ExternalTreeNodeI> colourNodesAboveThreshold(
387 List<ExternalTreeNodeI> nodeList, double threshold,
388 ExternalTreeNodeI node)
391 for (ExternalTreeNodeI childNode : node.getDirectChildren())
393 childNode.setBranchColor(Color.black);
394 float nodeCutoff = (childNode.getXcoord() - rootX)
395 / (furthestNodeX - rootX);
397 if (nodeCutoff > threshold)
399 nodeList.add(childNode);
401 Color randomColour = new Color((int) (Math.random() * 255),
402 (int) (Math.random() * 255), (int) (Math.random() * 255));
403 childNode.setBranchColor(randomColour);
405 List<SequenceI> groupSeqs = new ArrayList<>();
406 SequenceI seq = nodesBoundToSequences.get(childNode);
410 parentAvport.setSequenceColour(seq, randomColour);
413 List<ExternalTreeNodeI> descendantNodes = childNode
414 .getAllDescendants();
416 for (ExternalTreeNodeI descNode : descendantNodes)
418 seq = nodesBoundToSequences.get(descNode);
422 parentAvport.setSequenceColour(seq, randomColour);
425 descNode.setBranchColor(randomColour);
428 if (groupSeqs != null)
431 groupThresholdSequences(groupSeqs, randomColour);
436 colourNodesAboveThreshold(nodeList, threshold, childNode);
441 for (AlignmentPanel associatedPanel : associatedPanels) {
443 associatedPanel.updateAnnotation();
445 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
446 .getCodingComplement();
447 if (codingComplement != null)
450 ((AlignViewport) codingComplement).getAlignPanel()
459 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
462 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
463 true, true, false, 0,
464 parentAvport.getAlignment().getWidth() - 1);
466 ColourSchemeI cs = null;
467 if (parentAvport.getGlobalColourScheme() != null)
469 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
471 cs = new UserColourScheme(
472 ((UserColourScheme) parentAvport.getGlobalColourScheme())
477 cs = ColourSchemeProperty.getColourScheme(treeGroup,
478 ColourSchemeProperty.getColourName(
479 parentAvport.getGlobalColourScheme()));
483 treeGroup.setColourScheme(cs);
484 treeGroup.getGroupColourScheme().setThreshold(
485 parentAvport.getResidueShading().getThreshold(),
486 parentAvport.isIgnoreGapsConsensus());
488 treeGroup.setName("Tree Group " + nrTreeGroups);
489 treeGroup.setIdColour(groupColour);
491 for (AlignmentPanel associatedPanel : associatedPanels)
493 AlignViewportI altViewport = associatedPanel
496 if (altViewport.getGlobalColourScheme() != null
497 && altViewport.getResidueShading()
498 .conservationApplied())
500 Conservation conserv = new Conservation(treeGroup.getName(),
501 treeGroup.getSequences(null), treeGroup.getStartRes(),
502 treeGroup.getEndRes());
504 conserv.verdict(false, altViewport.getConsPercGaps());
505 treeGroup.getGroupColourScheme().setConservation(conserv);
508 altViewport.getAlignment().addGroup(treeGroup);
509 // TODO can we push all of the below into AlignViewportI?
510 final AlignViewportI codingComplement = altViewport
511 .getCodingComplement();
512 if (codingComplement != null)
514 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
515 parentAvport, codingComplement);
516 if (mappedGroup.getSequences().size() > 0)
518 codingComplement.getAlignment().addGroup(mappedGroup);
519 for (SequenceI seq : mappedGroup.getSequences())
521 codingComplement.setSequenceColour(seq, groupColour.brighter());
531 * may or may not need an extra repaint on the alignment view (check what kira
535 public void showNodeSelectionOnAlign(final ExternalTreeNodeI node)
538 if (node.isInternal())
540 showMatchingChildSequences(node);
545 showMatchingSequence(node);
556 public void showMatchingSequence(final ExternalTreeNodeI nodeToMatch)
558 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
559 if (matchingSequence != null)
561 long nodeId = nodeToMatch.getId();
562 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
563 treeSelectionChanged(matchingSequence);
564 parentAvport.sendSelection();
570 public void showMatchingChildSequences(final ExternalTreeNodeI parentNode)
572 // redundancy here, Forester already iterates through tree to get all
574 List<ExternalTreeNodeI> childNodes = parentNode.getAllDescendants();
577 for (ExternalTreeNodeI childNode : childNodes)
579 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
581 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
582 if (matchingSequence != null)
584 long nodeId = childNode.getId();
585 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
587 treeSelectionChanged(matchingSequence);
592 parentAvport.sendSelection();
598 * Refactored from TreeCanvas.
601 * of the node selected in the tree viewer.
604 public void treeSelectionChanged(final SequenceI sequence)
606 if (!parentAvport.isClosed()) // alignment view could be closed
608 SequenceGroup selected = parentAvport.getSelectionGroup();
610 if (selected == null)
612 selected = new SequenceGroup();
613 parentAvport.setSelectionGroup(selected);
616 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
617 selected.addOrRemove(sequence, true);
623 public void sortByTree_actionPerformed()// modify for Aptx
626 // if (treeCanvas.applyToAllViews)
628 // final ArrayList<CommandI> commands = new ArrayList<>();
629 // for (AlignmentPanel ap : PaintRefresher
630 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
632 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
634 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
638 // public void undoCommand(AlignmentI[] views)
640 // for (CommandI tsort : commands)
642 // tsort.undoCommand(views);
647 // public int getSize()
649 // return commands.size();
653 // public String getDescription()
655 // return "Tree Sort (many views)";
659 // public void doCommand(AlignmentI[] views)
662 // for (CommandI tsort : commands)
664 // tsort.doCommand(views);
668 // for (AlignmentPanel ap : PaintRefresher
669 // .getAssociatedPanels(av.getSequenceSetId()))
671 // // ensure all the alignFrames refresh their GI after adding an undo item
672 // ap.alignFrame.updateEditMenuBar();
677 // treeCanvas.ap.alignFrame
678 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
684 public CommandI sortAlignmentIn(AlignmentPanel ap)
686 // TODO: move to alignment view controller
688 AlignmentViewport viewport = ap.av;
689 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
692 AlignmentSorter.sortByTree(viewport.getAlignment(),
693 nodesBoundToSequences,
696 undo = new OrderCommand("Tree Sort", oldOrder,
697 viewport.getAlignment());
699 ap.paintAlignment(true, false);
702 } catch (Exception e)
704 System.err.println(e.getMessage());
716 * @param objectToCheck
718 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
720 if (set.contains(objectToCheck))
722 set.remove(objectToCheck);
726 set.add(objectToCheck);
731 public AlignmentViewport getParentAvport()
736 public void setParentAvport(final AlignmentViewport parentAvport)
738 this.parentAvport = parentAvport;
741 public AlignmentPanel[] getAssociatedPanels()
743 return associatedPanels;
746 public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
748 this.associatedPanels = associatedPanels;