Merge branch 'develop' into features/JAL-2446NCList
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Sun, 23 Jul 2017 07:59:37 +0000 (08:59 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Sun, 23 Jul 2017 07:59:37 +0000 (08:59 +0100)
Conflicts:
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java

1  2 
src/jalview/api/FeatureColourI.java
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/schemes/FeatureColour.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
test/jalview/renderer/seqfeatures/FeatureColourFinderTest.java
test/jalview/schemes/FeatureColourTest.java

@@@ -146,7 -146,7 +146,9 @@@ public interface FeatureColour
    boolean hasThreshold();
  
    /**
--   * Returns the computed colour for the given sequence feature
++   * Returns the computed colour for the given sequence feature. Answers null if
++   * the score of this feature instance is outside the range to render (if any),
++   * i.e. lies below or above a configured threshold.
     * 
     * @param feature
     * @return
    Color getColor(SequenceFeature feature);
  
    /**
--   * Answers true if the feature has a simple colour, or is coloured by label,
--   * or has a graduated colour and the score of this feature instance is within
--   * the range to render (if any), i.e. does not lie below or above any
--   * threshold set.
--   * 
--   * @param feature
--   * @return
--   */
--  boolean isColored(SequenceFeature feature);
--
--  /**
     * Update the min-max range for a graduated colour scheme
     * 
     * @param min
@@@ -300,57 -305,61 +300,66 @@@ public class FeatureRenderer extends Fe
          continue;
        }
  
 -      // loop through all features in sequence to find
 -      // current feature to render
 -      for (int sfindex = 0; sfindex < sfSize; sfindex++)
 +      FeatureColourI fc = getFeatureStyle(type);
 +      List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(
 +              visiblePositions.getBegin(), visiblePositions.getEnd(), type);
 +
 +      filterFeaturesForDisplay(overlaps, fc);
 +
 +      for (SequenceFeature sf : overlaps)
        {
 -        final SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
 -        if (!sequenceFeature.type.equals(type))
 +        Color featureColour = fc.getColor(sf);
++        if (featureColour == null)
+         {
++          // score feature outwith threshold for colouring
+           continue;
+         }
  
          /*
 -         * a feature type may be flagged as shown but the group 
 -         * an instance of it belongs to may be hidden
 +         * if feature starts/ends outside the visible range,
 +         * restrict to visible positions (or if a contact feature,
 +         * to a single position)
           */
 -        if (featureGroupNotShown(sequenceFeature))
 +        int visibleStart = sf.getBegin();
 +        if (visibleStart < visiblePositions.getBegin())
          {
 -          continue;
 +          visibleStart = sf.isContactFeature() ? sf.getEnd()
 +                  : visiblePositions.getBegin();
          }
 -
 -        /*
 -         * check feature overlaps the target range
 -         * TODO: efficient retrieval of features overlapping a range
 -         */
 -        if (sequenceFeature.getBegin() > endPos
 -                || sequenceFeature.getEnd() < startPos)
 +        int visibleEnd = sf.getEnd();
 +        if (visibleEnd > visiblePositions.getEnd())
          {
 -          continue;
 +          visibleEnd = sf.isContactFeature() ? sf.getBegin()
 +                  : visiblePositions.getEnd();
          }
  
 -        Color featureColour = getColour(sequenceFeature);
 -        if (featureColour == null)
 -        {
 -          // score feature outwith threshold for colouring
 -          continue;
 -        }
 +        int featureStartCol = seq.findIndex(visibleStart);
 +        int featureEndCol = sf.begin == sf.end ? featureStartCol : seq
 +                .findIndex(visibleEnd);
 +
-         if (sf.isContactFeature())
++        // Color featureColour = getColour(sequenceFeature);
 -        boolean isContactFeature = sequenceFeature.isContactFeature();
++        boolean isContactFeature = sf.isContactFeature();
+         if (isContactFeature)
          {
 -          boolean drawn = renderFeature(g, seq,
 -                  seq.findIndex(sequenceFeature.begin) - 1,
 -                  seq.findIndex(sequenceFeature.begin) - 1, featureColour,
 -                  start, end, y1, colourOnly);
 -          drawn |= renderFeature(g, seq,
 -                  seq.findIndex(sequenceFeature.end) - 1,
 -                  seq.findIndex(sequenceFeature.end) - 1, featureColour,
 -                  start, end, y1, colourOnly);
 +          boolean drawn = renderFeature(g, seq, featureStartCol - 1,
 +                  featureStartCol - 1, featureColour, start, end, y1,
 +                  colourOnly);
 +          drawn |= renderFeature(g, seq, featureEndCol - 1,
 +                  featureEndCol - 1, featureColour, start, end, y1,
 +                  colourOnly);
            if (drawn)
            {
              drawnColour = featureColour;
            }
          }
-         else if (showFeature(sf))
+         else
          {
 +          /*
 +           * showing feature score by height of colour
 +           * is not implemented as a selectable option 
 +           *
            if (av.isShowSequenceFeaturesHeight()
                    && !Float.isNaN(sequenceFeature.score))
            {
          continue;
        }
  
 -      for (int sfindex = 0; sfindex < sequenceFeatures.length; sfindex++)
 +      List<SequenceFeature> overlaps = seq.findFeatures(column, column,
 +              type);
 +      for (SequenceFeature sequenceFeature : overlaps)
        {
 -        SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
 -        if (!sequenceFeature.type.equals(type))
 +        if (!featureGroupNotShown(sequenceFeature))
          {
-           return getColour(sequenceFeature);
 -          continue;
 -        }
 -
 -        if (featureGroupNotShown(sequenceFeature))
 -        {
 -          continue;
 -        }
 -
 -        /*
 -         * check the column position is within the feature range
 -         * (or is one of the two contact positions for a contact feature)
 -         */
 -        boolean featureIsAtPosition = sequenceFeature.begin <= pos
 -                && sequenceFeature.end >= pos;
 -        if (sequenceFeature.isContactFeature())
 -        {
 -          featureIsAtPosition = sequenceFeature.begin == pos
 -                  || sequenceFeature.end == pos;
 -        }
 -        if (featureIsAtPosition)
 -        {
 -          return getColour(sequenceFeature);
++          Color col = getColour(sequenceFeature);
++          if (col != null)
++          {
++            return col;
++          }
          }
        }
      }
