From c3c8eb857e5544602671ea205da67f082de5a306 Mon Sep 17 00:00:00 2001 From: Jim Procter Date: Fri, 23 Sep 2022 12:09:24 +0100 Subject: [PATCH] JAL-4071 first working prototype --- src/jalview/api/FeatureRenderer.java | 32 +++- src/jalview/gui/AlignmentPanel.java | 60 ++++--- src/jalview/gui/FeatureRenderer.java | 9 + .../renderer/seqfeatures/FeatureRenderer.java | 2 +- .../seqfeatures/FeatureRendererModel.java | 9 + src/jalview/viewmodel/styles/ViewStyle.java | 3 + src/jalview/workers/ColumnCounterSetWorker.java | 28 ++- .../workers/VisibleFeaturesAnnotationTracks.java | 183 ++++++++++++++++++++ 8 files changed, 292 insertions(+), 34 deletions(-) create mode 100644 src/jalview/workers/VisibleFeaturesAnnotationTracks.java diff --git a/src/jalview/api/FeatureRenderer.java b/src/jalview/api/FeatureRenderer.java index 5430303..762350e 100644 --- a/src/jalview/api/FeatureRenderer.java +++ b/src/jalview/api/FeatureRenderer.java @@ -20,16 +20,17 @@ */ package jalview.api; -import jalview.datamodel.MappedFeatures; -import jalview.datamodel.SequenceFeature; -import jalview.datamodel.SequenceI; -import jalview.datamodel.features.FeatureMatcherSetI; - import java.awt.Color; import java.awt.Graphics; +import java.beans.PropertyChangeListener; import java.util.List; import java.util.Map; +import jalview.datamodel.MappedFeatures; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcherSetI; + /** * Abstract feature renderer interface * @@ -306,4 +307,25 @@ public interface FeatureRenderer */ void notifyFeaturesChanged(); + /** + * register as a listener for notifyFeaturesChanged events + * + * @param ourListener + */ + void addPropertyChangeListener(PropertyChangeListener ourListener); + + /** + * remove a listener for notifyFeaturesChanged events + * + * @param ourListener + */ + void removePropertyChangeListener(PropertyChangeListener ourListener); + + /** + * + * @return associated alignment panel for this feature renderer (may return + * null) + */ + AlignmentViewPanel getAlignPanel(); + } diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index e84aae0..9280fb1 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -20,30 +20,6 @@ */ package jalview.gui; -import jalview.analysis.AnnotationSorter; -import jalview.api.AlignViewportI; -import jalview.api.AlignmentViewPanel; -import jalview.bin.Cache; -import jalview.bin.Console; -import jalview.bin.Jalview; -import jalview.datamodel.AlignmentI; -import jalview.datamodel.HiddenColumns; -import jalview.datamodel.SearchResultsI; -import jalview.datamodel.SequenceFeature; -import jalview.datamodel.SequenceGroup; -import jalview.datamodel.SequenceI; -import jalview.gui.ImageExporter.ImageWriterI; -import jalview.io.HTMLOutput; -import jalview.jbgui.GAlignmentPanel; -import jalview.math.AlignmentDimension; -import jalview.schemes.ResidueProperties; -import jalview.structure.StructureSelectionManager; -import jalview.util.Comparison; -import jalview.util.ImageMaker; -import jalview.util.MessageManager; -import jalview.viewmodel.ViewportListenerI; -import jalview.viewmodel.ViewportRanges; - import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; @@ -68,6 +44,31 @@ import java.util.List; import javax.swing.SwingUtilities; +import jalview.analysis.AnnotationSorter; +import jalview.api.AlignViewportI; +import jalview.api.AlignmentViewPanel; +import jalview.bin.Cache; +import jalview.bin.Console; +import jalview.bin.Jalview; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.HiddenColumns; +import jalview.datamodel.SearchResultsI; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceGroup; +import jalview.datamodel.SequenceI; +import jalview.gui.ImageExporter.ImageWriterI; +import jalview.io.HTMLOutput; +import jalview.jbgui.GAlignmentPanel; +import jalview.math.AlignmentDimension; +import jalview.schemes.ResidueProperties; +import jalview.structure.StructureSelectionManager; +import jalview.util.Comparison; +import jalview.util.ImageMaker; +import jalview.util.MessageManager; +import jalview.viewmodel.ViewportListenerI; +import jalview.viewmodel.ViewportRanges; +import jalview.workers.VisibleFeaturesAnnotationTracks; + /** * DOCUMENT ME! * @@ -115,6 +116,8 @@ public class AlignmentPanel extends GAlignmentPanel implements private CalculationChooser calculationDialog; + private VisibleFeaturesAnnotationTracks featureCounter; + /** * Creates a new AlignmentPanel object. * @@ -130,6 +133,8 @@ public class AlignmentPanel extends GAlignmentPanel implements setIdPanel(new IdPanel(av, this)); setScalePanel(new ScalePanel(av, this)); + featureCounter = new VisibleFeaturesAnnotationTracks(av, + seqPanel.seqCanvas.fr); idPanelHolder.add(getIdPanel(), BorderLayout.CENTER); idwidthAdjuster = new IdwidthAdjuster(this); @@ -179,6 +184,7 @@ public class AlignmentPanel extends GAlignmentPanel implements }); final AlignmentPanel ap = this; + propertyChangeListener = new PropertyChangeListener() { @Override @@ -824,6 +830,7 @@ public class AlignmentPanel extends GAlignmentPanel implements public void paintAlignment(boolean updateOverview, boolean updateStructures) { + featureCounter.updateFeatureAnnotationTracks(); final AnnotationSorter sorter = new AnnotationSorter(getAlignment(), av.isShowAutocalculatedAbove()); sorter.sort(getAlignment().getAlignmentAnnotation(), @@ -1450,6 +1457,11 @@ public class AlignmentPanel extends GAlignmentPanel implements annotationPanel = null; } + if (featureCounter != null) + { + featureCounter.tidyUp(); + featureCounter = null; + } if (av != null) { av.removePropertyChangeListener(propertyChangeListener); diff --git a/src/jalview/gui/FeatureRenderer.java b/src/jalview/gui/FeatureRenderer.java index 83badd0..0cbb9bc 100644 --- a/src/jalview/gui/FeatureRenderer.java +++ b/src/jalview/gui/FeatureRenderer.java @@ -20,6 +20,8 @@ */ package jalview.gui; +import jalview.api.AlignmentViewPanel; + /** * A class that manages drawing of sequence features for the Swing gui */ @@ -37,6 +39,7 @@ public class FeatureRenderer { super(alignPanel.av); this.ap = alignPanel; + if (alignPanel.getSeqPanel() != null && alignPanel.getSeqPanel().seqCanvas != null && alignPanel.getSeqPanel().seqCanvas.fr != null) @@ -44,4 +47,10 @@ public class FeatureRenderer transferSettings(alignPanel.getSeqPanel().seqCanvas.fr); } } + + @Override + public AlignmentViewPanel getAlignPanel() + { + return ap; + } } diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index e66b7d5..2d16fcd 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -543,7 +543,7 @@ public class FeatureRenderer extends FeatureRendererModel */ List overlaps = seq.findFeatures(column, column, type); - for (int i = overlaps.size() - 1 ; i >= 0 ; i--) + for (int i = overlaps.size() - 1; i >= 0; i--) { SequenceFeature sequenceFeature = overlaps.get(i); if (!featureGroupNotShown(sequenceFeature)) diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index e812ed5..9155440 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -36,6 +36,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import jalview.api.AlignViewportI; +import jalview.api.AlignmentViewPanel; import jalview.api.FeatureColourI; import jalview.api.FeaturesDisplayedI; import jalview.datamodel.AlignedCodonFrame; @@ -801,6 +802,7 @@ public abstract class FeatureRendererModel * @param listener * @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener) */ + @Override public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); @@ -810,6 +812,7 @@ public abstract class FeatureRendererModel * @param listener * @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener) */ + @Override public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); @@ -1330,4 +1333,10 @@ public abstract class FeatureRendererModel } return true; } + + @Override + public AlignmentViewPanel getAlignPanel() + { + return null; + } } diff --git a/src/jalview/viewmodel/styles/ViewStyle.java b/src/jalview/viewmodel/styles/ViewStyle.java index 0a525e2..eaa77cb 100644 --- a/src/jalview/viewmodel/styles/ViewStyle.java +++ b/src/jalview/viewmodel/styles/ViewStyle.java @@ -219,6 +219,7 @@ public class ViewStyle implements ViewStyleI setShowNPFeats(vs.isShowNPFeats()); setShowSequenceFeaturesHeight(vs.isShowSequenceFeaturesHeight()); setShowSequenceFeatures(vs.isShowSequenceFeatures()); + setShowSequenceFeatureCounts(vs.isShowSequenceFeatureCounts()); setShowComplementFeatures(vs.isShowComplementFeatures()); setShowComplementFeaturesOnTop(vs.isShowComplementFeaturesOnTop()); setShowText(vs.getShowText()); @@ -282,6 +283,8 @@ public class ViewStyle implements ViewStyleI && isShowNPFeats() == vs.isShowNPFeats() && isShowSequenceFeaturesHeight() == vs .isShowSequenceFeaturesHeight() + && isShowSequenceFeatureCounts() == vs + .isShowSequenceFeatureCounts() && isShowSequenceFeatures() == vs.isShowSequenceFeatures() && isShowComplementFeatures() == vs.isShowComplementFeatures() && isShowComplementFeaturesOnTop() == vs diff --git a/src/jalview/workers/ColumnCounterSetWorker.java b/src/jalview/workers/ColumnCounterSetWorker.java index 74695fe..3a4bcc4 100644 --- a/src/jalview/workers/ColumnCounterSetWorker.java +++ b/src/jalview/workers/ColumnCounterSetWorker.java @@ -20,6 +20,10 @@ */ package jalview.workers; +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; + import jalview.api.AlignViewportI; import jalview.api.AlignmentViewPanel; import jalview.datamodel.AlignmentAnnotation; @@ -31,10 +35,6 @@ import jalview.renderer.seqfeatures.FeatureRenderer; import jalview.util.ColorUtils; import jalview.util.Comparison; -import java.awt.Color; -import java.util.ArrayList; -import java.util.List; - /** * A class to compute alignment annotations with column counts for a set of * properties of interest on positions in an alignment.
@@ -223,6 +223,26 @@ class ColumnCounterSetWorker extends AlignCalcWorker return annotationAdded; } + void removeOldAnnotations(String[] annotDescs) + { + // TODO use the commented out code once JAL-2075 is fixed + // to get adequate performance on genomic length sequence + AlignmentI alignment = alignViewport.getAlignment(); + ArrayList toRemove = new ArrayList(); + for (String toRemoveDesc : annotDescs) + { + for (AlignmentAnnotation aa : ourAnnots) + { + if (toRemoveDesc.equals(aa.getCalcId())) + toRemove.add(aa); + } + } + for (AlignmentAnnotation deleted : toRemove) + { + alignment.deleteAnnotation(deleted); + } + } + /** * Returns a count of any feature types present at the specified position of * the alignment diff --git a/src/jalview/workers/VisibleFeaturesAnnotationTracks.java b/src/jalview/workers/VisibleFeaturesAnnotationTracks.java new file mode 100644 index 0000000..baa8038 --- /dev/null +++ b/src/jalview/workers/VisibleFeaturesAnnotationTracks.java @@ -0,0 +1,183 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ +package jalview.workers; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import jalview.api.AlignViewportI; +import jalview.api.FeaturesDisplayedI; +import jalview.datamodel.SequenceFeature; +import jalview.gui.FeatureRenderer; + +public class VisibleFeaturesAnnotationTracks implements FeatureSetCounterI +{ + AlignViewportI ourViewport = null; + + jalview.api.FeatureRenderer ourFr = null; + + PropertyChangeListener ourListener = new PropertyChangeListener() + { + + @Override + public void propertyChange(PropertyChangeEvent evt) + { + if (ourViewport != null) // could check source is + // ourFr.getChangeSupport... + { + updateFeatureAnnotationTracks(); + } + } + }; + + public VisibleFeaturesAnnotationTracks(AlignViewportI viewport, + FeatureRenderer fr) + { + ourViewport = viewport; + ourFr = fr; + registerListener(); + } + + void registerListener() + { + ourFr.addPropertyChangeListener(ourListener); + } + + public void tidyUp() + { + if (ourFr != null) + { + ourFr.removePropertyChangeListener(ourListener); + } + if (ourViewport != null && ourViewport.getCalcManager() != null) + { + if (ourWorker != null) + { + ourWorker.abortAndDestroy(); + ourViewport.getCalcManager() + .removeRegisteredWorkersOfClass(ourWorker.getClass()); + } + ourWorker = null; + ourViewport = null; + } + } + + public void updateFeatureAnnotationTracks() + { + // if tracks are turned off, this returns null. + FeaturesDisplayedI featuresDisp = ourViewport + .isShowSequenceFeatureCounts() + ? ourViewport.getFeaturesDisplayed() + : null; + Set visibleFeatures = new HashSet(); + if (featuresDisp != null) + { + visibleFeatures.addAll(featuresDisp.getVisibleFeatures()); + } + if (dispFeatures.equals(visibleFeatures)) + { + // all the same features displayed + return; + } + // otherwise set up tracks accordingly + + /* + * and register the counter + */ + if (ourWorker != null) + { + Set toRemove = new HashSet(), + toAdd = new HashSet(); + toRemove.addAll(dispFeatures); + toRemove.removeAll(visibleFeatures); + dispFeatures = visibleFeatures; + ourWorker.removeOldAnnotations(toRemove.toArray(new String[0])); + + } + else + { + dispFeatures = visibleFeatures; + ourWorker = new ColumnCounterSetWorker(ourViewport, + ourFr.getAlignPanel(), this); + } + ourViewport.getCalcManager().registerWorker(ourWorker); + ourViewport.getCalcManager().startWorker(ourWorker); + } + + ColumnCounterSetWorker ourWorker = null; + + Set dispFeatures = Collections.EMPTY_SET; + + @Override + public int[] count(String residue, List features) + { + final Set ourDispFeatures = dispFeatures; + int[] obs = new int[ourDispFeatures.size()]; + SequenceFeature[] sfs = features.toArray(new SequenceFeature[0]); + for (SequenceFeature sf : sfs) + { + /* + * Here we inspect the type of the sequence feature. + * You can also test sf.description, sf.score, sf.featureGroup, + * sf.strand, sf.phase, sf.begin, sf.end + * or sf.getValue(attributeName) for GFF 'column 9' properties + */ + int pos = 0; + for (String type : ourDispFeatures) + { + if (type.equals(sf.type)) + { + obs[pos]++; + } + pos++; + } + } + return obs; + } + + @Override + public String[] getNames() + { + return dispFeatures.toArray(new String[0]); + } + + @Override + public String[] getDescriptions() + { + return dispFeatures.toArray(new String[0]); + } + + @Override + public int[] getMaxColour() + { + return new int[] { 0, 0, 255 }; + } + + @Override + public int[] getMinColour() + { + return new int[] { 0, 255, 255 }; + } +} \ No newline at end of file -- 1.7.10.2