+package jalview.schemes;
+
+import jalview.api.FeatureColourI;
+import jalview.datamodel.SequenceFeature;
+
+import java.awt.Color;
+
+/**
+ * A class that wraps either a simple colour or a graduated colour
+ */
+public class FeatureColour implements FeatureColourI
+{
+ final private Color colour;
+
+ final private Color minColour;
+
+ final private Color maxColour;
+
+ private boolean graduatedColour;
+
+ private boolean colourByLabel;
+
+ private float threshold;
+
+ private float base;
+
+ private float range;
+
+ private boolean belowThreshold;
+
+ private boolean aboveThreshold;
+
+ private boolean thresholdIsMinOrMax;
+
+ private boolean isHighToLow;
+
+ private boolean autoScaled;
+
+ final private float minRed;
+
+ final private float minGreen;
+
+ final private float minBlue;
+
+ final private float deltaRed;
+
+ final private float deltaGreen;
+
+ final private float deltaBlue;
+
+ /**
+ * Default constructor
+ */
+ public FeatureColour()
+ {
+ this((Color) null);
+ }
+
+ /**
+ * Constructor given a simple colour
+ *
+ * @param c
+ */
+ public FeatureColour(Color c)
+ {
+ minColour = null;
+ maxColour = null;
+ minRed = 0f;
+ minGreen = 0f;
+ minBlue = 0f;
+ deltaRed = 0f;
+ deltaGreen = 0f;
+ deltaBlue = 0f;
+ colour = c;
+ }
+
+ /**
+ * Constructor given a colour range and a score range
+ *
+ * @param low
+ * @param high
+ * @param min
+ * @param max
+ */
+ public FeatureColour(Color low, Color high, float min, float max)
+ {
+ 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;
+ }
+ }
+
+ /**
+ * Copy constructor
+ *
+ * @param fc
+ */
+ public FeatureColour(FeatureColour fc)
+ {
+ colour = fc.colour;
+ minColour = fc.minColour;
+ maxColour = fc.maxColour;
+ minRed = fc.minRed;
+ minGreen = fc.minGreen;
+ minBlue = fc.minBlue;
+ deltaRed = fc.deltaRed;
+ deltaGreen = fc.deltaGreen;
+ deltaBlue = fc.deltaBlue;
+ base = fc.base;
+ range = fc.range;
+ isHighToLow = fc.isHighToLow;
+ setAboveThreshold(fc.isAboveThreshold());
+ setBelowThreshold(fc.isBelowThreshold());
+ setThreshold(fc.getThreshold());
+ setAutoScaled(fc.isAutoScaled());
+ setColourByLabel(fc.isColourByLabel());
+ }
+
+ /**
+ * Copy constructor with new min/max ranges
+ * @param fc
+ * @param min
+ * @param max
+ */
+ public FeatureColour(FeatureColour fc, float min, float max)
+ {
+ this(fc);
+ graduatedColour = true;
+ updateBounds(min, max);
+ }
+
+ @Override
+ public boolean isGraduatedColour()
+ {
+ return graduatedColour;
+ }
+
+ /**
+ * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
+ * false.
+ */
+ @Override
+ public void setGraduatedColour(boolean b)
+ {
+ graduatedColour = b;
+ if (b)
+ {
+ setColourByLabel(false);
+ }
+ }
+
+ @Override
+ public Color getColour()
+ {
+ return colour;
+ }
+
+ @Override
+ public Color getMinColour()
+ {
+ return minColour;
+ }
+
+ @Override
+ public Color getMaxColour()
+ {
+ return maxColour;
+ }
+
+ @Override
+ public boolean isColourByLabel()
+ {
+ return colourByLabel;
+ }
+
+ /**
+ * Sets the 'colour by label' flag. If true, also sets 'graduated colour' to
+ * false.
+ */
+ @Override
+ public void setColourByLabel(boolean b)
+ {
+ colourByLabel = b;
+ if (b)
+ {
+ setGraduatedColour(false);
+ }
+ }
+ @Override
+ public boolean isBelowThreshold()
+ {
+ return belowThreshold;
+ }
+
+ @Override
+ public void setBelowThreshold(boolean b)
+ {
+ belowThreshold = b;
+ if (b)
+ {
+ setAboveThreshold(false);
+ }
+ }
+
+ @Override
+ public boolean isAboveThreshold()
+ {
+ return aboveThreshold;
+ }
+
+ @Override
+ public void setAboveThreshold(boolean b)
+ {
+ aboveThreshold = b;
+ if (b)
+ {
+ setBelowThreshold(false);
+ }
+ }
+
+ @Override
+ public boolean isThresholdMinMax()
+ {
+ return thresholdIsMinOrMax;
+ }
+
+ @Override
+ public void setThresholdMinMax(boolean b)
+ {
+ thresholdIsMinOrMax = b;
+ }
+
+ @Override
+ public float getThreshold()
+ {
+ return threshold;
+ }
+
+ @Override
+ public void setThreshold(float f)
+ {
+ threshold = f;
+ }
+
+ @Override
+ public boolean isAutoScaled()
+ {
+ return autoScaled;
+ }
+
+ @Override
+ public void setAutoScaled(boolean b)
+ {
+ this.autoScaled = b;
+ }
+
+ /**
+ * Updates the base and range appropriately for the given minmax range
+ *
+ * @param min
+ * @param max
+ */
+ @Override
+ public void updateBounds(float min, float max)
+ {
+ if (max < min)
+ {
+ base = max;
+ range = min - max;
+ isHighToLow = true;
+ }
+ else
+ {
+ base = min;
+ range = max - min;
+ isHighToLow = false;
+ }
+ }
+
+ /**
+ * 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()).
+ *
+ * @param feature
+ * @return
+ */
+ @Override
+ public Color getColor(SequenceFeature feature)
+ {
+ if (isColourByLabel())
+ {
+ return UserColourScheme
+ .createColourFromName(feature.getDescription());
+ }
+
+ if (!isGraduatedColour())
+ {
+ return getColour();
+ }
+
+ // todo should we check for above/below threshold here?
+ if (range == 0.0)
+ {
+ return getMaxColour();
+ }
+ float scr = feature.getScore();
+ if (Float.isNaN(scr))
+ {
+ return getMinColour();
+ }
+ float scl = (scr - base) / range;
+ if (isHighToLow)
+ {
+ scl = -scl;
+ }
+ if (scl < 0f)
+ {
+ scl = 0f;
+ }
+ if (scl > 1f)
+ {
+ scl = 1f;
+ }
+ return new Color(minRed + scl * deltaRed, minGreen + scl * deltaGreen, minBlue + scl * deltaBlue);
+ }
+
+ /**
+ * Returns the maximum score of the graduated colour range
+ *
+ * @return
+ */
+ @Override
+ public float getMax()
+ {
+ // regenerate the original values passed in to the constructor
+ return (isHighToLow) ? base : (base + range);
+ }
+
+ /**
+ * Returns the minimum score of the graduated colour range
+ *
+ * @return
+ */
+ @Override
+ public float getMin()
+ {
+ // regenerate the original value passed in to the constructor
+ return (isHighToLow) ? (base + range) : base;
+ }
+
+ /**
+ * Answers true if the feature has a simple colour, or is coloured by label,
+ * or has a graduated colour and the score of this feature instance is within
+ * the range to render (if any), i.e. does not lie below or above any
+ * threshold set.
+ *
+ * @param feature
+ * @return
+ */
+ @Override
+ public boolean isColored(SequenceFeature feature)
+ {
+ if (isColourByLabel() || !isGraduatedColour())
+ {
+ return true;
+ }
+
+ float val = feature.getScore();
+ if (Float.isNaN(val))
+ {
+ return true;
+ }
+ if (Float.isNaN(this.threshold))
+ {
+ return true;
+ }
+
+ if (isAboveThreshold() && val <= threshold)
+ {
+ return false;
+ }
+ if (isBelowThreshold() && val >= threshold)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean isSimpleColour()
+ {
+ return (!isColourByLabel() && !isGraduatedColour());
+ }
+
+ @Override
+ public boolean hasThreshold()
+ {
+ return isAboveThreshold() || isBelowThreshold();
+ }
+
+}