@@@ -551,12 -551,12 +551,23 @@@ public class FeatureColour implements F
        return getColour();
      }
  
--    // todo should we check for above/below threshold here?
++    /*
++     * graduated colour case, optionally with threshold
++     * (treating Float.NaN as within visible range here)
++     */
++    float scr = feature.getScore();
++    if (isAboveThreshold() && scr <= threshold)
++    {
++      return null;
++    }
++    if (isBelowThreshold() && scr >= threshold)
++    {
++      return null;
++    }
      if (range == 0.0)
      {
        return getMaxColour();
      }
--    float scr = feature.getScore();
      if (Float.isNaN(scr))
      {
        return getMinColour();
      return (isHighToLow) ? (base + range) : base;
    }
  
--  /**
--   * Answers true if the feature has a simple colour, or is coloured by label,
--   * or has a graduated colour and the score of this feature instance is within
--   * the range to render (if any), i.e. does not lie below or above any
--   * threshold set.
--   * 
--   * @param feature
--   * @return
--   */
--  @Override
--  public boolean isColored(SequenceFeature feature)
--  {
--    if (isColourByLabel() || !isGraduatedColour())
--    {
--      return true;
--    }
--
--    float val = feature.getScore();
--    if (Float.isNaN(val))
--    {
--      return true;
--    }
--    if (Float.isNaN(this.threshold))
--    {
--      return true;
--    }
--
--    if (isAboveThreshold() && val <= threshold)
--    {
--      return false;
--    }
--    if (isBelowThreshold() && val >= threshold)
--    {
--      return false;
--    }
--    return true;
--  }
--
    @Override
    public boolean isSimpleColour()
    {
@@@ -559,7 -567,7 +559,8 @@@ public abstract class FeatureRendererMo
     * Returns the configured colour for a particular feature instance. This
     * includes calculation of 'colour by label', or of a graduated score colour,
     * if applicable. It does not take into account feature visibility or colour
--   * transparency.
++   * transparency. Returns null for a score feature whose score value lies
++   * outside any colour threshold.
     * 
     * @param feature
     * @return
@@@ -22,6 -22,6 +22,7 @@@ package jalview.schemes
  
  import static org.testng.AssertJUnit.assertEquals;
  import static org.testng.AssertJUnit.assertFalse;
++import static org.testng.AssertJUnit.assertNull;
  import static org.testng.AssertJUnit.assertTrue;
  import static org.testng.AssertJUnit.fail;
  
@@@ -84,62 -84,56 +85,6 @@@ public class FeatureColourTes
    }
  
    @Test(groups = { "Functional" })
