Merge branch 'feature/JAL-3180colourAnnotationMenu' into merge/JAL-3180
[jalview.git] / src / jalview / schemes / AnnotationColourGradient.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.schemes;
22
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.AnnotatedCollectionI;
26 import jalview.datamodel.Annotation;
27 import jalview.datamodel.GraphLine;
28 import jalview.datamodel.SequenceCollectionI;
29 import jalview.datamodel.SequenceI;
30 import jalview.renderer.AnnotationRenderer;
31 import jalview.util.Comparison;
32
33 import java.awt.Color;
34 import java.util.IdentityHashMap;
35 import java.util.Map;
36
37 public class AnnotationColourGradient extends FollowerColourScheme
38 {
39   public static final int NO_THRESHOLD = -1;
40
41   public static final int BELOW_THRESHOLD = 0;
42
43   public static final int ABOVE_THRESHOLD = 1;
44
45   private final AlignmentAnnotation annotation;
46
47   private final int aboveAnnotationThreshold;
48
49   public boolean thresholdIsMinMax = false;
50
51   private GraphLine annotationThreshold;
52
53   private int redMin;
54
55   private int greenMin;
56
57   private int blueMin;
58
59   private int redRange;
60
61   private int greenRange;
62
63   private int blueRange;
64
65   private boolean predefinedColours = false;
66
67   private boolean seqAssociated = false;
68
69   /**
70    * false if the scheme was constructed without a minColour and maxColour used
71    * to decide if existing colours should be taken from annotation elements when
72    * they exist
73    */
74   private boolean noGradient = false;
75
76   private IdentityHashMap<SequenceI, AlignmentAnnotation> seqannot = null;
77
78   @Override
79   public ColourSchemeI getInstance(AnnotatedCollectionI sg,
80           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
81   {
82     AnnotationColourGradient acg = new AnnotationColourGradient(annotation,
83             getColourScheme(), aboveAnnotationThreshold);
84     acg.thresholdIsMinMax = thresholdIsMinMax;
85     acg.annotationThreshold = (annotationThreshold == null) ? null
86             : new GraphLine(annotationThreshold);
87     acg.redMin = redMin;
88     acg.greenMin = greenMin;
89     acg.blueMin = blueMin;
90     acg.redRange = redRange;
91     acg.greenRange = greenRange;
92     acg.blueRange = blueRange;
93     acg.predefinedColours = predefinedColours;
94     acg.seqAssociated = seqAssociated;
95     acg.noGradient = noGradient;
96     return acg;
97   }
98
99   /**
100    * Creates a new AnnotationColourGradient object.
101    */
102   public AnnotationColourGradient(AlignmentAnnotation annotation,
103           ColourSchemeI originalColour, int aboveThreshold)
104   {
105     if (originalColour instanceof AnnotationColourGradient)
106     {
107       setColourScheme(((AnnotationColourGradient) originalColour)
108               .getColourScheme());
109     }
110     else
111     {
112       setColourScheme(originalColour);
113     }
114
115     this.annotation = annotation;
116
117     aboveAnnotationThreshold = aboveThreshold;
118
119     if (aboveThreshold != NO_THRESHOLD && annotation.threshold != null)
120     {
121       annotationThreshold = annotation.threshold;
122     }
123     // clear values so we don't get weird black bands...
124     redMin = 254;
125     greenMin = 254;
126     blueMin = 254;
127     redRange = 0;
128     greenRange = 0;
129     blueRange = 0;
130
131     noGradient = true;
132     checkLimits();
133   }
134
135   /**
136    * Creates a new AnnotationColourGradient object.
137    */
138   public AnnotationColourGradient(AlignmentAnnotation annotation,
139           Color minColour, Color maxColour, int aboveThreshold)
140   {
141     this.annotation = annotation;
142
143     aboveAnnotationThreshold = aboveThreshold;
144
145     if (aboveThreshold != NO_THRESHOLD && annotation.threshold != null)
146     {
147       annotationThreshold = annotation.threshold;
148     }
149
150     redMin = minColour.getRed();
151     greenMin = minColour.getGreen();
152     blueMin = minColour.getBlue();
153
154     redRange = maxColour.getRed() - redMin;
155     greenRange = maxColour.getGreen() - greenMin;
156     blueRange = maxColour.getBlue() - blueMin;
157
158     noGradient = false;
159     checkLimits();
160   }
161
162   private void checkLimits()
163   {
164     aamax = annotation.graphMax;
165     aamin = annotation.graphMin;
166     if (annotation.isRNA())
167     {
168       // reset colour palette
169       ColourSchemeProperty.resetRnaHelicesShading();
170       ColourSchemeProperty.initRnaHelicesShading(1 + (int) aamax);
171     }
172   }
173
174   @Override
175   public void alignmentChanged(AnnotatedCollectionI alignment,
176           Map<SequenceI, SequenceCollectionI> hiddenReps)
177   {
178     super.alignmentChanged(alignment, hiddenReps);
179
180     if (seqAssociated && annotation.getCalcId() != null)
181     {
182       if (seqannot != null)
183       {
184         seqannot.clear();
185       }
186       else
187       {
188         seqannot = new IdentityHashMap<>();
189       }
190       // resolve the context containing all the annotation for the sequence
191       AnnotatedCollectionI alcontext = alignment instanceof AlignmentI
192               ? alignment
193               : alignment.getContext();
194       boolean f = true, rna = false;
195       for (AlignmentAnnotation alan : alcontext
196               .findAnnotation(annotation.getCalcId()))
197       {
198         if (alan.sequenceRef != null
199                 && (alan.label != null && annotation != null
200                         && alan.label.equals(annotation.label)))
201         {
202           if (!rna && alan.isRNA())
203           {
204             rna = true;
205           }
206           seqannot.put(alan.sequenceRef, alan);
207           if (f || alan.graphMax > aamax)
208           {
209             aamax = alan.graphMax;
210           }
211           if (f || alan.graphMin < aamin)
212           {
213             aamin = alan.graphMin;
214           }
215           f = false;
216         }
217       }
218       if (rna)
219       {
220         ColourSchemeProperty.initRnaHelicesShading(1 + (int) aamax);
221       }
222     }
223   }
224
225   float aamin = 0f, aamax = 0f;
226
227   public AlignmentAnnotation getAnnotation()
228   {
229     return annotation;
230   }
231
232   public int getAboveThreshold()
233   {
234     return aboveAnnotationThreshold;
235   }
236
237   public float getAnnotationThreshold()
238   {
239     if (annotationThreshold == null)
240     {
241       return 0;
242     }
243     else
244     {
245       return annotationThreshold.value;
246     }
247   }
248
249   public Color getMinColour()
250   {
251     return new Color(redMin, greenMin, blueMin);
252   }
253
254   public Color getMaxColour()
255   {
256     return new Color(redMin + redRange, greenMin + greenRange,
257             blueMin + blueRange);
258   }
259
260   /**
261    * DOCUMENT ME!
262    * 
263    * @param n
264    *          DOCUMENT ME!
265    * 
266    * @return DOCUMENT ME!
267    */
268   @Override
269   public Color findColour(char c)
270   {
271     return Color.red;
272   }
273
274   /**
275    * Returns the colour for a given character and position in a sequence
276    * 
277    * @param c
278    *          the residue character
279    * @param j
280    *          the aligned position
281    * @param seq
282    *          the sequence
283    * @return
284    */
285   @Override
286   public Color findColour(char c, int j, SequenceI seq)
287   {
288     /*
289      * locate the annotation we are configured to colour by
290      */
291     AlignmentAnnotation ann = (seqAssociated && seqannot != null
292             ? seqannot.get(seq)
293             : this.annotation);
294
295     /*
296      * if gap or no annotation at position, no colour (White)
297      */
298     if (ann == null || ann.annotations == null
299             || j >= ann.annotations.length || ann.annotations[j] == null
300             || Comparison.isGap(c))
301     {
302       return Color.white;
303     }
304
305     Annotation aj = ann.annotations[j];
306     // 'use original colours' => colourScheme != null
307     // -> look up colour to be used
308     // predefined colours => preconfigured shading
309     // -> only use original colours reference if thresholding enabled &
310     // minmax exists
311     // annotation.hasIcons => null or black colours replaced with glyph
312     // colours
313     // -> reuse original colours if present
314     // -> if thresholding enabled then return colour on non-whitespace glyph
315
316     /*
317      * if threshold applies, and annotation fails the test - no colour (white)
318      */
319     if (annotationThreshold != null)
320     {
321       if ((aboveAnnotationThreshold == ABOVE_THRESHOLD
322               && aj.value < annotationThreshold.value)
323               || (aboveAnnotationThreshold == BELOW_THRESHOLD
324                       && aj.value > annotationThreshold.value))
325       {
326         return Color.white;
327       }
328     }
329
330     /*
331      * If 'use original colours' then return the colour of the annotation
332      * at the aligned position - computed using the background colour scheme
333      */
334     if (predefinedColours && aj.colour != null
335             && !aj.colour.equals(Color.black))
336     {
337       return aj.colour;
338     }
339
340     Color result = Color.white;
341     if (ann.hasIcons && ann.graph == AlignmentAnnotation.NO_GRAPH)
342     {
343       /*
344        * secondary structure symbol colouring
345        */
346       if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.'
347               && aj.secondaryStructure != '-')
348       {
349         if (getColourScheme() != null)
350         {
351           result = getColourScheme().findColour(c, j, seq, null, 0f);
352         }
353         else
354         {
355           if (ann.isRNA())
356           {
357             result = ColourSchemeProperty.rnaHelices[(int) aj.value];
358           }
359           else
360           {
361             result = ann.annotations[j].secondaryStructure == 'H'
362                     ? AnnotationRenderer.HELIX_COLOUR
363                     : ann.annotations[j].secondaryStructure == 'E'
364                             ? AnnotationRenderer.SHEET_COLOUR
365                             : AnnotationRenderer.STEM_COLOUR;
366           }
367         }
368       }
369       else
370       {
371         return Color.white;
372       }
373     }
374     else if (noGradient)
375     {
376       if (getColourScheme() != null)
377       {
378         result = getColourScheme().findColour(c, j, seq, null, 0f);
379       }
380       else
381       {
382         if (aj.colour != null)
383         {
384           result = aj.colour;
385         }
386       }
387     }
388     else
389     {
390       result = shadeCalculation(ann, j);
391     }
392
393     return result;
394   }
395
396   /**
397    * Returns a graduated colour for the annotation at the given column. If there
398    * is a threshold value, and it is used as the top/bottom of the colour range,
399    * and the value satisfies the threshold condition, then a colour
400    * proportionate to the range from the threshold is calculated. For all other
401    * cases, a colour proportionate to the annotation's min-max range is
402    * calulated. Note that thresholding is _not_ done here (a colour is computed
403    * even if threshold is not passed).
404    * 
405    * @param ann
406    * @param col
407    * @return
408    */
409   Color shadeCalculation(AlignmentAnnotation ann, int col)
410   {
411     float range = 1f;
412     float value = ann.annotations[col].value;
413     if (thresholdIsMinMax && ann.threshold != null
414             && aboveAnnotationThreshold == ABOVE_THRESHOLD
415             && value >= ann.threshold.value)
416     {
417       range = ann.graphMax == ann.threshold.value ? 1f
418               : (value - ann.threshold.value)
419               / (ann.graphMax - ann.threshold.value);
420     }
421     else if (thresholdIsMinMax && ann.threshold != null
422             && aboveAnnotationThreshold == BELOW_THRESHOLD
423             && value <= ann.threshold.value)
424     {
425       range = ann.graphMin == ann.threshold.value ? 0f
426               : (value - ann.graphMin)
427                       / (ann.threshold.value - ann.graphMin);
428     }
429     else
430     {
431       if (ann.graphMax != ann.graphMin)
432       {
433         range = (value - ann.graphMin) / (ann.graphMax - ann.graphMin);
434       }
435       else
436       {
437         range = 0f;
438       }
439     }
440
441     int dr = (int) (redRange * range + redMin);
442     int dg = (int) (greenRange * range + greenMin);
443     int db = (int) (blueRange * range + blueMin);
444
445     return new Color(dr, dg, db);
446   }
447
448   public boolean isPredefinedColours()
449   {
450     return predefinedColours;
451   }
452
453   public void setPredefinedColours(boolean predefinedColours)
454   {
455     this.predefinedColours = predefinedColours;
456   }
457
458   public boolean isSeqAssociated()
459   {
460     return seqAssociated;
461   }
462
463   public void setSeqAssociated(boolean sassoc)
464   {
465     seqAssociated = sassoc;
466   }
467
468   public boolean isThresholdIsMinMax()
469   {
470     return thresholdIsMinMax;
471   }
472
473   public void setThresholdIsMinMax(boolean minMax)
474   {
475     this.thresholdIsMinMax = minMax;
476   }
477
478   @Override
479   public String getSchemeName()
480   {
481     return ANNOTATION_COLOUR;
482   }
483
484   @Override
485   public boolean isSimple()
486   {
487     return false;
488   }
489 }