Merge branch 'bug/JAL-2791exportFilteredFeature' into merge/JAL-2791
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 1 Mar 2019 15:35:54 +0000 (15:35 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 1 Mar 2019 15:35:54 +0000 (15:35 +0000)
1  2 
src/jalview/api/FeatureRenderer.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
test/jalview/renderer/seqfeatures/FeatureRendererTest.java

@@@ -255,14 -255,29 +255,29 @@@ public interface FeatureRendere
     * <p>
     * Returns null if
     * <ul>
 -   * <li>feature type is not visible, or</li>
     * <li>feature group is not visible, or</li>
     * <li>feature values lie outside any colour threshold, or</li>
     * <li>feature is excluded by filter conditions</li>
     * </ul>
 +   * This method does not check feature type visibility.
     * 
     * @param feature
     * @return
     */
    Color getColour(SequenceFeature feature);
+   /**
+    * Answers true if feature would be shown, else false. A feature is shown if
+    * <ul>
+    * <li>its feature type is set to visible</li>
+    * <li>its feature group is either null, or set to visible</li>
+    * <li>it is not excluded by a colour threshold on score or other numeric
+    * attribute</li>
+    * <li>it is not excluded by a filter condition</li>
+    * </ul>
+    * 
+    * @param feature
+    * @return
+    */
+   boolean isVisible(SequenceFeature feature);
  }
@@@ -1029,11 -1029,11 +1029,11 @@@ public abstract class FeatureRendererMo
    }
  
    /**
 -   * Removes from the list of features any that duplicate the location of a
 -   * feature of the same type. Should be used only for features of the same,
 -   * simple, feature colour (which normally implies the same feature type). Does
 -   * not check visibility settings for feature type or feature group. No
 -   * filtering is done if transparency, or any feature filters, are in force.
 +   * Removes from the list of features any whose group is not shown, or that are
 +   * visible and duplicate the location of a visible feature of the same type.
 +   * Should be used only for features of the same, simple, feature colour (which
 +   * normally implies the same feature type). No filtering is done if
 +   * transparency, or any feature filters, are in force.
     * 
     * @param features
     */
      while (it.hasNext())
      {
        SequenceFeature sf = it.next();
 +      if (featureGroupNotShown(sf))
 +      {
 +        it.remove();
 +        continue;
 +      }
  
        /*
         * a feature is redundant for rendering purposes if it has the
         * (checking type and isContactFeature as a fail-safe here, although
         * currently they are guaranteed to match in this context)
         */
 -      if (lastFeature != null && sf.getBegin() == lastFeature.getBegin()
 +      if (lastFeature != null
 +              && sf.getBegin() == lastFeature.getBegin()
                && sf.getEnd() == lastFeature.getEnd()
                && sf.isContactFeature() == lastFeature.isContactFeature()
                && sf.getType().equals(lastFeature.getType()))
      return filter == null ? true : filter.matches(sf);
    }
  
+   @Override
+   public boolean isVisible(SequenceFeature feature)
+   {
+     if (feature == null)
+     {
+       return false;
+     }
+     if (getFeaturesDisplayed() == null
+             || !getFeaturesDisplayed().isVisible(feature.getType()))
+     {
+       return false;
+     }
+     if (featureGroupNotShown(feature))
+     {
+       return false;
+     }
+     FeatureColourI fc = featureColours.get(feature.getType());
+     if (fc != null && fc.isOutwithThreshold(feature))
+     {
+       return false;
+     }
+     if (!featureMatchesFilters(feature))
+     {
+       return false;
+     }
+     return true;
+   }
  }