--  public void testIsColored_simpleColour()
--  {
--    FeatureColour fc = new FeatureColour(Color.RED);
-     assertTrue(fc
-             .isColored(new SequenceFeature("Cath", "", 1, 2, 0f, null)));
-   }
-   @Test(groups = { "Functional" })
-   public void testIsColored_colourByLabel()
-   {
-     FeatureColour fc = new FeatureColour();
-     fc.setColourByLabel(true);
-     assertTrue(fc
-             .isColored(new SequenceFeature("Cath", "", 1, 2, 0f, null)));
-   }
-   @Test(groups = { "Functional" })
-   public void testIsColored_aboveThreshold()
-   {
-     // graduated colour range from score 20 to 100
-     FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 20f,
-             100f);
-     // score 0 is adjusted to bottom of range
-     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 0f,
-             null);
-     assertTrue(fc.isColored(sf));
-     assertEquals(Color.WHITE, fc.getColor(sf));
-     // score 120 is adjusted to top of range
-     sf = new SequenceFeature(sf, sf.getBegin(), sf.getEnd(),
-             sf.getFeatureGroup(), 120f);
-     assertEquals(Color.BLACK, fc.getColor(sf));
-     // value below threshold is still rendered
-     // setting threshold has no effect yet...
-     fc.setThreshold(60f);
-     sf = new SequenceFeature(sf, sf.getBegin(), sf.getEnd(),
-             sf.getFeatureGroup(), 36f);
-     assertTrue(fc.isColored(sf));
-     assertEquals(new Color(204, 204, 204), fc.getColor(sf));
-     // now apply threshold:
-     fc.setAboveThreshold(true);
-     assertFalse(fc.isColored(sf));
-     // colour is still returned though ?!?
-     assertEquals(new Color(204, 204, 204), fc.getColor(sf));
-     sf = new SequenceFeature(sf, sf.getBegin(), sf.getEnd(),
-             sf.getFeatureGroup(), 84f);
-     // above threshold now
-     assertTrue(fc.isColored(sf));
-     assertEquals(new Color(51, 51, 51), fc.getColor(sf));
-   }
-   @Test(groups = { "Functional" })
 -    assertTrue(fc.isColored(new SequenceFeature()));
 -  }
 -
 -  @Test(groups = { "Functional" })
 -  public void testIsColored_colourByLabel()
 -  {
 -    FeatureColour fc = new FeatureColour();
 -    fc.setColourByLabel(true);
 -    assertTrue(fc.isColored(new SequenceFeature()));
 -  }
 -
 -  @Test(groups = { "Functional" })
 -  public void testIsColored_aboveThreshold()
 -  {
 -    // graduated colour range from score 20 to 100
 -    FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 20f,
 -            100f);
 -
 -    // score 0 is adjusted to bottom of range
 -    SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 0f,
 -            null);
 -    assertTrue(fc.isColored(sf));
 -    assertEquals(Color.WHITE, fc.getColor(sf));
 -
 -    // score 120 is adjusted to top of range
 -    sf.setScore(120f);
 -    assertEquals(Color.BLACK, fc.getColor(sf));
 -
 -    // value below threshold is still rendered
 -    // setting threshold has no effect yet...
 -    fc.setThreshold(60f);
 -    sf.setScore(36f);
 -    assertTrue(fc.isColored(sf));
 -    assertEquals(new Color(204, 204, 204), fc.getColor(sf));
 -
 -    // now apply threshold:
 -    fc.setAboveThreshold(true);
 -    assertFalse(fc.isColored(sf));
 -    // colour is still returned though ?!?
 -    assertEquals(new Color(204, 204, 204), fc.getColor(sf));
 -
 -    sf.setScore(84); // above threshold now
 -    assertTrue(fc.isColored(sf));
 -    assertEquals(new Color(51, 51, 51), fc.getColor(sf));
 -  }
 -
 -  @Test(groups = { "Functional" })
    public void testGetColor_simpleColour()
    {
      FeatureColour fc = new FeatureColour(Color.RED);
    }
  
    @Test(groups = { "Functional" })
--  public void testGetColor_belowThreshold()
++  public void testGetColor_aboveBelowThreshold()
    {
      // gradient from [50, 150] from WHITE(255, 255, 255) to BLACK(0, 0, 0)
      FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 50f,
      SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 70f,
              null);
      fc.setThreshold(100f); // ignore for now
      assertEquals(new Color(204, 204, 204), fc.getColor(sf));
  
      fc.setAboveThreshold(true); // feature lies below threshold
--    assertFalse(fc.isColored(sf));
--    assertEquals(new Color(204, 204, 204), fc.getColor(sf));
++    assertNull(fc.getColor(sf));
++
++    fc.setBelowThreshold(true);
++    fc.setThreshold(70f);
++    assertNull(fc.getColor(sf)); // feature score == threshold - hidden
++    fc.setThreshold(69f);
++    assertNull(fc.getColor(sf)); // feature score > threshold - hidden
    }
  
    /**