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 * @param archaeopteryx
73 * @param jalviewAlignmentViewport
74 * alignment viewport from which the tree was calculated.
76 * @param alignMappedToNodes
77 * map with sequences used to calculate the tree and matching tree
78 * nodes as key, value pair respectively.
80 * @param nodesMappedToAlign
81 * map with tree nodes and matching sequences used to calculate the
82 * tree as key, value pair respectively.
84 public JalviewBinding(final MainFrame archaeopteryx,
85 final AlignmentViewport jalviewAlignmentViewport,
86 final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
87 final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
90 if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1)
92 JvOptionPane.showMessageDialog(Desktop.desktop,
93 MessageManager.getString("label.tabs_detected_archaeopteryx"),
94 MessageManager.getString("label.problem_reading_tree_file"),
95 JvOptionPane.WARNING_MESSAGE);
99 // deal with/prohibit null values here as that will cause problems
100 parentAvport = jalviewAlignmentViewport;
101 sequencesBoundToNodes = alignMappedToNodes;
102 nodesBoundToSequences = nodesMappedToAlign;
104 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
105 treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
106 ssm = parentAvport.getStructureSelectionManager();
108 ssm.addSelectionListener(this);
109 treeView.addMouseListener(this);
110 PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
113 treeTabs.addChangeListener(new ChangeListener()
117 public void stateChanged(ChangeEvent e)
120 SwingUtilities.invokeLater(new Runnable()
125 * Resend the selection to the tree view when tabs get switched, this
126 * has to be buried in invokeLater as Forester first resets the tree
127 * view on switching tabs, without invokeLater this would get called
128 * before Forester resets which would nullify the selection.
132 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
133 parentAvport.sendSelection();
134 // PaintRefresher.Refresh(treeView,
135 // parentAvport.getSequenceSetId());
147 public void actionPerformed(ActionEvent e)
152 public void mouseClicked(MouseEvent e)
154 SwingUtilities.invokeLater(new Runnable() {
158 * invokeLater so that this always runs after Forester's mouseClicked
162 final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
165 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
166 // selection if shift
169 parentAvport.setSelectionGroup(null);
172 showNodeSelectionOnAlign(node);
177 partitionTree(e.getX());
179 PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
190 public void mousePressed(final MouseEvent e)
195 public void mouseReleased(MouseEvent e)
200 public void mouseEntered(MouseEvent e)
205 public void mouseExited(MouseEvent e)
211 public void selection(final SequenceGroup seqsel,
212 final ColumnSelection colsel, final HiddenColumns hidden,
213 final SelectionSource source)
215 if (source == parentAvport) // check if source is alignment from where the
218 treeView.setFoundNodes0(
219 new HashSet<Long>(seqsel.getSequences().size()));
221 for (SequenceI selectedSequence : seqsel.getSequences())
223 PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
224 if (matchingNode != null)
226 treeView.getFoundNodes0().add(matchingNode.getId());
239 * Partially refactored from TreeCanvas
241 public void partitionTree(final int x)
243 Phylogeny tree = treeView.getPhylogeny();
247 double longestBranch = tree.calculateHeight(true);
248 if (longestBranch != 0)
251 // double relativeTreeWidth = longestBranch / viewWidth;
253 Graphics g = treeView.getGraphics();
254 int panelHeight = treeView.getHeight();
255 g.drawLine(x, 0, x, panelHeight);
257 float rootX = tree.getRoot().getXcoord();
258 double threshold = ((double) x - rootX) / longestBranch;
259 List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
260 longestBranch, tree.getRoot());
268 public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
269 double treeLength, PhylogenyNode node)
272 List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
274 parentAvport.setSelectionGroup(null);
275 parentAvport.getAlignment().deleteAllGroups();
276 parentAvport.clearSequenceColours();
277 if (parentAvport.getCodingComplement() != null)
279 parentAvport.getCodingComplement().setSelectionGroup(null);
280 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
281 parentAvport.getCodingComplement().clearSequenceColours();
285 colourNodesAboveThreshold(nodesAboveThreshold, threshold, treeLength,
287 return nodesAboveThreshold;
292 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
300 private List<PhylogenyNode> colourNodesAboveThreshold(
301 List<PhylogenyNode> nodeList, double threshold,
302 double treeLength, PhylogenyNode node)
304 // could also use PhylogenyMethods.getAllDescendants
305 for (PhylogenyNode childNode : node.getDescendants())
307 childNode.getBranchData()
308 .setBranchColor(new BranchColor(Color.black));
309 double nodeCutoff = childNode.calculateDistanceToRoot() / treeLength;
311 if (nodeCutoff > threshold)
313 nodeList.add(childNode);
315 Color randomColor = new Color((int) (Math.random() * 255),
316 (int) (Math.random() * 255), (int) (Math.random() * 255));
317 TreePanelUtil.colorizeSubtree(childNode,
318 new BranchColor(randomColor));
320 List<PhylogenyNode> descendantNodes = childNode
321 .getAllExternalDescendants();
322 List<SequenceI> descendantSeqs = new ArrayList<>();
323 for (PhylogenyNode descNode : descendantNodes)
325 descendantSeqs.add(nodesBoundToSequences.get(descNode));
329 SequenceGroup sg = new SequenceGroup(descendantSeqs, null, null,
330 true, true, false, 0,
331 parentAvport.getAlignment().getWidth() - 1);
333 ColourSchemeI cs = null;
334 if (parentAvport.getGlobalColourScheme() != null)
336 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
338 cs = new UserColourScheme(
339 ((UserColourScheme) parentAvport.getGlobalColourScheme())
345 cs = ColourSchemeProperty.getColourScheme(sg, ColourSchemeProperty
346 .getColourName(parentAvport.getGlobalColourScheme()));
349 sg.setColourScheme(cs);
350 sg.getGroupColourScheme().setThreshold(
351 parentAvport.getResidueShading().getThreshold(),
352 parentAvport.isIgnoreGapsConsensus());
353 // sg.recalcConservation();
354 sg.setName("Tree Group:" + sg.hashCode());
355 sg.setIdColour(randomColor);
357 if (parentAvport.getGlobalColourScheme() != null
358 && parentAvport.getResidueShading().conservationApplied())
360 Conservation c = new Conservation("Group", sg.getSequences(null),
361 sg.getStartRes(), sg.getEndRes());
363 c.verdict(false, parentAvport.getConsPercGaps());
364 sg.cs.setConservation(c);
367 parentAvport.getAlignment().addGroup(new SequenceGroup(sg));
368 // TODO can we push all of the below into AlignViewportI?
369 final AlignViewportI codingComplement = parentAvport
370 .getCodingComplement();
371 if (codingComplement != null)
373 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
376 if (mappedGroup.getSequences().size() > 0)
378 codingComplement.getAlignment().addGroup(mappedGroup);
379 for (SequenceI seq : mappedGroup.getSequences())
381 codingComplement.setSequenceColour(seq,
382 randomColor.brighter());
391 colourNodesAboveThreshold(nodeList, threshold, treeLength,
397 ((AlignViewport) parentAvport).getAlignPanel().updateAnnotation();
399 final AlignViewportI codingComplement = parentAvport
400 .getCodingComplement();
401 if (codingComplement != null)
403 ((AlignViewport) codingComplement).getAlignPanel().updateAnnotation();
413 // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
414 // double treeHeight)
416 // List<PhylogenyNode> groups = new ArrayList<>();
417 // _groupNodes(groups, root, threshold, treeHeight);
418 // System.out.println(groups);
422 // protected void _groupNodes(List<PhylogenyNode> groups, PhylogenyNode nd,
423 // float threshold, double treeHeight)
430 // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold)
436 // for (PhylogenyNode childNode : nd.getDescendants())
438 // _groupNodes(groups, childNode, threshold, treeHeight);
446 * may or may not need an extra repaint on the alignment view (check what kira
450 public void showNodeSelectionOnAlign(final PhylogenyNode node)
453 if (node.isInternal())
455 showMatchingChildSequences(node);
460 showMatchingSequence(node);
471 public void showMatchingSequence(final PhylogenyNode nodeToMatch)
473 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
474 if (matchingSequence != null)
476 long nodeId = nodeToMatch.getId();
477 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
478 treeSelectionChanged(matchingSequence);
479 parentAvport.sendSelection();
485 public void showMatchingChildSequences(final PhylogenyNode parentNode)
487 List<PhylogenyNode> childNodes = PhylogenyMethods
488 .getAllDescendants(parentNode);
491 for (PhylogenyNode childNode : childNodes)
493 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
495 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
496 if (matchingSequence != null)
498 long nodeId = childNode.getId();
499 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
501 treeSelectionChanged(matchingSequence);
506 parentAvport.sendSelection();
512 * Refactored from TreeCanvas.
515 * of the node selected in the tree viewer.
518 public void treeSelectionChanged(final SequenceI sequence)
520 if (!parentAvport.isClosed()) // alignment view could be closed
522 SequenceGroup selected = parentAvport.getSelectionGroup();
524 if (selected == null)
526 selected = new SequenceGroup();
527 parentAvport.setSelectionGroup(selected);
530 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
531 selected.addOrRemove(sequence, true);
535 public void sortByTree_actionPerformed() {
536 // parentAvport.mirrorCommand(command, undo, ssm, source);
539 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
545 * sort the associated alignment view by the current tree.
550 // public void sortByTree_actionPerformed()// modify for Aptx
553 // // if (treeCanvas.applyToAllViews)
555 // final ArrayList<CommandI> commands = new ArrayList<>();
556 // for (AlignmentPanel ap : PaintRefresher
557 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
559 // commands.add(sortAlignmentIn(ap.parentAvport.getAlignPanel()));
561 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
565 // public void undoCommand(AlignmentI[] views)
567 // for (CommandI tsort : commands)
569 // tsort.undoCommand(views);
574 // public int getSize()
576 // return commands.size();
580 // public String getDescription()
582 // return "Tree Sort (many views)";
586 // public void doCommand(AlignmentI[] views)
589 // for (CommandI tsort : commands)
591 // tsort.doCommand(views);
595 // for (AlignmentPanel ap : PaintRefresher
596 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
598 // // ensure all the alignFrames refresh their GI after adding an undo item
599 // ap.alignFrame.updateEditMenuBar();
604 // treeCanvas.ap.alignFrame
605 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
614 * @param objectToCheck
616 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
618 if (set.contains(objectToCheck))
620 set.remove(objectToCheck);
624 set.add(objectToCheck);
629 public AlignmentViewport getParentAvport()
634 public void setParentAvport(final AlignmentViewport parentAvport)
636 this.parentAvport = parentAvport;