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());
529 * may or may not need an extra repaint on the alignment view (check what kira
533 public void showNodeSelectionOnAlign(final TreeNodeI node)
536 if (node.isInternal())
538 showMatchingChildSequences(node);
543 showMatchingSequence(node);
554 public void showMatchingSequence(final TreeNodeI nodeToMatch)
556 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
557 if (matchingSequence != null)
559 long nodeId = nodeToMatch.getId();
560 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
561 treeSelectionChanged(matchingSequence);
562 parentAvport.sendSelection();
568 public void showMatchingChildSequences(final TreeNodeI parentNode)
570 // redundancy here, Forester already iterates through tree to get all
572 List<TreeNodeI> childNodes = parentNode.getAllDescendants();
575 for (TreeNodeI childNode : childNodes)
577 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
579 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
580 if (matchingSequence != null)
582 long nodeId = childNode.getId();
583 addOrRemoveInSet(treeView.getMatchingNodes(), nodeId);
585 treeSelectionChanged(matchingSequence);
590 parentAvport.sendSelection();
596 * Refactored from TreeCanvas.
599 * of the node selected in the tree viewer.
602 public void treeSelectionChanged(final SequenceI sequence)
604 if (!parentAvport.isClosed()) // alignment view could be closed
606 SequenceGroup selected = parentAvport.getSelectionGroup();
608 if (selected == null)
610 selected = new SequenceGroup();
611 parentAvport.setSelectionGroup(selected);
614 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
615 selected.addOrRemove(sequence, true);
621 public void sortByTree_actionPerformed()
624 // if (applyToAllViews)
626 final ArrayList<CommandI> commands = new ArrayList<>();
627 for (AlignmentPanel ap : PaintRefresher
628 .getAssociatedPanels(parentAvport.getSequenceSetId()))
630 commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
631 ap.alignFrame.addHistoryItem(new CommandI()
635 public void undoCommand(AlignmentI[] views)
637 for (CommandI tsort : commands)
639 tsort.undoCommand(views);
646 return commands.size();
650 public String getDescription()
652 return "Tree Sort (many views)";
656 public void doCommand(AlignmentI[] views)
659 for (CommandI tsort : commands)
661 tsort.doCommand(views);
666 ap.alignFrame.updateEditMenuBar();
671 // alignPanel.alignFrame.addHistoryItem(sortAlignmentIn(alignPanel));
677 public CommandI sortAlignmentIn(AlignmentPanel ap)
679 AlignmentViewport viewport = ap.av;
680 SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
683 AlignmentSorter.sortByTree(viewport.getAlignment(),
684 nodesBoundToSequences,
687 undo = new OrderCommand("Tree Sort", oldOrder,
688 viewport.getAlignment());
690 ap.paintAlignment(true, false);
693 } catch (Exception e)
695 System.err.println(e.getMessage());
707 * @param objectToCheck
709 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
711 if (set.contains(objectToCheck))
713 set.remove(objectToCheck);
717 set.add(objectToCheck);
722 public AlignmentViewport getParentAvport()
727 public void setParentAvport(final AlignmentViewport parentAvport)
729 this.parentAvport = parentAvport;
732 public AlignmentPanel[] getAssociatedPanels()
734 return PaintRefresher
735 .getAssociatedPanels(parentAvport.getSequenceSetId());