X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2Fjalview%2Fext%2Ftreeviewer%2FJalviewBinding.java;fp=src%2Fjalview%2Fext%2Ftreeviewer%2FJalviewBinding.java;h=fd1735e418e77e79abbc2c107ca3897b5028259b;hb=4a3def9f59cefe629c9a33d87483283aee085928;hp=0000000000000000000000000000000000000000;hpb=eca4795050a0f7eca3d5dece68eaa54987cebd15;p=jalview.git diff --git a/src/jalview/ext/treeviewer/JalviewBinding.java b/src/jalview/ext/treeviewer/JalviewBinding.java new file mode 100644 index 0000000..fd1735e --- /dev/null +++ b/src/jalview/ext/treeviewer/JalviewBinding.java @@ -0,0 +1,763 @@ +package jalview.ext.treeviewer; + +import jalview.analysis.AlignmentSorter; +import jalview.analysis.Conservation; +import jalview.api.AlignViewportI; +import jalview.commands.CommandI; +import jalview.commands.OrderCommand; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.ColumnSelection; +import jalview.datamodel.HiddenColumns; +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; +import jalview.gui.AlignViewport; +import jalview.gui.AlignmentPanel; +import jalview.gui.Desktop; +import jalview.gui.JvOptionPane; +import jalview.gui.PaintRefresher; +import jalview.schemes.ColourSchemeI; +import jalview.schemes.ColourSchemeProperty; +import jalview.schemes.UserColourScheme; +import jalview.structure.SelectionSource; +import jalview.structure.StructureSelectionManager; +import jalview.util.MappingUtils; +import jalview.util.MessageManager; +import jalview.viewmodel.AlignmentViewport; + +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import javax.swing.SwingUtilities; +import javax.swing.event.InternalFrameAdapter; +import javax.swing.event.InternalFrameEvent; + +/** + * Class for binding the tree viewer to the Jalview alignment that it originates + * from, meaning that selecting sequences in the tree viewer also selects them + * in the alignment view and vice versa. + * + * @author kjvanderheide + * + */ +public final class JalviewBinding + implements TreeViewerBindingI +{ + private final TreeFrameI aptxFrame; + + private TreePanelI treeView; + + private AlignmentViewport parentAvport; + + private final StructureSelectionManager ssm; + + private Map sequencesBoundToNodes; + + private Map nodesBoundToSequences; + + private float rootX; + + private float furthestNodeX; + + private int nrTreeGroups = 0; + + private boolean applyToAllViews = false; + + /** + * + * @param archaeopteryx + * + * @param jalviewAlignmentViewport + * alignment viewport from which the tree was calculated. + * + * @param alignMappedToNodes + * map with sequences used to calculate the tree and matching tree + * nodes as key, value pair respectively. + * + * @param nodesMappedToAlign + * map with tree nodes and matching sequences used to calculate the + * tree as key, value pair respectively. + */ + public JalviewBinding(final TreeFrameI archaeopteryx, + final AlignmentViewport jalviewAlignmentViewport, + final Map alignMappedToNodes, + final Map nodesMappedToAlign) + { + + if (archaeopteryx.getNumberOfTrees() > 1) + { + JvOptionPane.showMessageDialog(Desktop.desktop, + MessageManager.getString("label.tabs_detected_archaeopteryx"), + MessageManager.getString("label.problem_reading_tree_file"), + JvOptionPane.WARNING_MESSAGE); + + } + + // deal with/prohibit null values here as that will cause problems + aptxFrame = archaeopteryx; + parentAvport = jalviewAlignmentViewport; + sequencesBoundToNodes = alignMappedToNodes; + nodesBoundToSequences = nodesMappedToAlign; + + treeView = archaeopteryx.getTreePanel(); + ssm = parentAvport.getStructureSelectionManager(); + + aptxFrame.setViewBinding(this); + ssm.addSelectionListener(this); + treeView.addMouseListener(this); + treeView.registerWithPaintRefresher( + parentAvport.getSequenceSetId()); + + aptxFrame.addFrameListener(new InternalFrameAdapter() + { + + @Override + public void internalFrameClosed(InternalFrameEvent e) + { + TreeViewerUtils.getActiveTreeViews().remove(aptxFrame); + ssm.removeSelectionListener(JalviewBinding.this); + } + + }); + + // treeTabs.addChangeListener(new ChangeListener() + // { + // + // @Override + // public void stateChanged(ChangeEvent e) + // { + // + // SwingUtilities.invokeLater(new Runnable() + // { + // + // @Override + // /** + // * Resend the selection to the tree view when tabs get switched, this + // * has to be buried in invokeLater as Forester first resets the tree + // * view on switching tabs, without invokeLater this would get called + // * before Forester resets which would nullify the selection. + // */ + // public void run() + // { + // treeView = archaeopteryx.getMainPanel().getCurrentTreePanel(); + // parentAvport.sendSelection(); + // // PaintRefresher.Refresh(treeView, + // // parentAvport.getSequenceSetId()); + // + // } + // }); + // + // } + // + // }); + + } + + @Override + public void actionPerformed(ActionEvent e) + { + // reset hidden sequences first + parentAvport.showAllHiddenSeqs(); + + if (treeView.showingSubTree()) + { + LoadedTreeSequenceAssociation bindAptxNodes = new LoadedTreeSequenceAssociation( + parentAvport.getAlignment().getSequencesArray(), + treeView.getTree()); + bindAptxNodes.associateNodesToSequences(); + sequencesBoundToNodes = bindAptxNodes.getAlignmentWithNodes(); + nodesBoundToSequences = bindAptxNodes.getNodesWithAlignment(); + TreeViewerUtils.associateNodesWithJalviewSequences(aptxFrame, + parentAvport, sequencesBoundToNodes, nodesBoundToSequences); + + + for (SequenceI seq : parentAvport.getAlignment().getSequencesArray()) + { + if (!sequencesBoundToNodes.containsKey(seq)) + { + parentAvport.hideSequence(new SequenceI[] { seq }); + + } + } + } + + else + { + + Rectangle visibleView = treeView.getVisibleArea(); + + for (TreeNodeI node : treeView.getTree().getRoot() + .getAllDescendants()) + { + if (!(node.getXcoord() > visibleView.getMinX() + && node.getXcoord() < visibleView.getMaxX() + && node.getYcoord() > visibleView.getMinY() + && node.getYcoord() < visibleView.getMaxY())) + { + parentAvport + .hideSequence(new SequenceI[] + { nodesBoundToSequences.get(node) }); + } + } + + } + + + + } + + @Override + public void mouseClicked(MouseEvent e) + { + SwingUtilities.invokeLater(new Runnable() { + + @Override + /** + * invokeLater so that this always runs after Forester's mouseClicked + */ + public void run() + { + final TreeNodeI node = treeView.findNode(e.getX(), + e.getY()); + if (node != null) + { + if ((e.getModifiers() & InputEvent.SHIFT_MASK) == 0) // clear previous + // selection if shift + // IS NOT pressed + { + parentAvport.setSelectionGroup(null); + + } + showNodeSelectionOnAlign(node); + } + else + { + + partitionTree(e.getX()); + } + treeView.notifyPaintRefresher(parentAvport.getSequenceSetId(), + false, false); + treeView.repaint(); + + + + } + }); + + + } + + @Override + public void mousePressed(final MouseEvent e) + { + + } + @Override + public void mouseReleased(MouseEvent e) + { + } + + @Override + public void mouseEntered(MouseEvent e) + { + } + + @Override + public void mouseExited(MouseEvent e) + { + } + + + @Override + public void selection(final SequenceGroup seqsel, + final ColumnSelection colsel, final HiddenColumns hidden, + final SelectionSource source) + { + if (source == parentAvport) // check if source is alignment from where the + // tree originates + { + treeView.setMatchingNodes( + new HashSet(seqsel.getSequences().size())); + + + for (SequenceI selectedSequence : seqsel.getSequences()) + { + TreeNodeI matchingNode = sequencesBoundToNodes + .get(selectedSequence); + if (matchingNode != null) + { + treeView.addToMatchingNodes(matchingNode); + + + // if (!matchingNode.getBranchData().isHasBranchColor()) + // { + // // Color foundNodesColour = treeView.getTreeColorSet() + // // .getFoundColor0(); + // // matchingNode.getBranchData() + // // .setBranchColor(new BranchColor(foundNodesColour)); + // + // } + + } + + } + + treeView.repaint(); + } + + + } + + /** + * Partially refactored from TreeCanvas + */ + @Override + public void partitionTree(final int x) + { + TreeI tree = treeView.getTree(); + + if (!tree.isEmpty()) + { + // should be calculated on each partition as the tree can theoretically + // change in the meantime + TreeNodeI furthestNode = tree.getFurthestNode(); + furthestNodeX = furthestNode.getXcoord(); + rootX = tree.getRoot().getXcoord(); + + // don't bother if 0 distance tree or clicked x lies outside of tree + // if (furthestNodeX != rootX && !(x > furthestNodeX)) + + float threshold = (x - rootX) / (furthestNodeX - rootX); + List foundNodes = getNodesAboveThreshold( + threshold, + tree.getRoot()); + + + } + + + } + + public List getNodesAboveThreshold(float threshold, + TreeNodeI node) + { + + List nodesAboveThreshold = new ArrayList<>(); + + parentAvport.setSelectionGroup(null); + parentAvport.getAlignment().deleteAllGroups(); + parentAvport.clearSequenceColours(); + if (parentAvport.getCodingComplement() != null) + { + parentAvport.getCodingComplement().setSelectionGroup(null); + parentAvport.getCodingComplement().getAlignment().deleteAllGroups(); + parentAvport.getCodingComplement().clearSequenceColours(); + } + + + colourNodesAboveThreshold(nodesAboveThreshold, threshold, + node); + return nodesAboveThreshold; + + } + + /** + * Partially refactored from TreeCanvas colourGroups (can be made nicer). + * + * @param nodeList + * @param threshold + * @param treeLength + * @param node + * @return + */ + private List colourNodesAboveThreshold( + List nodeList, float threshold, + TreeNodeI node) + { + + for (TreeNodeI childNode : node.getDirectChildren()) + { + childNode.setBranchColor(Color.black); + float nodeCutoff = (childNode.getXcoord() - rootX) + / (furthestNodeX - rootX); + + if (nodeCutoff > threshold) + { + nodeList.add(childNode); + + Color randomColour = new Color((int) (Math.random() * 255), + (int) (Math.random() * 255), (int) (Math.random() * 255)); + childNode.setBranchColor(randomColour); + + List groupSeqs = new ArrayList<>(); + SequenceI seq = nodesBoundToSequences.get(childNode); + if (seq != null) + { + groupSeqs.add(seq); + parentAvport.setSequenceColour(seq, randomColour); + } + + List descendantNodes = childNode + .getAllDescendants(); + // .forEach instead? + for (TreeNodeI descNode : descendantNodes) + { + seq = nodesBoundToSequences.get(descNode); + if (seq != null) + { + groupSeqs.add(seq); + parentAvport.setSequenceColour(seq, randomColour); + } + + descNode.setBranchColor(randomColour); + } + + if (groupSeqs != null) + { + nrTreeGroups++; + groupThresholdSequences(groupSeqs, randomColour); + }} + + else + { + colourNodesAboveThreshold(nodeList, threshold, childNode); + } + } + + for (AlignmentPanel associatedPanel : getAssociatedPanels()) + { + + associatedPanel.updateAnnotation(); + + final AlignViewportI codingComplement = associatedPanel.getAlignViewport() + .getCodingComplement(); + if (codingComplement != null) + { + // GROSS + ((AlignViewport) codingComplement).getAlignPanel() + .updateAnnotation(); + } + } + + + return nodeList; + } + + public void groupThresholdSequences(List groupedSeqs, + Color groupColour) + { + SequenceGroup treeGroup = new SequenceGroup(groupedSeqs, null, null, + true, true, false, 0, + parentAvport.getAlignment().getWidth() - 1); + + ColourSchemeI cs = null; + if (parentAvport.getGlobalColourScheme() != null) + { + if (parentAvport.getGlobalColourScheme() instanceof UserColourScheme) + { + cs = new UserColourScheme( + ((UserColourScheme) parentAvport.getGlobalColourScheme()) + .getColours()); + } + else + { + cs = ColourSchemeProperty.getColourScheme(parentAvport,treeGroup, + ColourSchemeProperty.getColourName( + parentAvport.getGlobalColourScheme())); + } + + } + treeGroup.setColourScheme(cs); + treeGroup.getGroupColourScheme().setThreshold( + parentAvport.getResidueShading().getThreshold(), + parentAvport.isIgnoreGapsConsensus()); + + treeGroup.setName("Tree Group " + nrTreeGroups); + treeGroup.setIdColour(groupColour); + + for (AlignmentPanel associatedPanel : getAssociatedPanels()) + { + AlignViewportI altViewport = associatedPanel + .getAlignViewport(); + + if (altViewport.getGlobalColourScheme() != null + && altViewport.getResidueShading() + .conservationApplied()) + { + Conservation conserv = new Conservation(treeGroup.getName(), + treeGroup.getSequences(null), treeGroup.getStartRes(), + treeGroup.getEndRes()); + conserv.calculate(); + conserv.verdict(false, altViewport.getConsPercGaps()); + treeGroup.getGroupColourScheme().setConservation(conserv); + } + + altViewport.getAlignment().addGroup(treeGroup); + // TODO can we push all of the below into AlignViewportI? + final AlignViewportI codingComplement = altViewport + .getCodingComplement(); + if (codingComplement != null) + { + SequenceGroup mappedGroup = MappingUtils.mapSequenceGroup(treeGroup, + parentAvport, codingComplement); + if (mappedGroup.getSequences().size() > 0) + { + codingComplement.getAlignment().addGroup(mappedGroup); + for (SequenceI seq : mappedGroup.getSequences()) + { + codingComplement.setSequenceColour(seq, groupColour.brighter()); + } + } + } + + } + + } + + + @Override + public void showNodeSelectionOnAlign(final TreeNodeI node) + { + + if (node.isInternal()) + { + showMatchingChildSequences(node); + } + + else + { + showMatchingSequence(node); + } + + + } + + + + + + @Override + public void showMatchingSequence(final TreeNodeI nodeToMatch) + { + SequenceI matchingSequence = nodesBoundToSequences.get(nodeToMatch); + if (matchingSequence != null) + { + long nodeId = nodeToMatch.getId(); + addOrRemoveInCollection(treeView.getMatchingNodesIds(), nodeId); + treeSelectionChanged(matchingSequence); + parentAvport.sendSelection(); + + } + } + + @Override + public void showMatchingChildSequences(final TreeNodeI parentNode) + { + // redundancy here, Forester already iterates through tree to get all + // descendants + List childNodes = parentNode.getAllDescendants(); + + + for (TreeNodeI childNode : childNodes) + { + SequenceI matchingSequence = nodesBoundToSequences.get(childNode); + if (matchingSequence != null) + { + long nodeId = childNode.getId(); + addOrRemoveInCollection(treeView.getMatchingNodesIds(), nodeId); + + treeSelectionChanged(matchingSequence); + + } + + } + parentAvport.sendSelection(); + + + } + + + @Override + public void treeSelectionChanged(final SequenceI sequence) + { + if (!parentAvport.isClosed()) // alignment view could be closed + { + SequenceGroup selected = parentAvport.getSelectionGroup(); + + if (selected == null) + { + selected = new SequenceGroup(); + parentAvport.setSelectionGroup(selected); + } + + selected.setEndRes(parentAvport.getAlignment().getWidth() - 1); + selected.addOrRemove(sequence, true); + } + + } + + @Override + public void sortByTree_actionPerformed() + { + + // if (applyToAllViews) + + final ArrayList commands = new ArrayList<>(); + for (AlignmentPanel ap : PaintRefresher + .getAssociatedPanels(parentAvport.getSequenceSetId())) + { + commands.add(sortAlignmentIn(ap.av.getAlignPanel())); + ap.alignFrame.addHistoryItem(new CommandI() + { + + @Override + public void undoCommand(AlignmentI[] views) + { + for (CommandI tsort : commands) + { + tsort.undoCommand(views); + } + } + + @Override + public int getSize() + { + return commands.size(); + } + + @Override + public String getDescription() + { + return "Tree Sort (many views)"; + } + + @Override + public void doCommand(AlignmentI[] views) + { + + for (CommandI tsort : commands) + { + tsort.doCommand(views); + } + } + }); + + ap.alignFrame.updateEditMenuBar(); + } + } + // else + // { + // alignPanel.alignFrame.addHistoryItem(sortAlignmentIn(alignPanel)); + // } + + + + @Override + public CommandI sortAlignmentIn(AlignmentPanel ap) + { + AlignmentViewport viewport = ap.av; + SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray(); + try + { + AlignmentSorter.sortByTree(viewport.getAlignment(), + nodesBoundToSequences, + treeView.getTree()); + CommandI undo; + undo = new OrderCommand("Tree Sort", oldOrder, + viewport.getAlignment()); + + ap.paintAlignment(true, false); + return undo; + + } catch (Exception e) + { + System.err.println(e.getMessage()); + } + return null; + + } + + + + /** + * TO BE MOVED + * + * @param collection + * @param nodeId + */ + public static void addOrRemoveInCollection(Collection collection, + long nodeId) + { + if (collection.contains(nodeId)) + { + collection.remove(nodeId); + } + else + { + collection.add(nodeId); + } + + } + + public AlignmentViewport getParentAvport() + { + return parentAvport; + } + + public void setParentAvport(final AlignmentViewport parentAvport) + { + this.parentAvport = parentAvport; + } + + public AlignmentPanel[] getAssociatedPanels() + { + return PaintRefresher + .getAssociatedPanels(parentAvport.getSequenceSetId()); + } + + @Override + public Map getAlignmentWithNodes() + { + return sequencesBoundToNodes; + } + + @Override + public Map getNodesWithAlignment() + { + return nodesBoundToSequences; + } + + @Override + public void hideCollapsedSequences_actionPerformed() + { + parentAvport.showAllHiddenSeqs(); + + for (TreeNodeI node : treeView.getTree().getAllNodes()) + { + if (node.isCollapsed()) + { + SequenceI seqToHide = nodesBoundToSequences.get(node); + if (seqToHide != null) + { + parentAvport.hideSequence(new SequenceI[] { seqToHide }); + + + } + + } + } + + + } + +} + + +