JAL-3691 automatic insertion of Locale.ROOT to toUpperCase() and toLowerCase() and...
[jalview.git] / src / jalview / schemes / FeatureColour.java
index e683c87..e5bda58 100644 (file)
@@ -20,6 +20,8 @@
  */
 package jalview.schemes;
 
+import java.util.Locale;
+
 import jalview.api.FeatureColourI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.features.FeatureMatcher;
@@ -194,19 +196,19 @@ public class FeatureColour implements FeatureColourI
               "Expected either 'label' or a colour specification in the line: "
                       + descriptor);
     }
-    if (nextToken.toLowerCase().startsWith(LABEL))
+    if (nextToken.toLowerCase(Locale.ROOT).startsWith(LABEL))
     {
       byLabel = true;
       // get the token after the next delimiter:
       mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
       mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
     }
-    else if (nextToken.toLowerCase().startsWith(SCORE))
+    else if (nextToken.toLowerCase(Locale.ROOT).startsWith(SCORE))
     {
       mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
       mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
     }
-    else if (nextToken.toLowerCase().startsWith(ATTRIBUTE))
+    else if (nextToken.toLowerCase(Locale.ROOT).startsWith(ATTRIBUTE))
     {
       byAttribute = true;
       attName = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
@@ -304,7 +306,7 @@ public class FeatureColour implements FeatureColourI
       }
 
       gcol.nextToken(); // skip next '|'
