--- /dev/null
+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();
+ }
+}