JAL-2808 add attribute filter(s) to FeatureColour
[jalview.git] / src / jalview / schemes / FeatureColour.java
index c3b46df..480522a 100644 (file)
@@ -1,11 +1,34 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
 package jalview.schemes;
 
 import jalview.api.FeatureColourI;
 import jalview.datamodel.SequenceFeature;
+import jalview.util.ColorUtils;
 import jalview.util.Format;
+import jalview.util.matcher.KeyedMatcherI;
 
 import java.awt.Color;
 import java.util.StringTokenizer;
+import java.util.function.Function;
 
 /**
  * A class that wraps either a simple colour or a graduated colour
@@ -52,6 +75,11 @@ public class FeatureColour implements FeatureColourI
 
   final private float deltaBlue;
 
+  /*
+   * optional filter by attribute values
+   */
+  private KeyedMatcherI attributeFilters;
+
   /**
    * Parses a Jalview features file format colour descriptor
    * [label|][mincolour|maxcolour
@@ -101,11 +129,11 @@ public class FeatureColour implements FeatureColourI
       /*
        * only a simple colour specification - parse it
        */
-      Color colour = UserColourScheme.getColourFromString(descriptor);
+      Color colour = ColorUtils.parseColourString(descriptor);
       if (colour == null)
       {
-        throw new IllegalArgumentException("Invalid colour descriptor: "
-                + descriptor);
+        throw new IllegalArgumentException(
+                "Invalid colour descriptor: " + descriptor);
       }
       return new FeatureColour(colour);
     }
@@ -192,9 +220,9 @@ public class FeatureColour implements FeatureColourI
     FeatureColour featureColour;
     try
     {
-      featureColour = new FeatureColour(
-              new UserColourScheme(mincol).findColour('A'),
-              new UserColourScheme(maxcol).findColour('A'), min, max);
+      Color minColour = ColorUtils.parseColourString(mincol);
+      Color maxColour = ColorUtils.parseColourString(maxcol);
+      featureColour = new FeatureColour(minColour, maxColour, min, max);
       featureColour.setColourByLabel(labelColour);
       featureColour.setAutoScaled(autoScaled);
       // add in any additional parameters
@@ -215,8 +243,8 @@ public class FeatureColour implements FeatureColourI
         {
           if (!ttype.toLowerCase().startsWith("no"))
           {
-            System.err.println("Ignoring unrecognised threshold type : "
-                    + ttype);
+            System.err.println(
+                    "Ignoring unrecognised threshold type : " + ttype);
           }
         }
       }
@@ -235,8 +263,8 @@ public class FeatureColour implements FeatureColourI
       }
       if (gcol.hasMoreTokens())
       {
-        System.err
-                .println("Ignoring additional tokens in parameters in graduated colour specification\n");
+        System.err.println(
+                "Ignoring additional tokens in parameters in graduated colour specification\n");
         while (gcol.hasMoreTokens())
         {
           System.err.println("|" + gcol.nextToken());
@@ -286,6 +314,14 @@ public class FeatureColour implements FeatureColourI
    */
   public FeatureColour(Color low, Color high, float min, float max)
   {
+    if (low == null)
+    {
+      low = Color.white;
+    }
+    if (high == null)
+    {
+      high = Color.black;
+    }
     graduatedColour = true;
     colour = null;
     minColour = low;
@@ -330,6 +366,7 @@ public class FeatureColour implements FeatureColourI
     base = fc.base;
     range = fc.range;
     isHighToLow = fc.isHighToLow;
+    attributeFilters = fc.attributeFilters;
     setAboveThreshold(fc.isAboveThreshold());
     setBelowThreshold(fc.isBelowThreshold());
     setThreshold(fc.getThreshold());
@@ -511,10 +548,14 @@ public class FeatureColour implements FeatureColourI
   @Override
   public Color getColor(SequenceFeature feature)
   {
+    if (!matchesFilters(feature))
+    {
+      return null;
+    }
+
     if (isColourByLabel())
     {
-      return UserColourScheme
-              .createColourFromName(feature.getDescription());
+      return ColorUtils.createColourFromName(feature.getDescription());
     }
 
     if (!isGraduatedColour())
@@ -522,16 +563,27 @@ public class FeatureColour implements FeatureColourI
       return getColour();
     }
 
-    // todo should we check for above/below threshold here?
-    if (range == 0.0)
-    {
-      return getMaxColour();
-    }
+    /*
+     * graduated colour case, optionally with threshold
+     * Float.NaN is assigned minimum visible score colour
+     */
     float scr = feature.getScore();
     if (Float.isNaN(scr))
     {
       return getMinColour();
     }
+    if (isAboveThreshold() && scr <= threshold)
+    {
+      return null;
+    }
+    if (isBelowThreshold() && scr >= threshold)
+    {
+      return null;
+    }
+    if (range == 0.0)
+    {
+      return getMaxColour();
+    }
     float scl = (scr - base) / range;
     if (isHighToLow)
     {
@@ -550,6 +602,23 @@ public class FeatureColour implements FeatureColourI
   }
 
   /**
+   * Answers true if there are any attribute value filters defined, and the
+   * feature matches all of the filter conditions
+   * 
+   * @param feature
+   * 
+   * @return
+   */
+  boolean matchesFilters(SequenceFeature feature)
+  {
+    Function<String, String> valueProvider = key -> feature.otherDetails == null ? null
+                    : (feature.otherDetails.containsKey(key) ? feature.otherDetails
+                            .get(key).toString() : null);
+    return attributeFilters == null ? true : attributeFilters
+            .matches(valueProvider);
+  }
+
+  /**
    * Returns the maximum score of the graduated colour range
    * 
    * @return
@@ -573,44 +642,6 @@ public class FeatureColour implements FeatureColourI
     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()
   {
@@ -673,4 +704,21 @@ public class FeatureColour implements FeatureColourI
     return String.format("%s\t%s", featureType, colourString);
   }
 
+  /**
+   * Adds an attribute filter
+   * 
+   * @param attName
+   * @param filter
+   */
+  @Override
+  public void setAttributeFilters(KeyedMatcherI matcher)
+  {
+    attributeFilters = matcher;
+  }
+
+  @Override
+  public KeyedMatcherI getAttributeFilters()
+  {
+    return attributeFilters;
+  }
 }