JAL-2015 JAL-1956 rollout of FeatureColourI in place of
[jalview.git] / src / jalview / schemes / FeatureColour.java
1 package jalview.schemes;
2
3 import jalview.api.FeatureColourI;
4 import jalview.datamodel.SequenceFeature;
5
6 import java.awt.Color;
7
8 /**
9  * A class that wraps either a simple colour or a graduated colour
10  */
11 public class FeatureColour implements FeatureColourI
12 {
13   final private Color colour;
14
15   final private Color minColour;
16
17   final private Color maxColour;
18
19   private boolean graduatedColour;
20
21   private boolean colourByLabel;
22
23   private float threshold;
24
25   private float base;
26
27   private float range;
28
29   private boolean belowThreshold;
30
31   private boolean aboveThreshold;
32
33   private boolean thresholdIsMinOrMax;
34
35   private boolean isHighToLow;
36
37   private boolean autoScaled;
38
39   final private float minRed;
40
41   final private float minGreen;
42
43   final private float minBlue;
44
45   final private float deltaRed;
46
47   final private float deltaGreen;
48
49   final private float deltaBlue;
50
51   /**
52    * Default constructor
53    */
54   public FeatureColour()
55   {
56     this((Color) null);
57   }
58
59   /**
60    * Constructor given a simple colour
61    * 
62    * @param c
63    */
64   public FeatureColour(Color c)
65   {
66     minColour = null;
67     maxColour = null;
68     minRed = 0f;
69     minGreen = 0f;
70     minBlue = 0f;
71     deltaRed = 0f;
72     deltaGreen = 0f;
73     deltaBlue = 0f;
74     colour = c;
75   }
76
77   /**
78    * Constructor given a colour range and a score range
79    * 
80    * @param low
81    * @param high
82    * @param min
83    * @param max
84    */
85   public FeatureColour(Color low, Color high, float min, float max)
86   {
87     graduatedColour = true;
88     colour = null;
89     minColour = low;
90     maxColour = high;
91     threshold = Float.NaN;
92     isHighToLow = min >= max;
93     minRed = low.getRed() / 255f;
94     minGreen = low.getGreen() / 255f;
95     minBlue = low.getBlue() / 255f;
96     deltaRed = (high.getRed() / 255f) - minRed;
97     deltaGreen = (high.getGreen() / 255f) - minGreen;
98     deltaBlue = (high.getBlue() / 255f) - minBlue;
99     if (isHighToLow)
100     {
101       base = max;
102       range = min - max;
103     }
104     else
105     {
106       base = min;
107       range = max - min;
108     }
109   }
110
111   /**
112    * Copy constructor
113    * 
114    * @param fc
115    */
116   public FeatureColour(FeatureColour fc)
117   {
118     colour = fc.colour;
119     minColour = fc.minColour;
120     maxColour = fc.maxColour;
121     minRed = fc.minRed;
122     minGreen = fc.minGreen;
123     minBlue = fc.minBlue;
124     deltaRed = fc.deltaRed;
125     deltaGreen = fc.deltaGreen;
126     deltaBlue = fc.deltaBlue;
127     base = fc.base;
128     range = fc.range;
129     isHighToLow = fc.isHighToLow;
130     setAboveThreshold(fc.isAboveThreshold());
131     setBelowThreshold(fc.isBelowThreshold());
132     setThreshold(fc.getThreshold());
133     setAutoScaled(fc.isAutoScaled());
134     setColourByLabel(fc.isColourByLabel());
135   }
136   
137   /**
138    * Copy constructor with new min/max ranges
139    * @param fc
140    * @param min
141    * @param max
142    */
143   public FeatureColour(FeatureColour fc, float min, float max)
144   {
145     this(fc);
146     graduatedColour = true;
147     updateBounds(min, max);
148   }
149
150   @Override
151   public boolean isGraduatedColour()
152   {
153     return graduatedColour;
154   }
155
156   /**
157    * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
158    * false.
159    */
160   @Override
161   public void setGraduatedColour(boolean b)
162   {
163     graduatedColour = b;
164     if (b)
165     {
166       setColourByLabel(false);
167     }
168   }
169
170   @Override
171   public Color getColour()
172   {
173     return colour;
174   }
175
176   @Override
177   public Color getMinColour()
178   {
179     return minColour;
180   }
181
182   @Override
183   public Color getMaxColour()
184   {
185     return maxColour;
186   }
187
188   @Override
189   public boolean isColourByLabel()
190   {
191     return colourByLabel;
192   }
193
194   /**
195    * Sets the 'colour by label' flag. If true, also sets 'graduated colour' to
196    * false.
197    */
198   @Override
199   public void setColourByLabel(boolean b)
200   {
201     colourByLabel = b;
202     if (b)
203     {
204       setGraduatedColour(false);
205     }
206   }
207   @Override
208   public boolean isBelowThreshold()
209   {
210     return belowThreshold;
211   }
212
213   @Override
214   public void setBelowThreshold(boolean b)
215   {
216     belowThreshold = b;
217     if (b)
218     {
219       setAboveThreshold(false);
220     }
221   }
222
223   @Override
224   public boolean isAboveThreshold()
225   {
226     return aboveThreshold;
227   }
228
229   @Override
230   public void setAboveThreshold(boolean b)
231   {
232     aboveThreshold = b;
233     if (b)
234     {
235       setBelowThreshold(false);
236     }
237   }
238
239   @Override
240   public boolean isThresholdMinMax()
241   {
242     return thresholdIsMinOrMax;
243   }
244
245   @Override
246   public void setThresholdMinMax(boolean b)
247   {
248     thresholdIsMinOrMax = b;
249   }
250
251   @Override
252   public float getThreshold()
253   {
254     return threshold;
255   }
256
257   @Override
258   public void setThreshold(float f)
259   {
260     threshold = f;
261   }
262
263   @Override
264   public boolean isAutoScaled()
265   {
266     return autoScaled;
267   }
268
269   @Override
270   public void setAutoScaled(boolean b)
271   {
272     this.autoScaled = b;
273   }
274
275   /**
276    * Updates the base and range appropriately for the given minmax range
277    * 
278    * @param min
279    * @param max
280    */
281   @Override
282   public void updateBounds(float min, float max)
283   {
284     if (max < min)
285     {
286       base = max;
287       range = min - max;
288       isHighToLow = true;
289     }
290     else
291     {
292       base = min;
293       range = max - min;
294       isHighToLow = false;
295     }
296   }
297
298   /**
299    * Returns the colour for the given instance of the feature. This may be a
300    * simple colour, a colour generated from the feature description (if
301    * isColourByLabel()), or a colour derived from the feature score (if
302    * isGraduatedColour()).
303    * 
304    * @param feature
305    * @return
306    */
307   @Override
308   public Color getColor(SequenceFeature feature)
309   {
310     if (isColourByLabel())
311     {
312       return UserColourScheme
313               .createColourFromName(feature.getDescription());
314     }
315
316     if (!isGraduatedColour())
317     {
318       return getColour();
319     }
320
321     // todo should we check for above/below threshold here?
322     if (range == 0.0)
323     {
324       return getMaxColour();
325     }
326     float scr = feature.getScore();
327     if (Float.isNaN(scr))
328     {
329       return getMinColour();
330     }
331     float scl = (scr - base) / range;
332     if (isHighToLow)
333     {
334       scl = -scl;
335     }
336     if (scl < 0f)
337     {
338       scl = 0f;
339     }
340     if (scl > 1f)
341     {
342       scl = 1f;
343     }
344     return new Color(minRed + scl * deltaRed, minGreen + scl * deltaGreen, minBlue + scl * deltaBlue);
345   }
346
347   /**
348    * Returns the maximum score of the graduated colour range
349    * 
350    * @return
351    */
352   @Override
353   public float getMax()
354   {
355     // regenerate the original values passed in to the constructor
356     return (isHighToLow) ? base : (base + range);
357   }
358
359   /**
360    * Returns the minimum score of the graduated colour range
361    * 
362    * @return
363    */
364   @Override
365   public float getMin()
366   {
367     // regenerate the original value passed in to the constructor
368     return (isHighToLow) ? (base + range) : base;
369   }
370
371   /**
372    * Answers true if the feature has a simple colour, or is coloured by label,
373    * or has a graduated colour and the score of this feature instance is within
374    * the range to render (if any), i.e. does not lie below or above any
375    * threshold set.
376    * 
377    * @param feature
378    * @return
379    */
380   @Override
381   public boolean isColored(SequenceFeature feature)
382   {
383     if (isColourByLabel() || !isGraduatedColour())
384     {
385       return true;
386     }
387
388     float val = feature.getScore();
389     if (Float.isNaN(val))
390     {
391       return true;
392     }
393     if (Float.isNaN(this.threshold))
394     {
395       return true;
396     }
397
398     if (isAboveThreshold() && val <= threshold)
399     {
400       return false;
401     }
402     if (isBelowThreshold() && val >= threshold)
403     {
404       return false;
405     }
406     return true;
407   }
408
409   @Override
410   public boolean isSimpleColour()
411   {
412     return (!isColourByLabel() && !isGraduatedColour());
413   }
414
415   @Override
416   public boolean hasThreshold()
417   {
418     return isAboveThreshold() || isBelowThreshold();
419   }
420
421 }