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.gui.AlignViewport;
18 import jalview.gui.AlignmentPanel;
19 import jalview.gui.Desktop;
20 import jalview.gui.JvOptionPane;
21 import jalview.gui.PaintRefresher;
22 import jalview.schemes.ColourSchemeI;
23 import jalview.schemes.ColourSchemeProperty;
24 import jalview.schemes.UserColourScheme;
25 import jalview.structure.SelectionSource;
26 import jalview.structure.StructureSelectionManager;
27 import jalview.util.MappingUtils;
28 import jalview.util.MessageManager;
29 import jalview.viewmodel.AlignmentViewport;
31 import java.awt.Color;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.InputEvent;
34 import java.awt.event.MouseEvent;
35 import java.util.ArrayList;
36 import java.util.HashSet;
37 import java.util.List;
41 import javax.swing.SwingUtilities;
42 import javax.swing.event.InternalFrameAdapter;
43 import javax.swing.event.InternalFrameEvent;
46 * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
47 * it originates from, meaning that selecting sequences in the tree viewer also
48 * selects them in the alignment view and vice versa.
50 * @author kjvanderheide
53 public final class JalviewBinding
54 implements ExternalTreeViewerBindingI
56 private final ExternalTreeFrame aptxFrame;
58 private ExternalTreePanel treeView;
60 private AlignmentViewport parentAvport;
62 private final StructureSelectionManager ssm;
64 private AlignmentPanel[] associatedPanels;
66 private Map<SequenceI, ExternalTreeNodeI> sequencesBoundToNodes;
68 private Map<ExternalTreeNodeI, SequenceI> nodesBoundToSequences;
72 private float furthestNodeX;
74 private int nrTreeGroups = 0;
76 private boolean applyToAllViews = false;
80 * @param archaeopteryx
82 * @param jalviewAlignmentViewport
83 * alignment viewport from which the tree was calculated.
85 * @param alignMappedToNodes
86 * map with sequences used to calculate the tree and matching tree
87 * nodes as key, value pair respectively.
89 * @param nodesMappedToAlign
90 * map with tree nodes and matching sequences used to calculate the
91 * tree as key, value pair respectively.
93 public JalviewBinding(final ExternalTreeFrame archaeopteryx,
94 final AlignmentViewport jalviewAlignmentViewport,
95 final Map<SequenceI, ExternalTreeNodeI> alignMappedToNodes,
96 final Map<ExternalTreeNodeI, SequenceI> nodesMappedToAlign)
99 if (archaeopteryx.getNumberOfTrees() > 1)
101 JvOptionPane.showMessageDialog(Desktop.desktop,
102 MessageManager.getString("label.tabs_detected_archaeopteryx"),
103 MessageManager.getString("label.problem_reading_tree_file"),
104 JvOptionPane.WARNING_MESSAGE);
108 // deal with/prohibit null values here as that will cause problems
109 aptxFrame = archaeopteryx;
110 parentAvport = jalviewAlignmentViewport;
111 sequencesBoundToNodes = alignMappedToNodes;
112 nodesBoundToSequences = nodesMappedToAlign;
114 treeView = archaeopteryx.getTreePanel();
115 ssm = parentAvport.getStructureSelectionManager();
117 ssm.addSelectionListener(this);
118 treeView.addMouseListener(this);
119 treeView.registerWithPaintRefresher(
120 parentAvport.getSequenceSetId());
121 associatedPanels = PaintRefresher
122 .getAssociatedPanels(parentAvport.getSequenceSetId());
124 aptxFrame.addFrameListener(new InternalFrameAdapter()
128 public void internalFrameClosed(InternalFrameEvent e)
130 AptxInit.getAllAptxFrames().remove(aptxFrame);
131 ssm.removeSelectionListener(JalviewBinding.this);
136 // treeTabs.addChangeListener(new ChangeListener()
140 // public void stateChanged(ChangeEvent e)
143 // SwingUtilities.invokeLater(new Runnable()
148 // * Resend the selection to the tree view when tabs get switched, this
149 // * has to be buried in invokeLater as Forester first resets the tree
150 // * view on switching tabs, without invokeLater this would get called
151 // * before Forester resets which would nullify the selection.
155 // treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
156 // parentAvport.sendSelection();
157 // // PaintRefresher.Refresh(treeView,
158 // // parentAvport.getSequenceSetId());
170 public void actionPerformed(ActionEvent e)
172 // aptxFrame.actionPerformed(e);
177 public void mouseClicked(MouseEvent e)
179 SwingUtilities.invokeLater(new Runnable() {
183 * invokeLater so that this always runs after Forester's mouseClicked
187 final ExternalTreeNodeI node = treeView.findNode(e.getX(),
191 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
192 // selection if shift
195 parentAvport.setSelectionGroup(null);
198 showNodeSelectionOnAlign(node);
203 partitionTree(e.getX());
205 treeView.notifyPaintRefresher(parentAvport.getSequenceSetId(),
218 public void mousePressed(final MouseEvent e)
223 public void mouseReleased(MouseEvent e)
228 public void mouseEntered(MouseEvent e)
233 public void mouseExited(MouseEvent e)
239 public void selection(final SequenceGroup seqsel,
240 final ColumnSelection colsel, final HiddenColumns hidden,
241 final SelectionSource source)
243 if (source == parentAvport) // check if source is alignment from where the
246 treeView.setMatchingNodes(
247 new HashSet<Long>(seqsel.getSequences().size()));
250 for (SequenceI selectedSequence : seqsel.getSequences())
252 ExternalTreeNodeI matchingNode = sequencesBoundToNodes
253 .get(selectedSequence);
254 if (matchingNode != null)
256 treeView.getMatchingNodes().add(matchingNode.getId());
259 // if (!matchingNode.getBranchData().isHasBranchColor())
261 // // Color foundNodesColour = treeView.getTreeColorSet()
262 // // .getFoundColor0();
263 // // matchingNode.getBranchData()
264 // // .setBranchColor(new BranchColor(foundNodesColour));
279 * Partially refactored from TreeCanvas
281 public void partitionTree(final int x)
283 ExternalTreeI tree = treeView.getTree();
287 // should be calculated on each partition as the tree can theoretically
288 // change in the meantime
289 ExternalTreeNodeI furthestNode = tree.getFurthestNode();
290 furthestNodeX = furthestNode.getXcoord();
291 rootX = tree.getRoot().getXcoord();
293 // don't bother if 0 distance tree or clicked x lies outside of tree
294 if (furthestNodeX != rootX && !(x > furthestNodeX))
296 float threshold = (x - rootX) / (furthestNodeX - rootX);
297 List<ExternalTreeNodeI> foundNodes = getNodesAboveThreshold(
307 public List<ExternalTreeNodeI> getNodesAboveThreshold(double threshold,
308 ExternalTreeNodeI node)
311 List<ExternalTreeNodeI> nodesAboveThreshold = new ArrayList<>();
313 parentAvport.setSelectionGroup(null);
314 parentAvport.getAlignment().deleteAllGroups();
315 parentAvport.clearSequenceColours();
316 if (parentAvport.getCodingComplement() != null)
318 parentAvport.getCodingComplement().setSelectionGroup(null);
319 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
320 parentAvport.getCodingComplement().clearSequenceColours();
324 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
326 return nodesAboveThreshold;
331 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
339 private List<ExternalTreeNodeI> colourNodesAboveThreshold(
340 List<ExternalTreeNodeI> nodeList, double threshold,
341 ExternalTreeNodeI node)
344 for (ExternalTreeNodeI childNode : node.getDirectChildren())
346 childNode.setBranchColor(Color.black);
347 float nodeCutoff = (childNode.getXcoord() - rootX)
348 / (furthestNodeX - rootX);
350 if (nodeCutoff > threshold)
352 nodeList.add(childNode);
354 Color randomColour = new Color((int) (Math.random() * 255),
355 (int) (Math.random() * 255), (int) (Math.random() * 255));
356 childNode.setBranchColor(randomColour);
358 List<SequenceI> groupSeqs = new ArrayList<>();
359 SequenceI seq = nodesBoundToSequences.get(childNode);
363 parentAvport.setSequenceColour(seq, randomColour);
366 List<ExternalTreeNodeI> descendantNodes = childNode
367 .getAllDescendants();
369 for (ExternalTreeNodeI descNode : descendantNodes)
371 seq = nodesBoundToSequences.get(descNode);
375 parentAvport.setSequenceColour(seq, randomColour);
378 descNode.setBranchColor(randomColour);
381 if (groupSeqs != null)
384 groupThresholdSequences(groupSeqs, randomColour);
389 colourNodesAboveThreshold(nodeList, threshold, childNode);
394 for (AlignmentPanel associatedPanel : associatedPanels) {
396 associatedPanel.updateAnnotation();
398 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
399 .getCodingComplement();
400 if (codingComplement != null)
403 ((AlignViewport) codingComplement).getAlignPanel()
412 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
415 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
416 true, true, false, 0,
417 parentAvport.getAlignment().getWidth() - 1);
419 ColourSchemeI cs = null;
420 if (parentAvport.getGlobalColourScheme() != null)
422 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
424 cs = new UserColourScheme(
425 ((UserColourScheme) parentAvport.getGlobalColourScheme())
430 cs = ColourSchemeProperty.getColourScheme(treeGroup,
431 ColourSchemeProperty.getColourName(
432 parentAvport.getGlobalColourScheme()));
436 treeGroup.setColourScheme(cs);
437 treeGroup.getGroupColourScheme().setThreshold(
438 parentAvport.getResidueShading().getThreshold(),
439 parentAvport.isIgnoreGapsConsensus());
441 treeGroup.setName("Tree Group " + nrTreeGroups);
442 treeGroup.setIdColour(groupColour);
444 for (AlignmentPanel associatedPanel : associatedPanels)
446 AlignViewportI altViewport = associatedPanel
449 if (altViewport.getGlobalColourScheme() != null
450 && altViewport.getResidueShading()
451 .conservationApplied())
453 Conservation conserv = new Conservation(treeGroup.getName(),
454 treeGroup.getSequences(null), treeGroup.getStartRes(),
455 treeGroup.getEndRes());
457 conserv.verdict(false, altViewport.getConsPercGaps());
458 treeGroup.getGroupColourScheme().setConservation(conserv);
461 altViewport.getAlignment().addGroup(treeGroup);
462 // TODO can we push all of the below into AlignViewportI?
463 final AlignViewportI codingComplement = altViewport
464 .getCodingComplement();
465 if (codingComplement != null)
467 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
468 parentAvport, codingComplement);
469 if (mappedGroup.getSequences().size() > 0)
471 codingComplement.getAlignment().addGroup(mappedGroup);
472 for (SequenceI seq : mappedGroup.getSequences())
474 codingComplement.setSequenceColour(seq, groupColour.brighter());
484 * may or may not need an extra repaint on the alignment view (check what kira
488 public void showNodeSelectionOnAlign(final ExternalTreeNodeI node)
491 if (node.isInternal())
493 showMatchingChildSequences(node);
498 showMatchingSequence(node);
509 public void showMatchingSequence(final ExternalTreeNodeI nodeToMatch)
511 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
512 if (matchingSequence != null)
514 long nodeId = nodeToMatch.getId();
515 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
516 treeSelectionChanged(matchingSequence);
517 parentAvport.sendSelection();
523 public void showMatchingChildSequences(final ExternalTreeNodeI parentNode)
525 // redundancy here, Forester already iterates through tree to get all
527 List<ExternalTreeNodeI> childNodes = parentNode.getAllDescendants();
530 for (ExternalTreeNodeI childNode : childNodes)
532 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
534 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
535 if (matchingSequence != null)
537 long nodeId = childNode.getId();
538 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
540 treeSelectionChanged(matchingSequence);
545 parentAvport.sendSelection();
551 * Refactored from TreeCanvas.
554 * of the node selected in the tree viewer.
557 public void treeSelectionChanged(final SequenceI sequence)
559 if (!parentAvport.isClosed()) // alignment view could be closed
561 SequenceGroup selected = parentAvport.getSelectionGroup();
563 if (selected == null)
565 selected = new SequenceGroup();
566 parentAvport.setSelectionGroup(selected);
569 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
570 selected.addOrRemove(sequence, true);
576 public void sortByTree_actionPerformed()// modify for Aptx
579 // if (treeCanvas.applyToAllViews)
581 // final ArrayList<CommandI> commands = new ArrayList<>();
582 // for (AlignmentPanel ap : PaintRefresher
583 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
585 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
587 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
591 // public void undoCommand(AlignmentI[] views)
593 // for (CommandI tsort : commands)
595 // tsort.undoCommand(views);
600 // public int getSize()
602 // return commands.size();
606 // public String getDescription()
608 // return "Tree Sort (many views)";
612 // public void doCommand(AlignmentI[] views)
615 // for (CommandI tsort : commands)
617 // tsort.doCommand(views);
621 // for (AlignmentPanel ap : PaintRefresher
622 // .getAssociatedPanels(av.getSequenceSetId()))
624 // // ensure all the alignFrames refresh their GI after adding an undo item
625 // ap.alignFrame.updateEditMenuBar();
630 // treeCanvas.ap.alignFrame
631 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
637 public CommandI sortAlignmentIn(AlignmentPanel ap)
639 // TODO: move to alignment view controller
641 AlignmentViewport viewport = ap.av;
642 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
645 AlignmentSorter.sortByTree(viewport.getAlignment(),
646 nodesBoundToSequences,
649 undo = new OrderCommand("Tree Sort", oldOrder,
650 viewport.getAlignment());
652 ap.paintAlignment(true, false);
655 } catch (Exception e)
657 System.err.println(e.getMessage());
669 * @param objectToCheck
671 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
673 if (set.contains(objectToCheck))
675 set.remove(objectToCheck);
679 set.add(objectToCheck);
684 public AlignmentViewport getParentAvport()
689 public void setParentAvport(final AlignmentViewport parentAvport)
691 this.parentAvport = parentAvport;
694 public AlignmentPanel[] getAssociatedPanels()
696 return associatedPanels;
699 public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
701 this.associatedPanels = associatedPanels;