1 package jalview.ext.archaeopteryx;
3 import jalview.datamodel.ColumnSelection;
4 import jalview.datamodel.HiddenColumns;
5 import jalview.datamodel.SequenceGroup;
6 import jalview.datamodel.SequenceI;
7 import jalview.ext.treeviewer.ExternalTreeViewerBindingI;
8 import jalview.gui.Desktop;
9 import jalview.gui.JvOptionPane;
10 import jalview.gui.PaintRefresher;
11 import jalview.structure.SelectionSource;
12 import jalview.structure.StructureSelectionManager;
13 import jalview.util.MessageManager;
14 import jalview.viewmodel.AlignmentViewport;
16 import java.awt.Color;
17 import java.awt.Graphics;
18 import java.awt.event.ActionEvent;
19 import java.awt.event.InputEvent;
20 import java.awt.event.MouseEvent;
21 import java.util.ArrayList;
22 import java.util.HashSet;
23 import java.util.List;
27 import javax.swing.JTabbedPane;
28 import javax.swing.SwingUtilities;
29 import javax.swing.event.ChangeEvent;
30 import javax.swing.event.ChangeListener;
32 import org.forester.archaeopteryx.MainFrame;
33 import org.forester.archaeopteryx.TreePanelUtil;
34 import org.forester.phylogeny.Phylogeny;
35 import org.forester.phylogeny.PhylogenyMethods;
36 import org.forester.phylogeny.PhylogenyNode;
37 import org.forester.phylogeny.data.BranchColor;
40 * Class for binding the Archaeopteryx tree viewer to the Jalview alignment that
41 * it originates from, meaning that selecting sequences in the tree viewer also
42 * selects them in the alignment view and vice versa.
44 * @author kjvanderheide
47 public final class JalviewBinding
48 implements ExternalTreeViewerBindingI<PhylogenyNode>
50 private org.forester.archaeopteryx.TreePanel treeView;
52 private AlignmentViewport parentAvport;
54 private final JTabbedPane treeTabs;
56 private final StructureSelectionManager ssm;
58 private Map<SequenceI, PhylogenyNode> sequencesBoundToNodes;
60 private Map<PhylogenyNode, SequenceI> nodesBoundToSequences;
64 * @param archaeopteryx
66 * @param jalviewAlignmentViewport
67 * alignment viewport from which the tree was calculated.
69 * @param alignMappedToNodes
70 * map with sequences used to calculate the tree and matching tree
71 * nodes as key, value pair respectively.
73 * @param nodesMappedToAlign
74 * map with tree nodes and matching sequences used to calculate the
75 * tree as key, value pair respectively.
77 public JalviewBinding(final MainFrame archaeopteryx,
78 final AlignmentViewport jalviewAlignmentViewport,
79 final Map<SequenceI, PhylogenyNode> alignMappedToNodes,
80 final Map<PhylogenyNode, SequenceI> nodesMappedToAlign)
83 if (archaeopteryx.getMainPanel().getTabbedPane().getTabCount() > 1)
85 JvOptionPane.showMessageDialog(Desktop.desktop,
86 MessageManager.getString("label.tabs_detected_archaeopteryx"),
87 MessageManager.getString("label.problem_reading_tree_file"),
88 JvOptionPane.WARNING_MESSAGE);
92 // deal with/prohibit null values here as that will cause problems
93 parentAvport = jalviewAlignmentViewport;
94 sequencesBoundToNodes = alignMappedToNodes;
95 nodesBoundToSequences = nodesMappedToAlign;
97 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
98 treeTabs = archaeopteryx.getMainPanel().getTabbedPane();
99 ssm = parentAvport.getStructureSelectionManager();
101 ssm.addSelectionListener(this);
102 treeView.addMouseListener(this);
103 PaintRefresher.Register(treeView, parentAvport.getSequenceSetId());
106 treeTabs.addChangeListener(new ChangeListener()
110 public void stateChanged(ChangeEvent e)
113 SwingUtilities.invokeLater(new Runnable()
118 * Resend the selection to the tree view when tabs get switched, this
119 * has to be buried in invokeLater as Forester first resets the tree
120 * view on switching tabs, without invokeLater this would get called
121 * before Forester resets which would nullify the selection.
125 treeView = archaeopteryx.getMainPanel().getCurrentTreePanel();
126 parentAvport.sendSelection();
127 // PaintRefresher.Refresh(treeView,
128 // parentAvport.getSequenceSetId());
140 public void actionPerformed(ActionEvent e)
145 public void mouseClicked(MouseEvent e)
147 SwingUtilities.invokeLater(new Runnable() {
151 * invokeLater so that this always runs after Forester's mouseClicked
155 final PhylogenyNode node = treeView.findNode(e.getX(), e.getY());
158 if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous
159 // selection if shift
162 parentAvport.setSelectionGroup(null);
165 showNodeSelectionOnAlign(node);
169 partitionTree(e.getX());
171 PaintRefresher.Refresh(treeView, parentAvport.getSequenceSetId());
179 public void mousePressed(final MouseEvent e)
184 public void mouseReleased(MouseEvent e)
189 public void mouseEntered(MouseEvent e)
194 public void mouseExited(MouseEvent e)
200 public void selection(final SequenceGroup seqsel,
201 final ColumnSelection colsel, final HiddenColumns hidden,
202 final SelectionSource source)
204 if (source == parentAvport) // check if source is alignment from where the
207 treeView.setFoundNodes0(
208 new HashSet<Long>(seqsel.getSequences().size()));
210 for (SequenceI selectedSequence : seqsel.getSequences())
212 PhylogenyNode matchingNode = sequencesBoundToNodes.get(selectedSequence);
213 if (matchingNode != null)
215 treeView.getFoundNodes0().add(matchingNode.getId());
227 * Partially refactored from TreeCanvas
229 public void partitionTree(final int x)
231 Phylogeny tree = treeView.getPhylogeny();
235 double longestBranch = tree.calculateHeight(true);
236 if (longestBranch != 0)
238 Graphics g = treeView.getGraphics();
239 int panelHeight = treeView.getHeight();
240 g.drawLine(x, 0, x, panelHeight);
242 // double relativeTreeWidth = treeDepth / viewWidth;
244 float rootX = tree.getRoot().getXcoord();
245 double threshold = ((double) x - rootX) / longestBranch;
246 List<PhylogenyNode> foundNodes = getNodesAboveThreshold(threshold,
247 longestBranch, tree.getRoot());
261 public List<PhylogenyNode> getNodesAboveThreshold(double threshold,
262 double treeLength, PhylogenyNode node)
265 List<PhylogenyNode> nodesAboveThreshold = new ArrayList<>();
267 // could also use PhylogenyMethods.getAllDescendants
268 colourNodesAboveThreshold(nodesAboveThreshold, threshold, treeLength,
270 return nodesAboveThreshold;
274 private List<PhylogenyNode> colourNodesAboveThreshold(
275 List<PhylogenyNode> nodeList, double threshold,
276 double treeLength, PhylogenyNode node)
278 for (PhylogenyNode childNode : node.getDescendants())
280 double nodeCutoff = childNode.calculateDistanceToRoot() / treeLength;
282 if (nodeCutoff > threshold)
284 nodeList.add(childNode);
286 Color randomColor = new Color((int) (Math.random() * 255),
287 (int) (Math.random() * 255), (int) (Math.random() * 255));
288 TreePanelUtil.colorizeSubtree(childNode,
289 new BranchColor(randomColor));
290 List<PhylogenyNode> descendantNodes = childNode
291 .getAllExternalDescendants();
292 List<SequenceI> descendantSeqs = new ArrayList<>();
293 for (PhylogenyNode descNode : descendantNodes)
295 descendantSeqs.add(nodesBoundToSequences.get(descNode));
298 SequenceGroup sg = new SequenceGroup(descendantSeqs, null, null,
299 true, true, false, 0,
300 parentAvport.getAlignment().getWidth() - 1);
301 sg.setIdColour(randomColor);
307 colourNodesAboveThreshold(nodeList, threshold, treeLength,
318 // public List<PhylogenyNode> groupNodes(float threshold, PhylogenyNode root,
319 // double treeHeight)
321 // List<PhylogenyNode> groups = new ArrayList<>();
322 // _groupNodes(groups, root, threshold, treeHeight);
323 // System.out.println(groups);
327 // protected void _groupNodes(List<PhylogenyNode> groups, PhylogenyNode nd,
328 // float threshold, double treeHeight)
335 // if ((nd.calculateDistanceToRoot() / treeHeight) > threshold)
341 // for (PhylogenyNode childNode : nd.getDescendants())
343 // _groupNodes(groups, childNode, threshold, treeHeight);
351 * may or may not need an extra repaint on the alignment view (check what kira
355 public void showNodeSelectionOnAlign(final PhylogenyNode node)
358 if (node.isInternal())
360 showMatchingChildSequences(node);
365 showMatchingSequence(node);
376 public void showMatchingSequence(final PhylogenyNode nodeToMatch)
378 SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch);
379 if (matchingSequence != null)
381 long nodeId = nodeToMatch.getId();
382 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
383 treeSelectionChanged(matchingSequence);
384 parentAvport.sendSelection();
390 public void showMatchingChildSequences(final PhylogenyNode parentNode)
392 List<PhylogenyNode> childNodes = PhylogenyMethods
393 .getAllDescendants(parentNode);
396 for (PhylogenyNode childNode : childNodes)
398 // childNode.getBranchData().setBranchColor(new BranchColor(Color.BLUE));
400 SequenceI matchingSequence = nodesBoundToSequences.get(childNode);
401 if (matchingSequence != null)
403 long nodeId = childNode.getId();
404 addOrRemoveInSet(treeView.getFoundNodes0(), nodeId);
406 treeSelectionChanged(matchingSequence);
411 parentAvport.sendSelection();
417 * Refactored from TreeCanvas.
420 * of the node selected in the tree viewer.
423 public void treeSelectionChanged(final SequenceI sequence)
425 if (!parentAvport.isClosed()) // alignment view could be closed
427 SequenceGroup selected = parentAvport.getSelectionGroup();
429 if (selected == null)
431 selected = new SequenceGroup();
432 parentAvport.setSelectionGroup(selected);
435 selected.setEndRes(parentAvport.getAlignment().getWidth() - 1);
436 selected.addOrRemove(sequence, true);
440 public void sortByTree_actionPerformed() {
441 // parentAvport.mirrorCommand(command, undo, ssm, source);
444 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
450 * sort the associated alignment view by the current tree.
455 // public void sortByTree_actionPerformed()// modify for Aptx
458 // // if (treeCanvas.applyToAllViews)
460 // final ArrayList<CommandI> commands = new ArrayList<>();
461 // for (AlignmentPanel ap : PaintRefresher
462 // .getAssociatedPanels(parentAvport.getSequenceSetId()))
464 // commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
466 // av.getAlignPanel().alignFrame.addHistoryItem(new CommandI()
470 // public void undoCommand(AlignmentI[] views)
472 // for (CommandI tsort : commands)
474 // tsort.undoCommand(views);
479 // public int getSize()
481 // return commands.size();
485 // public String getDescription()
487 // return "Tree Sort (many views)";
491 // public void doCommand(AlignmentI[] views)
494 // for (CommandI tsort : commands)
496 // tsort.doCommand(views);
500 // for (AlignmentPanel ap : PaintRefresher
501 // .getAssociatedPanels(av.getSequenceSetId()))
503 // // ensure all the alignFrames refresh their GI after adding an undo item
504 // ap.alignFrame.updateEditMenuBar();
509 // treeCanvas.ap.alignFrame
510 // .addHistoryItem(sortAlignmentIn(treeCanvas.ap));
519 * @param objectToCheck
521 public static <E> void addOrRemoveInSet(Set<E> set, E objectToCheck)
523 if (set.contains(objectToCheck))
525 set.remove(objectToCheck);
529 set.add(objectToCheck);
534 public AlignmentViewport getParentAvport()
539 public void setParentAvport(final AlignmentViewport parentAvport)
541 this.parentAvport = parentAvport;