@@@ -328,18 -328,18 +328,18 @@@ public class FeatureRendererTes
      SequenceI seq = av.getAlignment().getSequenceAt(0);
      SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
              "group1");
 -    seq.addSequenceFeature(sf1);
      SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
              "group2");
 -    seq.addSequenceFeature(sf2);
      SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
              "group3");
 -    seq.addSequenceFeature(sf3);
      SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
              "group4");
 -    seq.addSequenceFeature(sf4);
      SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
              "group4");
 +    seq.addSequenceFeature(sf1);
 +    seq.addSequenceFeature(sf2);
 +    seq.addSequenceFeature(sf3);
 +    seq.addSequenceFeature(sf4);
      seq.addSequenceFeature(sf5);
  
      fr.findAllFeatures(true);
      assertTrue(features.contains(sf5));
  
      /*
 -     * hide groups 2 and 3 makes no difference to this method
 +     * features in hidden groups are removed
       */
      fr.setGroupVisibility("group2", false);
      fr.setGroupVisibility("group3", false);
      features = seq.getSequenceFeatures();
      fr.filterFeaturesForDisplay(features);
 -    assertEquals(features.size(), 3);
 +    assertEquals(features.size(), 2);
      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));
 +    assertFalse(features.contains(sf2));
 +    assertFalse(features.contains(sf3));
      assertTrue(features.contains(sf5));
  
      /*
      csqData.put("Feature", "ENST01234");
      assertEquals(fr.getColour(sf2), expected);
    }
+   @Test(groups = "Functional")
+   public void testIsVisible()
+   {
+     String seqData = ">s1\nMLQGIFPRS\n";
+     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
+             DataSourceType.PASTE);
+     AlignViewportI av = af.getViewport();
+     FeatureRenderer fr = new FeatureRenderer(av);
+     SequenceI seq = av.getAlignment().getSequenceAt(0);
+     SequenceFeature sf = new SequenceFeature("METAL", "Desc", 10, 10, 1f,
+             "Group");
+     sf.setValue("AC", "11");
+     sf.setValue("CLIN_SIG", "Likely Pathogenic");
+     seq.addSequenceFeature(sf);
+     assertFalse(fr.isVisible(null));
+     /*
+      * initial state FeatureRenderer hasn't 'found' feature
+      * and so its feature type has not yet been set visible
+      */
+     assertFalse(fr.getDisplayedFeatureCols().containsKey("METAL"));
+     assertFalse(fr.isVisible(sf));
+     fr.findAllFeatures(true);
+     assertTrue(fr.isVisible(sf));
+     /*
+      * feature group not visible
+      */
+     fr.setGroupVisibility("Group", false);
+     assertFalse(fr.isVisible(sf));
+     fr.setGroupVisibility("Group", true);
+     assertTrue(fr.isVisible(sf));
+     /*
+      * feature score outwith colour threshold (score > 2)
+      */
+     FeatureColourI fc = new FeatureColour(Color.white, Color.black,
+             Color.white, 0, 10);
+     fc.setAboveThreshold(true);
+     fc.setThreshold(2f);
+     fr.setColour("METAL", fc);
+     assertFalse(fr.isVisible(sf)); // score 1 is not above threshold 2
+     fc.setBelowThreshold(true);
+     assertTrue(fr.isVisible(sf)); // score 1 is below threshold 2
+     /*
+      * colour with threshold on attribute AC (value is 11)
+      */
+     fc.setAttributeName("AC");
+     assertFalse(fr.isVisible(sf)); // value 11 is not below threshold 2
+     fc.setAboveThreshold(true);
+     assertTrue(fr.isVisible(sf)); // value 11 is above threshold 2
+     fc.setAttributeName("AF"); // attribute AF is absent in sf
+     assertTrue(fr.isVisible(sf)); // feature is not excluded by threshold
+     FeatureMatcherSetI filter = new FeatureMatcherSet();
+     filter.and(FeatureMatcher.byAttribute(Condition.Contains, "pathogenic",
+             "CLIN_SIG"));
+     fr.setFeatureFilter("METAL", filter);
+     assertTrue(fr.isVisible(sf)); // feature matches filter
+     filter.and(FeatureMatcher.byScore(Condition.LE, "0.4"));
+     assertFalse(fr.isVisible(sf)); // feature doesn't match filter
+   }
  }