JAL-4375 Add an AnnotationColouringI interface, and generic AnnotationColouringRanges...
[jalview.git] / src / jalview / datamodel / annotations / AnnotationColouringRanges.java
diff --git a/src/jalview/datamodel/annotations/AnnotationColouringRanges.java b/src/jalview/datamodel/annotations/AnnotationColouringRanges.java
new file mode 100644 (file)
index 0000000..bcb9e5c
--- /dev/null
@@ -0,0 +1,155 @@
+package jalview.datamodel.annotations;
+
+import java.awt.Color;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AnnotationColouringRanges extends AnnotationColouring
+{
+  private static List<Color> colours = new ArrayList<Color>();
+
+  private static List<Float> values = new ArrayList<Float>();
+
+  protected static void addValColour(float v, Color c)
+  {
+    values.add(v);
+    colours.add(c);
+  }
+
+  protected static void addFirstColour(Color c)
+  {
+    colours.add(0, c);
+  }
+
+  static
+  {
+    // e.g.
+    // addFirstColour(Color.black); -infty..25 = black
+    // addValColour(25,Color.darkGray); 25..50 = darkGray
+    // addValColour(50,Color.gray); 50..75 = gray
+    // addValColour(75,Color.lightGray); 75..100 = lightGray
+    // addValColour(100,Color.white); 100..infty = white
+  }
+
+  @Override
+  public Color valueToColour(float val)
+  {
+    Color col = null;
+    boolean set = false;
+    for (int i = 0; i < values.size(); i++)
+    {
+      float compareVal = values.get(i);
+      if (colours.size() > i)
+      {
+        col = colours.get(i);
+      }
+      if (val < compareVal)
+      {
+        set = true;
+        break;
+      }
+    }
+    if (!set && colours.size() > values.size())
+    {
+      col = colours.get(values.size());
+    }
+    return col;
+  }
+
+  @Override
+  public List<Map.Entry<Float, Color>> rangeColours(float val1, float val2)
+  {
+    String cacheKey = cacheKey(val1, val2);
+    if (!valColorsCache.containsKey(cacheKey))
+    {
+      List<Map.Entry<Float, Color>> valCols = new ArrayList<>();
+      float v1 = val1 <= val2 ? val1 : val2;
+      float v2 = val1 <= val2 ? val2 : val1;
+      boolean reversed = val1 > val2;
+      Color col = null;
+      boolean set1 = false;
+      boolean set2 = false;
+      int i = 0;
+      while (i < values.size() && (!set1 || !set2))
+      {
+        float compareVal = values.get(i);
+        if (colours.size() > i)
+        {
+          col = colours.get(i);
+        }
+
+        if (!set1 && v1 < compareVal)
+        {
+          // add the initial checkpoint
+          valCols.add(valCol(reversed ? 1f : 0f, col));
+          set1 = true;
+        }
+
+        if (!set2 && v2 < compareVal)
+        {
+          // add the final checkpoint
+          valCols.add(valCol(reversed ? 0f : 1f, col));
+          set2 = true;
+          break;
+        }
+
+        if (set1) // && !set2
+        {
+          // add an intermediate checkpoint
+          float v = (compareVal - v1) / (v2 - v1);
+          valCols.add(valCol(reversed ? 1f - v : v, col));
+        }
+
+        i++;
+      }
+      if (colours.size() > i)
+      {
+        col = colours.get(i);
+      }
+      // add above the final checkpoint colour(s) if not set
+      if (!set1)
+      {
+        valCols.add(valCol(reversed ? 1f : 0f, col));
+        set1 = true;
+      }
+      if (!set2)
+      {
+        // add the final checkpoint
+        valCols.add(valCol(reversed ? 0f : 1f, col));
+        set2 = true;
+      }
+      if (reversed)
+      {
+        Collections.reverse(valCols);
+      }
+      // put in the cache
+      valColorsCache.put(cacheKey, valCols);
+    }
+
+    return getFromCache(cacheKey);
+  }
+
+  private Map.Entry<Float, Color> valCol(Float v, Color c)
+  {
+    return new AbstractMap.SimpleEntry<Float, Color>(v, c);
+  }
+
+  private Map<String, List<Map.Entry<Float, Color>>> valColorsCache = new HashMap<String, List<Map.Entry<Float, Color>>>();
+
+  private List<Map.Entry<Float, Color>> getFromCache(String key)
+  {
+    // return a copy of the list in case of element order manipulation (e.g.
+    // valCols.remove(0))
+    return new ArrayList<Map.Entry<Float, Color>>(valColorsCache.get(key));
+  }
+
+  private static String cacheKey(float f1, float f2)
+  {
+    return new StringBuilder().append(Float.hashCode(f1)).append(' ')
+            .append(Float.hashCode(f2)).toString();
+  }
+}