JAL-2446 merged to spike branch
[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<SequenceI, AlignmentAnnotation>();
189       }
190       // resolve the context containing all the annotation for the sequence
191       AnnotatedCollectionI alcontext = alignment instanceof AlignmentI ? alignment
192               : alignment.getContext();
193       boolean f = true, rna = false;
194       for (AlignmentAnnotation alan : alcontext.findAnnotation(annotation
195               .getCalcId()))
196       {
197         if (alan.sequenceRef != null
198                 && (alan.label != null && annotation != null && alan.label
199                         .equals(annotation.label)))
200         {
201           if (!rna && alan.isRNA())
202           {
203             rna = true;
204           }
205           seqannot.put(alan.sequenceRef, alan);
206           if (f || alan.graphMax > aamax)
207           {
208             aamax = alan.graphMax;
209           }
210           if (f || alan.graphMin < aamin)
211           {
212             aamin = alan.graphMin;
213           }
214           f = false;
215         }
216       }
217       if (rna)
218       {
219         ColourSchemeProperty.initRnaHelicesShading(1 + (int) aamax);
220       }
221     }
222   }
223
224   float aamin = 0f, aamax = 0f;
225
226   public AlignmentAnnotation getAnnotation()
227   {
228     return annotation;
229   }
230
231   public int getAboveThreshold()
232   {
233     return aboveAnnotationThreshold;
234   }
235
236   public float getAnnotationThreshold()
237   {
238     if (annotationThreshold == null)
239     {
240       return 0;
241     }
242     else
243     {
244       return annotationThreshold.value;
245     }
246   }
247
248   public Color getMinColour()
249   {
250     return new Color(redMin, greenMin, blueMin);
251   }
252
253   public Color getMaxColour()
254   {
255     return new Color(redMin + redRange, greenMin + greenRange, blueMin
256             + blueRange);
257   }
258
259   /**
260    * DOCUMENT ME!
261    * 
262    * @param n
263    *          DOCUMENT ME!
264    * 
265    * @return DOCUMENT ME!
266    */
267   @Override
268   public Color findColour(char c)
269   {
270     return Color.red;
271   }
272
273   /**
274    * Returns the colour for a given character and position in a sequence
275    * 
276    * @param c
277    *          the residue character
278    * @param j
279    *          the aligned position
280    * @param seq
281    *          the sequence
282    * @return
283    */
284   @Override
285   public Color findColour(char c, int j, SequenceI seq)
286   {
287     /*
288      * locate the annotation we are configured to colour by
289      */
290     AlignmentAnnotation ann = (seqAssociated && seqannot != null ? seqannot
291             .get(seq) : this.annotation);
292
293     /*
294      * if gap or no annotation at position, no colour (White)
295      */
296     if (ann == null || ann.annotations == null
297             || j >= ann.annotations.length || ann.annotations[j] == null
298             || Comparison.isGap(c))
299     {
300       return Color.white;
301     }
302
303     Annotation aj = ann.annotations[j];
304     // 'use original colours' => colourScheme != null
305     // -> look up colour to be used
306     // predefined colours => preconfigured shading
307     // -> only use original colours reference if thresholding enabled &
308     // minmax exists
309     // annotation.hasIcons => null or black colours replaced with glyph
310     // colours
311     // -> reuse original colours if present
312     // -> if thresholding enabled then return colour on non-whitespace glyph
313
314     /*
315      * if threshold applies, and annotation fails the test - no colour (white)
316      */
317     if (annotationThreshold != null)
318     {
319       if ((aboveAnnotationThreshold == ABOVE_THRESHOLD && aj.value < annotationThreshold.value)
320               || (aboveAnnotationThreshold == BELOW_THRESHOLD && aj.value > annotationThreshold.value))
321       {
322         return Color.white;
323       }
324     }
325
326     /*
327      * If 'use original colours' then return the colour of the annotation
328      * at the aligned position - computed using the background colour scheme
329      */
330     if (predefinedColours && aj.colour != null
331             && !aj.colour.equals(Color.black))
332     {
333       return aj.colour;
334     }
335
336     Color result = Color.white;
337     if (ann.hasIcons && ann.graph == AlignmentAnnotation.NO_GRAPH)
338     {
339       /*
340        * secondary structure symbol colouring
341        */
342       if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.'
343               && aj.secondaryStructure != '-')
344       {
345         if (getColourScheme() != null)
346         {
347           result = getColourScheme().findColour(c, j, seq, null, 0f);
348         }
349         else
350         {
351           if (ann.isRNA())
352           {
353             result = ColourSchemeProperty.rnaHelices[(int) aj.value];
354           }
355           else
356           {
357             result = ann.annotations[j].secondaryStructure == 'H' ? AnnotationRenderer.HELIX_COLOUR
358                     : ann.annotations[j].secondaryStructure == 'E' ? AnnotationRenderer.SHEET_COLOUR
359                             : AnnotationRenderer.STEM_COLOUR;
360           }
361         }
362       }
363       else
364       {
365         return Color.white;
366       }
367     }
368     else if (noGradient)
369     {
370       if (getColourScheme() != null)
371       {
372         result = getColourScheme().findColour(c, j, seq, null, 0f);
373       }
374       else
375       {
376         if (aj.colour != null)
377         {
378           result = aj.colour;
379         }
380       }
381     }
382     else
383     {
384       result = shadeCalculation(ann, j);
385     }
386
387     return result;
388   }
389
390   /**
391    * Returns a graduated colour for the annotation at the given column. If there
392    * is a threshold value, and it is used as the top/bottom of the colour range,
393    * and the value satisfies the threshold condition, then a colour
394    * proportionate to the range from the threshold is calculated. For all other
395    * cases, a colour proportionate to the annotation's min-max range is
396    * calulated. Note that thresholding is _not_ done here (a colour is computed
397    * even if threshold is not passed).
398    * 
399    * @param ann
400    * @param col
401    * @return
402    */
403   Color shadeCalculation(AlignmentAnnotation ann, int col)
404   {
405     float range = 1f;
406     float value = ann.annotations[col].value;
407     if (thresholdIsMinMax && ann.threshold != null
408             && aboveAnnotationThreshold == ABOVE_THRESHOLD
409             && value >= ann.threshold.value)
410     {
411       range = (value - ann.threshold.value)
412               / (ann.graphMax - ann.threshold.value);
413     }
414     else if (thresholdIsMinMax && ann.threshold != null
415             && aboveAnnotationThreshold == BELOW_THRESHOLD
416             && value <= ann.threshold.value)
417     {
418       range = (value - ann.graphMin) / (ann.threshold.value - ann.graphMin);
419     }
420     else
421     {
422       if (ann.graphMax != ann.graphMin)
423       {
424         range = (value - ann.graphMin) / (ann.graphMax - ann.graphMin);
425       }
426       else
427       {
428         range = 0f;
429       }
430     }
431
432     int dr = (int) (redRange * range + redMin);
433     int dg = (int) (greenRange * range + greenMin);
434     int db = (int) (blueRange * range + blueMin);
435
436     return new Color(dr, dg, db);
437   }
438
439   public boolean isPredefinedColours()
440   {
441     return predefinedColours;
442   }
443
444   public void setPredefinedColours(boolean predefinedColours)
445   {
446     this.predefinedColours = predefinedColours;
447   }
448
449   public boolean isSeqAssociated()
450   {
451     return seqAssociated;
452   }
453
454   public void setSeqAssociated(boolean sassoc)
455   {
456     seqAssociated = sassoc;
457   }
458
459   public boolean isThresholdIsMinMax()
460   {
461     return thresholdIsMinMax;
462   }
463
464   public void setThresholdIsMinMax(boolean minMax)
465   {
466     this.thresholdIsMinMax = minMax;
467   }
468
469   @Override
470   public String getSchemeName()
471   {
472     return "Annotation";
473   }
474
475   @Override
476   public boolean isSimple()
477   {
478     return false;
479   }
480 }