JAL-2835 spike updated with latest
[jalview.git] / src / jalview / schemes / FeatureColour.java
index ed3e02d..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;
@@ -125,8 +163,8 @@ public class FeatureColour implements FeatureColourI
       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);
     }
@@ -236,8 +274,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);
           }
         }
       }
@@ -256,8 +294,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());
@@ -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,8 +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())
@@ -553,13 +606,32 @@ public class FeatureColour implements FeatureColourI
 
     /*
      * graduated colour case, optionally with threshold
-     * (treating Float.NaN as within visible range here)
+     * 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 noColour;
+    }
+
     if (isAboveThreshold() && scr <= threshold)
     {
       return null;
     }
+
     if (isBelowThreshold() && scr >= threshold)
     {
       return null;
@@ -568,10 +640,6 @@ public class FeatureColour implements FeatureColourI
     {
       return getMaxColour();
     }
-    if (Float.isNaN(scr))
-    {
-      return getMinColour();
-    }
     float scl = (scr - base) / range;
     if (isHighToLow)
     {
@@ -675,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;
+  }
+
 }