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