1 package jalview.ext.archaeopteryx;
3 import jalview.analysis.Conservation;
4 import jalview.api.AlignViewportI;
5 import jalview.datamodel.ColumnSelection;
6 import jalview.datamodel.HiddenColumns;
7 import jalview.datamodel.SequenceGroup;
8 import jalview.datamodel.SequenceI;
9 import jalview.ext.treeviewer.ExternalTreeViewerBindingI;
10 import jalview.gui.AlignViewport;
11 import jalview.gui.Desktop;
12 import jalview.gui.JvOptionPane;
13 import jalview.gui.PaintRefresher;
14 import jalview.schemes.ColourSchemeI;
15 import jalview.schemes.ColourSchemeProperty;
16 import jalview.schemes.UserColourScheme;
17 import jalview.structure.SelectionSource;
18 import jalview.structure.StructureSelectionManager;
19 import jalview.util.MappingUtils;
20 import jalview.util.MessageManager;
21 import jalview.viewmodel.AlignmentViewport;
23 import java.awt.Color;
24 import java.awt.Graphics;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.InputEvent;
27 import java.awt.event.MouseEvent;
28 import java.util.ArrayList;
29 import java.util.HashSet;
30 import java.util.List;
34 import javax.swing.JTabbedPane;
35 import javax.swing.SwingUtilities;
36 import javax.swing.event.ChangeEvent;
37 import javax.swing.event.ChangeListener;
39 import org.forester.archaeopteryx.MainFrame;
40 import org.forester.archaeopteryx.TreePanelUtil;
41 import org.forester.phylogeny.Phylogeny;
42 import org.forester.phylogeny.PhylogenyMethods;
43 import org.forester.phylogeny.PhylogenyNode;
44 import org.forester.phylogeny.data.BranchColor;
47 * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
48 * it originates from, meaning that selecting sequences in the tree viewer also
49 * selects them in the alignment view and vice versa.
51 * @author kjvanderheide
54 public final class JalviewBinding
55 implements ExternalTreeViewerBindingI<PhylogenyNode>
57 private org.forester.archaeopteryx.TreePanel treeView;
59 private AlignmentViewport parentAvport;
61 private final JTabbedPane treeTabs;
63 private final StructureSelectionManager ssm;
65 private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
67 private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
71 private float furthestNodeX;
75 * @param archaeopteryx
77 * @param jalviewAlignmentViewport
78 * alignment viewport from which the tree was calculated.
80 * @param alignMappedToNodes
81 * map with sequences used to calculate the tree and matching tree
82 * nodes as key, value pair respectively.
84 * @param nodesMappedToAlign
85 * map with tree nodes and matching sequences used to calculate the
86 * tree as key, value pair respectively.
88 public JalviewBinding(final MainFrame archaeopteryx,
89 final AlignmentViewport jalviewAlignmentViewport,
90 final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
91 final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
94 if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1)
96 JvOptionPane.showMessageDialog(Desktop.desktop,
97 MessageManager.getString("label.tabs_detected_archaeopteryx"),
98 MessageManager.getString("label.problem_reading_tree_file"),
99 JvOptionPane.WARNING_MESSAGE);
103 // deal with/prohibit null values here as that will cause problems
104 parentAvport = jalviewAlignmentViewport;
105 sequencesBoundToNodes = alignMappedToNodes;
106 nodesBoundToSequences = nodesMappedToAlign;
108 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
109 treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
110 ssm = parentAvport.getStructureSelectionManager();
112 ssm.addSelectionListener(this);
113 treeView.addMouseListener(this);
114 PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
117 treeTabs.addChangeListener(new ChangeListener()
121 public void stateChanged(ChangeEvent e)
124 SwingUtilities.invokeLater(new Runnable()
129 * Resend the selection to the tree view when tabs get switched, this
130 * has to be buried in invokeLater as Forester first resets the tree
131 * view on switching tabs, without invokeLater this would get called
132 * before Forester resets which would nullify the selection.
136 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
137 parentAvport.sendSelection();
138 // PaintRefresher.Refresh(treeView,
139 // parentAvport.getSequenceSetId());
151 public void actionPerformed(ActionEvent e)
156 public void mouseClicked(MouseEvent e)
158 SwingUtilities.invokeLater(new Runnable() {
162 * invokeLater so that this always runs after Forester's mouseClicked
166 final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
169 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
170 // selection if shift
173 parentAvport.setSelectionGroup(null);
176 showNodeSelectionOnAlign(node);
181 partitionTree(e.getX());
183 PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
194 public void mousePressed(final MouseEvent e)
199 public void mouseReleased(MouseEvent e)
204 public void mouseEntered(MouseEvent e)
209 public void mouseExited(MouseEvent e)
215 public void selection(final SequenceGroup seqsel,
216 final ColumnSelection colsel, final HiddenColumns hidden,
217 final SelectionSource source)
219 if (source == parentAvport) // check if source is alignment from where the
222 treeView.setFoundNodes0(
223 new HashSet<Long>(seqsel.getSequences().size()));
225 for (SequenceI selectedSequence : seqsel.getSequences())
227 PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
228 if (matchingNode != null)
230 treeView.getFoundNodes0().add(matchingNode.getId());
233 if (!matchingNode.getBranchData().isHasBranchColor())
235 Color foundNodesColour = treeView.getTreeColorSet()
237 matchingNode.getBranchData()
238 .setBranchColor(new BranchColor(foundNodesColour));
253 * Partially refactored from TreeCanvas
255 public void partitionTree(final int x)
257 Phylogeny tree = treeView.getPhylogeny();
261 // should be calculated on each partition as the tree can theoretically
262 // change in the meantime
263 PhylogenyNode furthestNode = PhylogenyMethods
264 .calculateNodeWithMaxDistanceToRoot(tree);
265 furthestNodeX = furthestNode.getXcoord();
266 rootX = tree.getRoot().getXcoord();
268 if (furthestNodeX != rootX && x < furthestNodeX) // don't bother if 0
270 // clicked x lies outside
273 Graphics g = treeView.getGraphics();
274 int panelHeight = treeView.getHeight();
275 g.drawLine(x, 0, x, panelHeight);
277 float threshold = (x - rootX) / (furthestNodeX - rootX);
278 List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
287 public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
291 List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
293 parentAvport.setSelectionGroup(null);
294 parentAvport.getAlignment().deleteAllGroups();
295 parentAvport.clearSequenceColours();
296 if (parentAvport.getCodingComplement() != null)
298 parentAvport.getCodingComplement().setSelectionGroup(null);
299 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
300 parentAvport.getCodingComplement().clearSequenceColours();
304 colourNodesAboveThreshold(nodesAboveThreshold, threshold,
306 return nodesAboveThreshold;
311 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
319 private List<PhylogenyNode> colourNodesAboveThreshold(
320 List<PhylogenyNode> nodeList, double threshold,
323 // could also use PhylogenyMethods.getAllDescendants
324 for (PhylogenyNode childNode : node.getDescendants())
326 childNode.getBranchData()
327 .setBranchColor(new BranchColor(Color.black));
328 float nodeCutoff = (childNode.getXcoord() - rootX)
329 / (furthestNodeX - rootX);
331 if (nodeCutoff > threshold)
333 nodeList.add(childNode);
335 Color randomColor = new Color((int) (Math.random() * 255),
336 (int) (Math.random() * 255), (int) (Math.random() * 255));
337 TreePanelUtil.colorizeSubtree(childNode,
338 new BranchColor(randomColor));
339 List<PhylogenyNode> descendantNodes = childNode
340 .getAllExternalDescendants();
341 List<SequenceI> descendantSeqs = new ArrayList<>(); // .forEach instead?
342 for (PhylogenyNode descNode : descendantNodes)
344 descendantSeqs.add(nodesBoundToSequences.get(descNode));
347 SequenceGroup sg = new SequenceGroup(descendantSeqs, null, null,
348 true, true, false, 0,
349 parentAvport.getAlignment().getWidth() - 1);
351 ColourSchemeI cs = null;
352 if (parentAvport.getGlobalColourScheme() != null)
354 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
356 cs = new UserColourScheme(
357 ((UserColourScheme) parentAvport.getGlobalColourScheme())
362 cs = ColourSchemeProperty.getColourScheme(sg, ColourSchemeProperty
363 .getColourName(parentAvport.getGlobalColourScheme()));
366 sg.setColourScheme(cs);
367 sg.getGroupColourScheme().setThreshold(
368 parentAvport.getResidueShading().getThreshold(),
369 parentAvport.isIgnoreGapsConsensus());
370 // sg.recalcConservation();
371 sg.setName("Tree Group:" + sg.hashCode());
372 sg.setIdColour(randomColor);
374 if (parentAvport.getGlobalColourScheme() != null
375 && parentAvport.getResidueShading().conservationApplied())
377 Conservation c = new Conservation("Group", sg.getSequences(null),
378 sg.getStartRes(), sg.getEndRes());
380 c.verdict(false, parentAvport.getConsPercGaps());
381 sg.cs.setConservation(c);
384 parentAvport.getAlignment().addGroup(new SequenceGroup(sg));
385 // TODO can we push all of the below into AlignViewportI?
386 final AlignViewportI codingComplement = parentAvport
387 .getCodingComplement();
388 if (codingComplement != null)
390 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
393 if (mappedGroup.getSequences().size() > 0)
395 codingComplement.getAlignment().addGroup(mappedGroup);
396 for (SequenceI seq : mappedGroup.getSequences())
398 codingComplement.setSequenceColour(seq,
399 randomColor.brighter());
409 colourNodesAboveThreshold(nodeList, threshold,
415 ((AlignViewport) parentAvport).getAlignPanel().updateAnnotation();
417 final AlignViewportI codingComplement = parentAvport
418 .getCodingComplement();
419 if (codingComplement != null)
421 ((AlignViewport) codingComplement).getAlignPanel().updateAnnotation();
430 // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
431 // double treeHeight)
433 // List<PhylogenyNode> groups = new ArrayList<>();
434 // _groupNodes(groups, root, threshold, treeHeight);
435 // System.out.println(groups);
439 // protected void _groupNodes(List<PhylogenyNode> groups, PhylogenyNode nd,
440 // float threshold, double treeHeight)
447 // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold)
453 // for (PhylogenyNode childNode : nd.getDescendants())
455 // _groupNodes(groups, childNode, threshold, treeHeight);
463 * may or may not need an extra repaint on the alignment view (check what kira
467 public void showNodeSelectionOnAlign(final PhylogenyNode node)
470 if (node.isInternal())
472 showMatchingChildSequences(node);
477 showMatchingSequence(node);
488 public void showMatchingSequence(final PhylogenyNode nodeToMatch)
490 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
491 if (matchingSequence != null)
493 long nodeId = nodeToMatch.getId();
494 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
495 treeSelectionChanged(matchingSequence);
496 parentAvport.sendSelection();
502 public void showMatchingChildSequences(final PhylogenyNode parentNode)
504 List<PhylogenyNode> childNodes = PhylogenyMethods
505 .getAllDescendants(parentNode);
508 for (PhylogenyNode childNode : childNodes)
510 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
512 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
513 if (matchingSequence != null)
515 long nodeId = childNode.getId();
516 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
518 treeSelectionChanged(matchingSequence);
523 parentAvport.sendSelection();
529 * Refactored from TreeCanvas.
532 * of the node selected in the tree viewer.
535 public void treeSelectionChanged(final SequenceI sequence)
537 if (!parentAvport.isClosed()) // alignment view could be closed
539 SequenceGroup selected = parentAvport.getSelectionGroup();
541 if (selected == null)
543 selected = new SequenceGroup();
544 parentAvport.setSelectionGroup(selected);
547 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
548 selected.addOrRemove(sequence, true);
552 public void sortByTree_actionPerformed() {
553 // parentAvport.mirrorCommand(command, undo, ssm, source);
556 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
562 * sort the associated alignment view by the current tree.
567 // public void sortByTree_actionPerformed()// modify for Aptx
570 // // if (treeCanvas.applyToAllViews)
572 // final ArrayList<CommandI> commands = new ArrayList<>();
573 // for (AlignmentPanel ap : PaintRefresher
574 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
576 // commands.add(sortAlignmentIn(ap.parentAvport.getAlignPanel()));
578 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
582 // public void undoCommand(AlignmentI[] views)
584 // for (CommandI tsort : commands)
586 // tsort.undoCommand(views);
591 // public int getSize()
593 // return commands.size();
597 // public String getDescription()
599 // return "Tree Sort (many views)";
603 // public void doCommand(AlignmentI[] views)
606 // for (CommandI tsort : commands)
608 // tsort.doCommand(views);
612 // for (AlignmentPanel ap : PaintRefresher
613 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
615 // // ensure all the alignFrames refresh their GI after adding an undo item
616 // ap.alignFrame.updateEditMenuBar();
621 // treeCanvas.ap.alignFrame
622 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
631 * @param objectToCheck
633 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
635 if (set.contains(objectToCheck))
637 set.remove(objectToCheck);
641 set.add(objectToCheck);
646 public AlignmentViewport getParentAvport()
651 public void setParentAvport(final AlignmentViewport parentAvport)
653 this.parentAvport = parentAvport;