c57b679573435bf366a1dda09ef7288d57b71113
[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 java.awt.Color;
24 import java.util.IdentityHashMap;
25 import java.util.Map;
26
27 import jalview.datamodel.AlignmentAnnotation;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.AnnotatedCollectionI;
30 import jalview.datamodel.Annotation;
31 import jalview.datamodel.GraphLine;
32 import jalview.datamodel.SequenceCollectionI;
33 import jalview.datamodel.SequenceI;
34
35 public class AnnotationColourGradient extends FollowerColourScheme
36 {
37   public static final int NO_THRESHOLD = -1;
38
39   public static final int BELOW_THRESHOLD = 0;
40
41   public static final int ABOVE_THRESHOLD = 1;
42
43   private final AlignmentAnnotation annotation;
44
45   private final int aboveAnnotationThreshold;
46
47   private boolean thresholdIsMinMax = false;
48
49   private GraphLine annotationThreshold;
50
51   private int redMin;
52
53   private int greenMin;
54
55   private int blueMin;
56
57   private int redRange;
58
59   private int greenRange;
60
61   private int blueRange;
62
63   private boolean predefinedColours = false;
64
65   private boolean seqAssociated = false;
66
67   /**
68    * false if the scheme was constructed without a minColour and maxColour used
69    * to decide if existing colours should be taken from annotation elements when
70    * they exist
71    */
72   private boolean noGradient = false;
73
74   private IdentityHashMap<SequenceI, AlignmentAnnotation> seqannot = null;
75
76   @Override
77   public ColourSchemeI applyTo(AnnotatedCollectionI sg,
78           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
79   {
80     AnnotationColourGradient acg = new AnnotationColourGradient(annotation,
81             colourScheme, aboveAnnotationThreshold);
82     acg.thresholdIsMinMax = thresholdIsMinMax;
83     acg.annotationThreshold = (annotationThreshold == null) ? null
84             : new GraphLine(annotationThreshold);
85     acg.redMin = redMin;
86     acg.greenMin = greenMin;
87     acg.blueMin = blueMin;
88     acg.redRange = redRange;
89     acg.greenRange = greenRange;
90     acg.blueRange = blueRange;
91     acg.predefinedColours = predefinedColours;
92     acg.seqAssociated = seqAssociated;
93     acg.noGradient = noGradient;
94     return acg;
95   }
96
97   /**
98    * Creates a new AnnotationColourGradient object.
99    */
100   public AnnotationColourGradient(AlignmentAnnotation annotation,
101           ColourSchemeI originalColour, int aboveThreshold)
102   {
103     if (originalColour instanceof AnnotationColourGradient)
104     {
105       colourScheme = ((AnnotationColourGradient) originalColour).colourScheme;
106     }
107     else
108     {
109       colourScheme = originalColour;
110     }
111
112     this.annotation = annotation;
113
114     aboveAnnotationThreshold = aboveThreshold;
115
116     if (aboveThreshold != NO_THRESHOLD && annotation.threshold != null)
117     {
118       annotationThreshold = annotation.threshold;
119     }
120     // clear values so we don't get weird black bands...
121     redMin = 254;
122     greenMin = 254;
123     blueMin = 254;
124     redRange = 0;
125     greenRange = 0;
126     blueRange = 0;
127
128     noGradient = true;
129     checkLimits();
130   }
131
132   /**
133    * Creates a new AnnotationColourGradient object.
134    */
135   public AnnotationColourGradient(AlignmentAnnotation annotation,
136           Color minColour, Color maxColour, int aboveThreshold)
137   {
138     this.annotation = annotation;
139
140     aboveAnnotationThreshold = aboveThreshold;
141
142     if (aboveThreshold != NO_THRESHOLD && annotation.threshold != null)
143     {
144       annotationThreshold = annotation.threshold;
145     }
146
147     redMin = minColour.getRed();
148     greenMin = minColour.getGreen();
149     blueMin = minColour.getBlue();
150
151     redRange = maxColour.getRed() - redMin;
152     greenRange = maxColour.getGreen() - greenMin;
153     blueRange = maxColour.getBlue() - blueMin;
154
155     noGradient = false;
156     checkLimits();
157   }
158
159   private void checkLimits()
160   {
161     aamax = annotation.graphMax;
162     aamin = annotation.graphMin;
163     if (annotation.isRNA())
164     {
165       // reset colour palette
166       ColourSchemeProperty.resetRnaHelicesShading();
167       ColourSchemeProperty.initRnaHelicesShading(1 + (int) aamax);
168     }
169   }
170
171   @Override
172   public void alignmentChanged(AnnotatedCollectionI alignment,
173           Map<SequenceI, SequenceCollectionI> hiddenReps)
174   {
175     super.alignmentChanged(alignment, hiddenReps);
176
177     if (seqAssociated && annotation.getCalcId() != null)
178     {
179       if (seqannot != null)
180       {
181         seqannot.clear();
182       }
183       else
184       {
185         seqannot = new IdentityHashMap<SequenceI, AlignmentAnnotation>();
186       }
187       // resolve the context containing all the annotation for the sequence
188       AnnotatedCollectionI alcontext = alignment instanceof AlignmentI ? alignment
189               : alignment.getContext();
190       boolean f = true, rna = false;
191       for (AlignmentAnnotation alan : alcontext.findAnnotation(annotation
192               .getCalcId()))
193       {
194         if (alan.sequenceRef != null
195                 && (alan.label != null && annotation != null && alan.label
196                         .equals(annotation.label)))
197         {
198           if (!rna && alan.isRNA())
199           {
200             rna = true;
201           }
202           seqannot.put(alan.sequenceRef, alan);
203           if (f || alan.graphMax > aamax)
204           {
205             aamax = alan.graphMax;
206           }
207           if (f || alan.graphMin < aamin)
208           {
209             aamin = alan.graphMin;
210           }
211           f = false;
212         }
213       }
214       if (rna)
215       {
216         ColourSchemeProperty.initRnaHelicesShading(1 + (int) aamax);
217       }
218     }
219   }
220
221   float aamin = 0f, aamax = 0f;
222
223   public AlignmentAnnotation getAnnotation()
224   {
225     return annotation;
226   }
227
228   public int getAboveThreshold()
229   {
230     return aboveAnnotationThreshold;
231   }
232
233   public float getAnnotationThreshold()
234   {
235     if (annotationThreshold == null)
236     {
237       return 0;
238     }
239     else
240     {
241       return annotationThreshold.value;
242     }
243   }
244
245   public Color getMinColour()
246   {
247     return new Color(redMin, greenMin, blueMin);
248   }
249
250   public Color getMaxColour()
251   {
252     return new Color(redMin + redRange, greenMin + greenRange, blueMin
253             + blueRange);
254   }
255
256   /**
257    * DOCUMENT ME!
258    * 
259    * @param n
260    *          DOCUMENT ME!
261    * 
262    * @return DOCUMENT ME!
263    */
264   @Override
265   public Color findColour(char c)
266   {
267     return Color.red;
268   }
269
270   /**
271    * DOCUMENT ME!
272    * 
273    * @param n
274    *          DOCUMENT ME!
275    * @param j
276    *          DOCUMENT ME!
277    * 
278    * @return DOCUMENT ME!
279    */
280   @Override
281   public Color findColour(char c, int j, SequenceI seq)
282   {
283     Color currentColour = Color.white;
284     AlignmentAnnotation ann = (seqAssociated && seqannot != null ? seqannot
285             .get(seq) : this.annotation);
286     if (ann == null)
287     {
288       return currentColour;
289     }
290     if ((threshold == 0) || aboveThreshold(c, j))
291     {
292       if (ann.annotations != null && j < ann.annotations.length
293               && ann.annotations[j] != null
294               && !jalview.util.Comparison.isGap(c))
295       {
296         Annotation aj = ann.annotations[j];
297         // 'use original colours' => colourScheme != null
298         // -> look up colour to be used
299         // predefined colours => preconfigured shading
300         // -> only use original colours reference if thresholding enabled &
301         // minmax exists
302         // annotation.hasIcons => null or black colours replaced with glyph
303         // colours
304         // -> reuse original colours if present
305         // -> if thresholding enabled then return colour on non-whitespace glyph
306
307         if (aboveAnnotationThreshold == NO_THRESHOLD
308                 || (annotationThreshold != null && (aboveAnnotationThreshold == ABOVE_THRESHOLD ? aj.value >= annotationThreshold.value
309                         : aj.value <= annotationThreshold.value)))
310         {
311           if (predefinedColours && aj.colour != null
312                   && !aj.colour.equals(Color.black))
313           {
314             currentColour = aj.colour;
315           }
316           else if (ann.hasIcons
317                   && ann.graph == AlignmentAnnotation.NO_GRAPH)
318           {
319             if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.'
320                     && aj.secondaryStructure != '-')
321             {
322               if (colourScheme != null)
323               {
324                 currentColour = colourScheme.findColour(c, j, seq);
325               }
326               else
327               {
328                 if (ann.isRNA())
329                 {
330                   currentColour = ColourSchemeProperty.rnaHelices[(int) aj.value];
331                 }
332                 else
333                 {
334                   currentColour = ann.annotations[j].secondaryStructure == 'H' ? jalview.renderer.AnnotationRenderer.HELIX_COLOUR
335                           : ann.annotations[j].secondaryStructure == 'E' ? jalview.renderer.AnnotationRenderer.SHEET_COLOUR
336                                   : jalview.renderer.AnnotationRenderer.STEM_COLOUR;
337                 }
338               }
339             }
340             else
341             {
342               //
343               return Color.white;
344             }
345           }
346           else if (noGradient)
347           {
348             if (colourScheme != null)
349             {
350               currentColour = colourScheme.findColour(c, j, seq);
351             }
352             else
353             {
354               if (aj.colour != null)
355               {
356                 currentColour = aj.colour;
357               }
358             }
359           }
360           else
361           {
362             currentColour = shadeCalculation(ann, j);
363           }
364         }
365         if (conservationColouring)
366         {
367           currentColour = applyConservation(currentColour, j);
368         }
369       }
370     }
371     return currentColour;
372   }
373
374   /**
375    * Returns a graduated colour for the annotation at the given column. If there
376    * is a threshold value, and it is used as the top/bottom of the colour range,
377    * and the value satisfies the threshold condition, then a colour
378    * proportionate to the range from the threshold is calculated. For all other
379    * cases, a colour proportionate to the annotation's min-max range is
380    * calulated. Note that thresholding is _not_ done here (a colour is computed
381    * even if threshold is not passed).
382    * 
383    * @param ann
384    * @param col
385    * @return
386    */
387   Color shadeCalculation(AlignmentAnnotation ann, int col)
388   {
389     float range = 1f;
390     float value = ann.annotations[col].value;
391     if (thresholdIsMinMax && ann.threshold != null
392             && aboveAnnotationThreshold == ABOVE_THRESHOLD
393             && value >= ann.threshold.value)
394     {
395       range = (value - ann.threshold.value)
396               / (ann.graphMax - ann.threshold.value);
397     }
398     else if (thresholdIsMinMax && ann.threshold != null
399             && aboveAnnotationThreshold == BELOW_THRESHOLD
400             && value <= ann.threshold.value)
401     {
402       range = (value - ann.graphMin) / (ann.threshold.value - ann.graphMin);
403     }
404     else
405     {
406       if (ann.graphMax != ann.graphMin)
407       {
408         range = (value - ann.graphMin) / (ann.graphMax - ann.graphMin);
409       }
410       else
411       {
412         range = 0f;
413       }
414     }
415
416     int dr = (int) (redRange * range + redMin);
417     int dg = (int) (greenRange * range + greenMin);
418     int db = (int) (blueRange * range + blueMin);
419
420     return new Color(dr, dg, db);
421   }
422
423   public boolean isPredefinedColours()
424   {
425     return predefinedColours;
426   }
427
428   public void setPredefinedColours(boolean predefinedColours)
429   {
430     this.predefinedColours = predefinedColours;
431   }
432
433   public boolean isSeqAssociated()
434   {
435     return seqAssociated;
436   }
437
438   public void setSeqAssociated(boolean sassoc)
439   {
440     seqAssociated = sassoc;
441   }
442
443   public boolean isThresholdIsMinMax()
444   {
445     return thresholdIsMinMax;
446   }
447
448   public void setThresholdIsMinMax(boolean thresholdIsMinMax)
449   {
450     this.thresholdIsMinMax = thresholdIsMinMax;
451   }
452 }