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);
120 PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
121 associatedPanels = PaintRefresher
122 .getAssociatedPanels(parentAvport.getSequenceSetId());
124 aptxFrame.addInternalFrameListener(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 PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
217 public void mousePressed(final MouseEvent e)
222 public void mouseReleased(MouseEvent e)
227 public void mouseEntered(MouseEvent e)
232 public void mouseExited(MouseEvent e)
238 public void selection(final SequenceGroup seqsel,
239 final ColumnSelection colsel, final HiddenColumns hidden,
240 final SelectionSource source)
242 if (source == parentAvport) // check if source is alignment from where the
245 treeView.setMatchingNodes(
246 new HashSet<Long>(seqsel.getSequences().size()));
249 for (SequenceI selectedSequence : seqsel.getSequences())
251 ExternalTreeNodeI matchingNode = sequencesBoundToNodes
252 .get(selectedSequence);
253 if (matchingNode != null)
255 treeView.getMatchingNodes().add(matchingNode.getId());
258 // if (!matchingNode.getBranchData().isHasBranchColor())
260 // // Color foundNodesColour = treeView.getTreeColorSet()
261 // // .getFoundColor0();
262 // // matchingNode.getBranchData()
263 // // .setBranchColor(new BranchColor(foundNodesColour));
278 * Partially refactored from TreeCanvas
280 public void partitionTree(final int x)
282 ExternalTreeI tree = treeView.getTree();
286 // should be calculated on each partition as the tree can theoretically
287 // change in the meantime
288 ExternalTreeNodeI furthestNode = tree.getFurthestNode();
289 furthestNodeX = furthestNode.getXcoord();
290 rootX = tree.getRoot().getXcoord();
292 // don't bother if 0 distance tree or clicked x lies outside of tree
293 if (furthestNodeX != rootX && !(x > furthestNodeX))
295 float threshold = (x - rootX) / (furthestNodeX - rootX);
296 List<ExternalTreeNodeI> foundNodes = getNodesAboveThreshold(
306 public List<ExternalTreeNodeI> getNodesAboveThreshold(double threshold,
307 ExternalTreeNodeI node)
310 List<ExternalTreeNodeI> nodesAboveThreshold = new ArrayList<>();
312 parentAvport.setSelectionGroup(null);
313 parentAvport.getAlignment().deleteAllGroups();
314 parentAvport.clearSequenceColours();
315 if (parentAvport.getCodingComplement() != null)
317 parentAvport.getCodingComplement().setSelectionGroup(null);
318 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
319 parentAvport.getCodingComplement().clearSequenceColours();
323 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
325 return nodesAboveThreshold;
330 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
338 private List<ExternalTreeNodeI> colourNodesAboveThreshold(
339 List<ExternalTreeNodeI> nodeList, double threshold,
340 ExternalTreeNodeI node)
343 for (ExternalTreeNodeI childNode : node.getDirectChildren())
345 childNode.setBranchColor(Color.black);
346 float nodeCutoff = (childNode.getXcoord() - rootX)
347 / (furthestNodeX - rootX);
349 if (nodeCutoff > threshold)
351 nodeList.add(childNode);
353 Color randomColour = new Color((int) (Math.random() * 255),
354 (int) (Math.random() * 255), (int) (Math.random() * 255));
355 childNode.setBranchColor(randomColour);
357 List<SequenceI> groupSeqs = new ArrayList<>();
358 SequenceI seq = nodesBoundToSequences.get(childNode);
362 parentAvport.setSequenceColour(seq, randomColour);
365 List<ExternalTreeNodeI> descendantNodes = childNode
366 .getAllDescendants();
368 for (ExternalTreeNodeI descNode : descendantNodes)
370 seq = nodesBoundToSequences.get(descNode);
374 parentAvport.setSequenceColour(seq, randomColour);
377 descNode.setBranchColor(randomColour);
380 if (groupSeqs != null)
383 groupThresholdSequences(groupSeqs, randomColour);
388 colourNodesAboveThreshold(nodeList, threshold, childNode);
393 for (AlignmentPanel associatedPanel : associatedPanels) {
395 associatedPanel.updateAnnotation();
397 final AlignViewportI codingComplement = associatedPanel.getAlignViewport()
398 .getCodingComplement();
399 if (codingComplement != null)
402 ((AlignViewport) codingComplement).getAlignPanel()
411 public void groupThresholdSequences(List<SequenceI> groupedSeqs,
414 SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null,
415 true, true, false, 0,
416 parentAvport.getAlignment().getWidth() - 1);
418 ColourSchemeI cs = null;
419 if (parentAvport.getGlobalColourScheme() != null)
421 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
423 cs = new UserColourScheme(
424 ((UserColourScheme) parentAvport.getGlobalColourScheme())
429 cs = ColourSchemeProperty.getColourScheme(treeGroup,
430 ColourSchemeProperty.getColourName(
431 parentAvport.getGlobalColourScheme()));
435 treeGroup.setColourScheme(cs);
436 treeGroup.getGroupColourScheme().setThreshold(
437 parentAvport.getResidueShading().getThreshold(),
438 parentAvport.isIgnoreGapsConsensus());
440 treeGroup.setName("Tree Group " + nrTreeGroups);
441 treeGroup.setIdColour(groupColour);
443 for (AlignmentPanel associatedPanel : associatedPanels)
445 AlignViewportI altViewport = associatedPanel
448 if (altViewport.getGlobalColourScheme() != null
449 && altViewport.getResidueShading()
450 .conservationApplied())
452 Conservation conserv = new Conservation(treeGroup.getName(),
453 treeGroup.getSequences(null), treeGroup.getStartRes(),
454 treeGroup.getEndRes());
456 conserv.verdict(false, altViewport.getConsPercGaps());
457 treeGroup.getGroupColourScheme().setConservation(conserv);
460 altViewport.getAlignment().addGroup(treeGroup);
461 // TODO can we push all of the below into AlignViewportI?
462 final AlignViewportI codingComplement = altViewport
463 .getCodingComplement();
464 if (codingComplement != null)
466 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup,
467 parentAvport, codingComplement);
468 if (mappedGroup.getSequences().size() > 0)
470 codingComplement.getAlignment().addGroup(mappedGroup);
471 for (SequenceI seq : mappedGroup.getSequences())
473 codingComplement.setSequenceColour(seq, groupColour.brighter());
483 * may or may not need an extra repaint on the alignment view (check what kira
487 public void showNodeSelectionOnAlign(final ExternalTreeNodeI node)
490 if (node.isInternal())
492 showMatchingChildSequences(node);
497 showMatchingSequence(node);
508 public void showMatchingSequence(final ExternalTreeNodeI nodeToMatch)
510 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
511 if (matchingSequence != null)
513 long nodeId = nodeToMatch.getId();
514 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
515 treeSelectionChanged(matchingSequence);
516 parentAvport.sendSelection();
522 public void showMatchingChildSequences(final ExternalTreeNodeI parentNode)
524 // redundancy here, Forester already iterates through tree to get all
526 List<ExternalTreeNodeI> childNodes = parentNode.getAllDescendants();
529 for (ExternalTreeNodeI childNode : childNodes)
531 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
533 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
534 if (matchingSequence != null)
536 long nodeId = childNode.getId();
537 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
539 treeSelectionChanged(matchingSequence);
544 parentAvport.sendSelection();
550 * Refactored from TreeCanvas.
553 * of the node selected in the tree viewer.
556 public void treeSelectionChanged(final SequenceI sequence)
558 if (!parentAvport.isClosed()) // alignment view could be closed
560 SequenceGroup selected = parentAvport.getSelectionGroup();
562 if (selected == null)
564 selected = new SequenceGroup();
565 parentAvport.setSelectionGroup(selected);
568 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
569 selected.addOrRemove(sequence, true);
575 public void sortByTree_actionPerformed()// modify for Aptx
578 // if (treeCanvas.applyToAllViews)
580 // final ArrayList<CommandI> commands = new ArrayList<>();
581 // for (AlignmentPanel ap : PaintRefresher
582 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
584 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
586 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
590 // public void undoCommand(AlignmentI[] views)
592 // for (CommandI tsort : commands)
594 // tsort.undoCommand(views);
599 // public int getSize()
601 // return commands.size();
605 // public String getDescription()
607 // return "Tree Sort (many views)";
611 // public void doCommand(AlignmentI[] views)
614 // for (CommandI tsort : commands)
616 // tsort.doCommand(views);
620 // for (AlignmentPanel ap : PaintRefresher
621 // .getAssociatedPanels(av.getSequenceSetId()))
623 // // ensure all the alignFrames refresh their GI after adding an undo item
624 // ap.alignFrame.updateEditMenuBar();
629 // treeCanvas.ap.alignFrame
630 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
636 public CommandI sortAlignmentIn(AlignmentPanel ap)
638 // TODO: move to alignment view controller
640 AlignmentViewport viewport = ap.av;
641 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
644 AlignmentSorter.sortByTree(viewport.getAlignment(),
645 nodesBoundToSequences,
648 undo = new OrderCommand("Tree Sort", oldOrder,
649 viewport.getAlignment());
651 ap.paintAlignment(true, false);
654 } catch (Exception e)
656 System.err.println(e.getMessage());
668 * @param objectToCheck
670 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
672 if (set.contains(objectToCheck))
674 set.remove(objectToCheck);
678 set.add(objectToCheck);
683 public AlignmentViewport getParentAvport()
688 public void setParentAvport(final AlignmentViewport parentAvport)
690 this.parentAvport = parentAvport;
693 public AlignmentPanel[] getAssociatedPanels()
695 return associatedPanels;
698 public void setAssociatedPanels(AlignmentPanel[] associatedPanels)
700 this.associatedPanels = associatedPanels;