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());
188 public void mousePressed(final MouseEvent e)
193 public void mouseReleased(MouseEvent e)
198 public void mouseEntered(MouseEvent e)
203 public void mouseExited(MouseEvent e)
209 public void selection(final SequenceGroup seqsel,
210 final ColumnSelection colsel, final HiddenColumns hidden,
211 final SelectionSource source)
213 if (source == parentAvport) // check if source is alignment from where the
216 treeView.setFoundNodes0(
217 new HashSet<Long>(seqsel.getSequences().size()));
219 for (SequenceI selectedSequence : seqsel.getSequences())
221 PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
222 if (matchingNode != null)
224 treeView.getFoundNodes0().add(matchingNode.getId());
236 * Partially refactored from TreeCanvas
238 public void partitionTree(final int x)
240 Phylogeny tree = treeView.getPhylogeny();
244 double longestBranch = tree.calculateHeight(true);
245 if (longestBranch != 0)
247 Graphics g = treeView.getGraphics();
248 int panelHeight = treeView.getHeight();
249 g.drawLine(x, 0, x, panelHeight);
251 // double relativeTreeWidth = longestBranch / viewWidth;
253 float rootX = tree.getRoot().getXcoord();
255 double threshold = ((double) x - rootX) / longestBranch;
256 List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
257 longestBranch, tree.getRoot());
265 public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
266 double treeLength, PhylogenyNode node)
269 List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
271 parentAvport.setSelectionGroup(null);
272 parentAvport.getAlignment().deleteAllGroups();
273 parentAvport.clearSequenceColours();
274 if (parentAvport.getCodingComplement() != null)
276 parentAvport.getCodingComplement().setSelectionGroup(null);
277 parentAvport.getCodingComplement().getAlignment().deleteAllGroups();
278 parentAvport.getCodingComplement().clearSequenceColours();
282 colourNodesAboveThreshold(nodesAboveThreshold, threshold, treeLength,
284 return nodesAboveThreshold;
289 * Partially refactored from TreeCanvas colourGroups (can be made nicer).
297 private List<PhylogenyNode> colourNodesAboveThreshold(
298 List<PhylogenyNode> nodeList, double threshold,
299 double treeLength, PhylogenyNode node)
301 // could also use PhylogenyMethods.getAllDescendants
302 for (PhylogenyNode childNode : node.getDescendants())
304 childNode.getBranchData()
305 .setBranchColor(new BranchColor(Color.black));
306 double nodeCutoff = childNode.calculateDistanceToRoot() / treeLength;
308 if (nodeCutoff > threshold)
310 nodeList.add(childNode);
312 Color randomColor = new Color((int) (Math.random() * 255),
313 (int) (Math.random() * 255), (int) (Math.random() * 255));
314 TreePanelUtil.colorizeSubtree(childNode,
315 new BranchColor(randomColor));
317 List<PhylogenyNode> descendantNodes = childNode
318 .getAllExternalDescendants();
319 List<SequenceI> descendantSeqs = new ArrayList<>();
320 for (PhylogenyNode descNode : descendantNodes)
322 descendantSeqs.add(nodesBoundToSequences.get(descNode));
326 SequenceGroup sg = new SequenceGroup(descendantSeqs, null, null,
327 true, true, false, 0,
328 parentAvport.getAlignment().getWidth() - 1);
330 ColourSchemeI cs = null;
331 if (parentAvport.getGlobalColourScheme() != null)
333 if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme)
335 cs = new UserColourScheme(
336 ((UserColourScheme) parentAvport.getGlobalColourScheme())
342 cs = ColourSchemeProperty.getColourScheme(sg, ColourSchemeProperty
343 .getColourName(parentAvport.getGlobalColourScheme()));
346 sg.setColourScheme(cs);
347 sg.getGroupColourScheme().setThreshold(
348 parentAvport.getResidueShading().getThreshold(),
349 parentAvport.isIgnoreGapsConsensus());
350 // sg.recalcConservation();
351 sg.setName("Tree Group:" + sg.hashCode());
352 sg.setIdColour(randomColor);
354 if (parentAvport.getGlobalColourScheme() != null
355 && parentAvport.getResidueShading().conservationApplied())
357 Conservation c = new Conservation("Group", sg.getSequences(null),
358 sg.getStartRes(), sg.getEndRes());
360 c.verdict(false, parentAvport.getConsPercGaps());
361 sg.cs.setConservation(c);
364 parentAvport.getAlignment().addGroup(new SequenceGroup(sg));
365 // TODO can we push all of the below into AlignViewportI?
366 final AlignViewportI codingComplement = parentAvport
367 .getCodingComplement();
368 if (codingComplement != null)
370 SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(sg,
373 if (mappedGroup.getSequences().size() > 0)
375 codingComplement.getAlignment().addGroup(mappedGroup);
376 for (SequenceI seq : mappedGroup.getSequences())
378 codingComplement.setSequenceColour(seq,
379 randomColor.brighter());
388 colourNodesAboveThreshold(nodeList, threshold, treeLength,
394 ((AlignViewport) parentAvport).getAlignPanel().updateAnnotation();
396 final AlignViewportI codingComplement = parentAvport
397 .getCodingComplement();
398 if (codingComplement != null)
400 ((AlignViewport) codingComplement).getAlignPanel().updateAnnotation();
410 // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
411 // double treeHeight)
413 // List<PhylogenyNode> groups = new ArrayList<>();
414 // _groupNodes(groups, root, threshold, treeHeight);
415 // System.out.println(groups);
419 // protected void _groupNodes(List<PhylogenyNode> groups, PhylogenyNode nd,
420 // float threshold, double treeHeight)
427 // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold)
433 // for (PhylogenyNode childNode : nd.getDescendants())
435 // _groupNodes(groups, childNode, threshold, treeHeight);
443 * may or may not need an extra repaint on the alignment view (check what kira
447 public void showNodeSelectionOnAlign(final PhylogenyNode node)
450 if (node.isInternal())
452 showMatchingChildSequences(node);
457 showMatchingSequence(node);
468 public void showMatchingSequence(final PhylogenyNode nodeToMatch)
470 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
471 if (matchingSequence != null)
473 long nodeId = nodeToMatch.getId();
474 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
475 treeSelectionChanged(matchingSequence);
476 parentAvport.sendSelection();
482 public void showMatchingChildSequences(final PhylogenyNode parentNode)
484 List<PhylogenyNode> childNodes = PhylogenyMethods
485 .getAllDescendants(parentNode);
488 for (PhylogenyNode childNode : childNodes)
490 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
492 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
493 if (matchingSequence != null)
495 long nodeId = childNode.getId();
496 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
498 treeSelectionChanged(matchingSequence);
503 parentAvport.sendSelection();
509 * Refactored from TreeCanvas.
512 * of the node selected in the tree viewer.
515 public void treeSelectionChanged(final SequenceI sequence)
517 if (!parentAvport.isClosed()) // alignment view could be closed
519 SequenceGroup selected = parentAvport.getSelectionGroup();
521 if (selected == null)
523 selected = new SequenceGroup();
524 parentAvport.setSelectionGroup(selected);
527 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
528 selected.addOrRemove(sequence, true);
532 public void sortByTree_actionPerformed() {
533 // parentAvport.mirrorCommand(command, undo, ssm, source);
536 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
542 * sort the associated alignment view by the current tree.
547 // public void sortByTree_actionPerformed()// modify for Aptx
550 // // if (treeCanvas.applyToAllViews)
552 // final ArrayList<CommandI> commands = new ArrayList<>();
553 // for (AlignmentPanel ap : PaintRefresher
554 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
556 // commands.add(sortAlignmentIn(ap.parentAvport.getAlignPanel()));
558 // parentAvport.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
562 // public void undoCommand(AlignmentI[] views)
564 // for (CommandI tsort : commands)
566 // tsort.undoCommand(views);
571 // public int getSize()
573 // return commands.size();
577 // public String getDescription()
579 // return "Tree Sort (many views)";
583 // public void doCommand(AlignmentI[] views)
586 // for (CommandI tsort : commands)
588 // tsort.doCommand(views);
592 // for (AlignmentPanel ap : PaintRefresher
593 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
595 // // ensure all the alignFrames refresh their GI after adding an undo item
596 // ap.alignFrame.updateEditMenuBar();
601 // treeCanvas.ap.alignFrame
602 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
611 * @param objectToCheck
613 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
615 if (set.contains(objectToCheck))
617 set.remove(objectToCheck);
621 set.add(objectToCheck);
626 public AlignmentViewport getParentAvport()
631 public void setParentAvport(final AlignmentViewport parentAvport)
633 this.parentAvport = parentAvport;