JAL-2835 support filter/colour by nested attribute names
[jalview.git] / src / jalview / schemes / FeatureColour.java
index 54d1c6c..71a89b0 100644 (file)
@@ -29,10 +29,28 @@ import java.awt.Color;
 import java.util.StringTokenizer;
 
 /**
- * A class that wraps either a simple colour or a graduated colour
+ * A class that represents a colour scheme for a feature type. Options supported
+ * are currently
+ * <ul>
+ * <li>a simple colour e.g. Red</li>
+ * <li>colour by label - a colour is generated from the feature description</li>
+ * <li>graduated colour by feature score</li>
+ * <ul>
+ * <li>minimum and maximum score range must be provided</li>
+ * <li>minimum and maximum value colours should be specified</li>
+ * <li>a colour for 'no value' may optionally be provided</li>
+ * <li>colours for intermediate scores are interpolated RGB values</li>
+ * <li>there is an optional threshold above/below which to colour values</li>
+ * <li>the range may be the full value range, or may be limited by the threshold
+ * value</li>
+ * </ul>
+ * <li>colour by (text) value of a named attribute</li> <li>graduated colour by
+ * (numeric) value of a named attribute</li> </ul>
  */
 public class FeatureColour implements FeatureColourI
 {
+  static final Color DEFAULT_NO_COLOUR = Color.LIGHT_GRAY;
+
   private static final String BAR = "|";
 
   final private Color colour;
@@ -41,10 +59,30 @@ public class FeatureColour implements FeatureColourI
 
   final private Color maxColour;
 
+  /*
+   * colour to use for colour by attribute when the 
+   * attribute value is absent
+   */
+  final private Color noColour;
+
+  /*
+   * if true, then colour has a gradient based on a numerical 
+   * range (either feature score, or an attribute value)
+   */
   private boolean graduatedColour;
 
+  /*
+   * if true, colour values are generated from a text string,
+   * either feature description, or an attribute value
+   */
   private boolean colourByLabel;
 
+  /*
+   * if not null, the value of [attribute, [sub-attribute] ...]
+   *  is used for colourByLabel or graduatedColour
+   */
+  private String[] attributeName;
+
   private float threshold;
 
   private float base;
@@ -288,6 +326,7 @@ public class FeatureColour implements FeatureColourI
   {
     minColour = Color.WHITE;
     maxColour = Color.BLACK;
+    noColour = DEFAULT_NO_COLOUR;
     minRed = 0f;
     minGreen = 0f;
     minBlue = 0f;
@@ -298,7 +337,8 @@ public class FeatureColour implements FeatureColourI
   }
 
   /**
-   * Constructor given a colour range and a score range
+   * Constructor given a colour range and a score range, defaulting 'no value
+   * colour' to be the same as minimum colour
    * 
    * @param low
    * @param high
@@ -307,36 +347,7 @@ 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;
-    maxColour = high;
-    threshold = Float.NaN;
-    isHighToLow = min >= max;
-    minRed = low.getRed() / 255f;
-    minGreen = low.getGreen() / 255f;
-    minBlue = low.getBlue() / 255f;
-    deltaRed = (high.getRed() / 255f) - minRed;
-    deltaGreen = (high.getGreen() / 255f) - minGreen;
-    deltaBlue = (high.getBlue() / 255f) - minBlue;
-    if (isHighToLow)
-    {
-      base = max;
-      range = min - max;
-    }
-    else
-    {
-      base = min;
-      range = max - min;
-    }
+    this(low, high, low, min, max);
   }
 
   /**
@@ -350,6 +361,7 @@ public class FeatureColour implements FeatureColourI
     colour = fc.colour;
     minColour = fc.minColour;
     maxColour = fc.maxColour;
+    noColour = fc.noColour;
     minRed = fc.minRed;
     minGreen = fc.minGreen;
     minBlue = fc.minBlue;
@@ -359,6 +371,7 @@ public class FeatureColour implements FeatureColourI
     base = fc.base;
     range = fc.range;
     isHighToLow = fc.isHighToLow;
+    attributeName = fc.attributeName;
     setAboveThreshold(fc.isAboveThreshold());
     setBelowThreshold(fc.isBelowThreshold());
     setThreshold(fc.getThreshold());
@@ -376,10 +389,45 @@ public class FeatureColour implements FeatureColourI
   public FeatureColour(FeatureColour fc, float min, float max)
   {
     this(fc);
-    graduatedColour = true;
     updateBounds(min, max);
   }
 
+  public FeatureColour(Color low, Color high, Color noValueColour,
+          float min, float max)
+  {
+    if (low == null)
+    {
+      low = Color.white;
+    }
+    if (high == null)
+    {
+      high = Color.black;
+    }
+    graduatedColour = true;
+    colour = null;
+    minColour = low;
+    maxColour = high;
+    noColour = noValueColour;
+    threshold = Float.NaN;
+    isHighToLow = min >= max;
+    minRed = low.getRed() / 255f;
+    minGreen = low.getGreen() / 255f;
+    minBlue = low.getBlue() / 255f;
+    deltaRed = (high.getRed() / 255f) - minRed;
+    deltaGreen = (high.getGreen() / 255f) - minGreen;
+    deltaBlue = (high.getBlue() / 255f) - minBlue;
+    if (isHighToLow)
+    {
+      base = max;
+      range = min - max;
+    }
+    else
+    {
+      base = min;
+      range = max - min;
+    }
+  }
+
   @Override
   public boolean isGraduatedColour()
   {
@@ -418,6 +466,12 @@ public class FeatureColour implements FeatureColourI
   }
 
   @Override
+  public Color getNoColour()
+  {
+    return noColour;
+  }
+
+  @Override
   public boolean isColourByLabel()
   {
     return colourByLabel;
@@ -506,10 +560,7 @@ public class FeatureColour implements FeatureColourI
   }
 
   /**
-   * Updates the base and range appropriately for the given minmax range
-   * 
-   * @param min
-   * @param max
+   * {@inheritDoc}
    */
   @Override
   public void updateBounds(float min, float max)
@@ -542,7 +593,10 @@ public class FeatureColour implements FeatureColourI
   {
     if (isColourByLabel())
     {
-      return ColorUtils.createColourFromName(feature.getDescription());
+      String label = attributeName == null ? feature.getDescription()
+              : feature.getValueAsString(attributeName);
+      return label == null ? noColour : ColorUtils
+              .createColourFromName(label);
     }
 
     if (!isGraduatedColour())
@@ -552,17 +606,32 @@ public class FeatureColour implements FeatureColourI
 
     /*
      * graduated colour case, optionally with threshold
+     * may be based on feature score on an attribute value
      * Float.NaN is assigned minimum visible score colour
+     * no such attribute is assigned the 'no value' colour
      */
     float scr = feature.getScore();
+    if (attributeName != null)
+    {
+      try
+      {
+        String attVal = feature.getValueAsString(attributeName);
+        scr = Float.valueOf(attVal);
+      } catch (Throwable e)
+      {
+        scr = Float.NaN;
+      }
+    }
     if (Float.isNaN(scr))
     {
-      return getMinColour();
+      return noColour;
     }
+
     if (isAboveThreshold() && scr <= threshold)
     {
       return null;
     }
+
     if (isBelowThreshold() && scr >= threshold)
     {
       return null;
@@ -674,4 +743,22 @@ public class FeatureColour implements FeatureColourI
     return String.format("%s\t%s", featureType, colourString);
   }
 
+  @Override
+  public boolean isColourByAttribute()
+  {
+    return attributeName != null;
+  }
+
+  @Override
+  public String[] getAttributeName()
+  {
+    return attributeName;
+  }
+
+  @Override
+  public void setAttributeName(String... name)
+  {
+    attributeName = name;
+  }
+
 }