From f0115a38526bc70b87ee919b3c0f1ba0f7e53989 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Fri, 9 Jun 2017 10:32:20 +0100 Subject: [PATCH] JAL-2593 avoid redundant rendering of co-located features --- .../renderer/seqfeatures/FeatureRenderer.java | 7 +- .../seqfeatures/FeatureRendererModel.java | 51 ++++++++++ .../renderer/seqfeatures/FeatureRendererTest.java | 102 ++++++++++++++++++++ 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index 5e071f9..8f4f139 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -21,6 +21,7 @@ package jalview.renderer.seqfeatures; import jalview.api.AlignViewportI; +import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.util.Comparison; @@ -290,8 +291,12 @@ public class FeatureRenderer extends FeatureRendererModel continue; } + FeatureColourI fc = getFeatureStyle(type); List overlaps = seq.findFeatures(start + 1, end + 1, type); + + filterFeaturesForDisplay(overlaps, fc); + for (SequenceFeature sf : overlaps) { /* @@ -303,7 +308,7 @@ public class FeatureRenderer extends FeatureRendererModel continue; } - Color featureColour = getColour(sf); + Color featureColour = fc.getColor(sf); boolean isContactFeature = sf.isContactFeature(); int featureStartCol = seq.findIndex(sf.begin); diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index a568341..de1ee5e 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -26,6 +26,7 @@ import jalview.api.FeaturesDisplayedI; import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.SequenceFeatures; import jalview.renderer.seqfeatures.FeatureRenderer; import jalview.schemes.FeatureColour; import jalview.util.ColorUtils; @@ -38,6 +39,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -1000,4 +1002,53 @@ public abstract class FeatureRendererModel implements return result; } + /** + * Removes from the list of features any that have a feature group that is not + * displayed, or duplicate the location of a feature of the same type (unless + * a graduated colour scheme is applied) + * + * @param features + * @param fc + */ + public void filterFeaturesForDisplay(List features, + FeatureColourI fc) + { + if (features.isEmpty()) + { + return; + } + SequenceFeatures.sortFeatures(features, true); + boolean graduated = fc != null && fc.isGraduatedColour(); + SequenceFeature lastFeature = null; + + Iterator it = features.iterator(); + while (it.hasNext()) + { + SequenceFeature sf = it.next(); + if (featureGroupNotShown(sf)) + { + it.remove(); + continue; + } + + /* + * a feature is redundant for rendering purposes if it has the + * same extent as another (so would just redraw the same colour); + * (checking type and isContactFeature as a fail-safe here, although + * currently they are guaranteed to match in this context) + */ + if (!graduated) + { + if (lastFeature != null && sf.getBegin() == lastFeature.getBegin() + && sf.getEnd() == lastFeature.getEnd() + && sf.isContactFeature() == lastFeature.isContactFeature() + && sf.getType().equals(lastFeature.getType())) + { + it.remove(); + } + } + lastFeature = sf; + } + } + } diff --git a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java index 31348c6..3e27aba 100644 --- a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java +++ b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java @@ -14,6 +14,7 @@ import jalview.io.FileLoader; import jalview.schemes.FeatureColour; import java.awt.Color; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -252,4 +253,105 @@ public class FeatureRendererTest assertEquals(features.size(), 1); assertTrue(features.contains(sf8)); } + + @Test(groups = "Functional") + public void testFilterFeaturesForDisplay() + { + String seqData = ">s1\nabcdef\n"; + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, + DataSourceType.PASTE); + AlignViewportI av = af.getViewport(); + FeatureRenderer fr = new FeatureRenderer(av); + + List features = new ArrayList<>(); + fr.filterFeaturesForDisplay(features, null); // empty list, does nothing + + SequenceI seq = av.getAlignment().getSequenceAt(0); + SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, "group1"); + seq.addSequenceFeature(sf1); + SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, "group2"); + seq.addSequenceFeature(sf2); + SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, "group3"); + seq.addSequenceFeature(sf3); + SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, "group4"); + seq.addSequenceFeature(sf4); + SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, "group4"); + seq.addSequenceFeature(sf5); + + fr.findAllFeatures(true); + + features = seq.getSequenceFeatures(); + assertEquals(features.size(), 5); + assertTrue(features.contains(sf1)); + assertTrue(features.contains(sf2)); + assertTrue(features.contains(sf3)); + assertTrue(features.contains(sf4)); + assertTrue(features.contains(sf5)); + + /* + * filter out duplicate (co-located) features + * note: which gets removed is not guaranteed + */ + fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue)); + assertEquals(features.size(), 3); + assertTrue(features.contains(sf1) || features.contains(sf4)); + assertFalse(features.contains(sf1) && features.contains(sf4)); + assertTrue(features.contains(sf2) || features.contains(sf3)); + assertFalse(features.contains(sf2) && features.contains(sf3)); + assertTrue(features.contains(sf5)); + + /* + * hide group 3 - sf3 is removed, sf2 is retained + */ + fr.setGroupVisibility("group3", false); + features = seq.getSequenceFeatures(); + fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue)); + assertEquals(features.size(), 3); + assertTrue(features.contains(sf1) || features.contains(sf4)); + assertFalse(features.contains(sf1) && features.contains(sf4)); + assertTrue(features.contains(sf2)); + assertFalse(features.contains(sf3)); + assertTrue(features.contains(sf5)); + + /* + * hide group 2, show group 3 - sf2 is removed, sf3 is retained + */ + fr.setGroupVisibility("group2", false); + fr.setGroupVisibility("group3", true); + features = seq.getSequenceFeatures(); + fr.filterFeaturesForDisplay(features, null); + assertEquals(features.size(), 3); + assertTrue(features.contains(sf1) || features.contains(sf4)); + assertFalse(features.contains(sf1) && features.contains(sf4)); + assertFalse(features.contains(sf2)); + assertTrue(features.contains(sf3)); + assertTrue(features.contains(sf5)); + + /* + * no filtering of co-located features with graduated colour scheme + * sf2 is removed as its group is hidden + */ + features = seq.getSequenceFeatures(); + fr.filterFeaturesForDisplay(features, new FeatureColour(Color.black, + Color.white, 0f, 1f)); + assertEquals(features.size(), 4); + assertTrue(features.contains(sf1)); + assertTrue(features.contains(sf3)); + assertTrue(features.contains(sf4)); + assertTrue(features.contains(sf5)); + + /* + * filtering of co-located features with colour by label + */ + features = seq.getSequenceFeatures(); + FeatureColour fc = new FeatureColour(Color.black); + fc.setColourByLabel(true); + fr.filterFeaturesForDisplay(features, fc); + assertEquals(features.size(), 3); + assertTrue(features.contains(sf1) || features.contains(sf4)); + assertFalse(features.contains(sf1) && features.contains(sf4)); + assertFalse(features.contains(sf2)); + assertTrue(features.contains(sf3)); + assertTrue(features.contains(sf5)); + } } -- 1.7.10.2