-      if (tok.toLowerCase().startsWith(ABSOLUTE))
+      if (tok.toLowerCase(Locale.ROOT).startsWith(ABSOLUTE))
       {
         minval = gcol.nextToken();
         gcol.nextToken(); // skip next '|'
@@ -323,19 +325,19 @@ public class FeatureColour implements FeatureColourI
       {
         if (minval.length() > 0)
         {
-          min = new Float(minval).floatValue();
+          min = Float.valueOf(minval).floatValue();
         }
       } catch (Exception e)
       {
         throw new IllegalArgumentException(
-                "Couldn't parse the minimum value for graduated colour ("
-                        + descriptor + ")");
+                "Couldn't parse the minimum value for graduated colour ('"
+                        + minval + "')");
       }
       try
       {
         if (maxval.length() > 0)
         {
-          max = new Float(maxval).floatValue();
+          max = Float.valueOf(maxval).floatValue();
         }
       } catch (Exception e)
       {
@@ -365,8 +367,8 @@ public class FeatureColour implements FeatureColourI
       Color maxColour = ColorUtils.parseColourString(maxcol);
       Color noColour = noValueColour.equals(NO_VALUE_MAX) ? maxColour
               : (noValueColour.equals(NO_VALUE_NONE) ? null : minColour);
-      featureColour = new FeatureColour(minColour, maxColour, noColour, min,
-              max);
+      featureColour = new FeatureColour(maxColour, minColour, maxColour,
+              noColour, min, max);
       featureColour.setColourByLabel(minColour == null);
       featureColour.setAutoScaled(autoScaled);
       if (byAttribute)
@@ -380,17 +382,17 @@ public class FeatureColour implements FeatureColourI
       {
         // threshold type and possibly a threshold value
         ttype = gcol.nextToken();
-        if (ttype.toLowerCase().startsWith(BELOW))
+        if (ttype.toLowerCase(Locale.ROOT).startsWith(BELOW))
         {
           featureColour.setBelowThreshold(true);
         }
-        else if (ttype.toLowerCase().startsWith(ABOVE))
+        else if (ttype.toLowerCase(Locale.ROOT).startsWith(ABOVE))
         {
           featureColour.setAboveThreshold(true);
         }
         else
         {
-          if (!ttype.toLowerCase().startsWith("no"))
+          if (!ttype.toLowerCase(Locale.ROOT).startsWith("no"))
           {
             System.err.println(
                     "Ignoring unrecognised threshold type : " + ttype);
@@ -403,7 +405,7 @@ public class FeatureColour implements FeatureColourI
         {
           gcol.nextToken();
           tval = gcol.nextToken();
-          featureColour.setThreshold(new Float(tval).floatValue());
+          featureColour.setThreshold(Float.valueOf(tval).floatValue());
         } catch (Exception e)
         {
           System.err.println("Couldn't parse threshold value as a float: ("
@@ -436,36 +438,25 @@ public class FeatureColour implements FeatureColourI
   }
 
   /**
-   * Constructor given a simple colour
+   * Constructor given a simple colour. This also 'primes' a graduated colour
+   * range, where the maximum colour is the given simple colour, and the minimum
+   * colour a paler shade of it. This is for convenience when switching from a
+   * simple colour to a graduated colour scheme.
    * 
    * @param c
    */
   public FeatureColour(Color c)
   {
-    minColour = Color.WHITE;
-    maxColour = Color.BLACK;
-    noColour = DEFAULT_NO_COLOUR;
-    minRed = 0f;
-    minGreen = 0f;
-    minBlue = 0f;
-    deltaRed = 0f;
-    deltaGreen = 0f;
-    deltaBlue = 0f;
-    colour = c;
-  }
+    /*
+     * set max colour to the simple colour, min colour to a paler shade of it
+     */
+    this(c, c == null ? Color.white : ColorUtils.bleachColour(c, 0.9f),
+            c == null ? Color.black : c, DEFAULT_NO_COLOUR, 0, 0);
 
-  /**
-   * Constructor given a colour range and a score range, defaulting 'no value
-   * colour' to be the same as minimum colour
-   * 
-   * @param low
-   * @param high
-   * @param min
-   * @param max
-   */
-  public FeatureColour(Color low, Color high, float min, float max)
-  {
-    this(low, high, low, min, max);
+    /*
+     * but enforce simple colour for now!
+     */
+    setGraduatedColour(false);
   }
 
   /**
@@ -498,29 +489,23 @@ public class FeatureColour implements FeatureColourI
   }
 
   /**
-   * Copy constructor with new min/max ranges
-   * 
-   * @param fc
-   * @param min
-   * @param max
-   */
-  public FeatureColour(FeatureColour fc, float min, float max)
-  {
-    this(fc);
-    updateBounds(min, max);
-  }
-
-  /**
-   * Constructor for a graduated colour
+   * Constructor that sets both simple and graduated colour values. This allows
+   * alternative colour schemes to be 'preserved' while switching between them
+   * to explore their effects on the visualisation.
+   * <p>
+   * This sets the colour scheme to 'graduated' by default. Override this if
+   * wanted by calling <code>setGraduatedColour(false)</code> for a simple
+   * colour, or <code>setColourByLabel(true)</code> for colour by label.
    * 
+   * @param myColour
    * @param low
    * @param high
    * @param noValueColour
    * @param min
    * @param max
    */
-  public FeatureColour(Color low, Color high, Color noValueColour,
-          float min, float max)
+  public FeatureColour(Color myColour, Color low, Color high,
+          Color noValueColour, float min, float max)
   {
     if (low == null)
     {
@@ -530,10 +515,10 @@ public class FeatureColour implements FeatureColourI
     {
       high = Color.black;
     }
-    graduatedColour = true;
-    colour = null;
+    colour = myColour;
     minColour = low;
     maxColour = high;
+    setGraduatedColour(true);
     noColour = noValueColour;
     threshold = Float.NaN;
     isHighToLow = min >= max;
@@ -565,7 +550,7 @@ public class FeatureColour implements FeatureColourI
    * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
    * false.
    */
-  void setGraduatedColour(boolean b)
+  public void setGraduatedColour(boolean b)
   {
     graduatedColour = b;
     if (b)
@@ -696,9 +681,12 @@ public class FeatureColour implements FeatureColourI
 
   /**
    * Returns the colour for the given instance of the feature. This may be a
-   * simple colour, a colour generated from the feature description (if
-   * isColourByLabel()), or a colour derived from the feature score (if
-   * isGraduatedColour()).
+   * simple colour, a colour generated from the feature description or other
+   * attribute (if isColourByLabel()), or a colour derived from the feature
+   * score or other attribute (if isGraduatedColour()).
+   * <p>
+   * Answers null if feature score (or attribute) value lies outside a
+   * configured threshold.
    * 
    * @param feature
    * @return
@@ -837,9 +825,20 @@ public class FeatureColour implements FeatureColourI
         sb.append(BAR).append(Format.getHexString(getMinColour()))
                 .append(BAR);
         sb.append(Format.getHexString(getMaxColour())).append(BAR);
-        String noValue = minColour.equals(noColour) ? NO_VALUE_MIN
-                : (maxColour.equals(noColour) ? NO_VALUE_MAX
-                        : NO_VALUE_NONE);
+        
+        /*
+         * 'no value' colour should be null, min or max colour;
+         * if none of these, coerce to minColour
+         */
+        String noValue = NO_VALUE_MIN;
+        if (maxColour.equals(noColour))
+        {
+          noValue = NO_VALUE_MAX;
+        }
+        if (noColour == null)
+        {
+          noValue = NO_VALUE_NONE;
+        }
         sb.append(noValue).append(BAR);
         if (!isAutoScaled())
         {
@@ -898,6 +897,34 @@ public class FeatureColour implements FeatureColourI
   }
 
   @Override
+  public boolean isOutwithThreshold(SequenceFeature feature)
+  {
+    if (!isGraduatedColour())
+    {
+      return false;
+    }
+    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 false;
+    }
+
+    return ((isAboveThreshold() && scr <= threshold)
+            || (isBelowThreshold() && scr >= threshold));
+  }
+
+  @Override
   public String getDescription()
   {
     if (isSimpleColour())