JAL-1593 allow per-sequence shading of RNA helix (stored in each annotation element...
[jalview.git] / src / jalview / schemes / AnnotationColourGradient.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
3  * Copyright (C) 2014 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
31 import java.awt.Color;
32 import java.util.IdentityHashMap;
33 import java.util.Map;
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   public AlignmentAnnotation annotation;
44
45   int aboveAnnotationThreshold = -1;
46
47   public boolean thresholdIsMinMax = false;
48
49   GraphLine annotationThreshold;
50
51   float r1, g1, b1, rr, gg, bb;
52
53   private boolean predefinedColours = false;
54
55   private boolean seqAssociated = false;
56   Color rnaHelices[] = null;
57   private void initRnaHelicesShading(int n)
58   {
59     int j = 0;
60     if (rnaHelices==null) 
61     {
62       rnaHelices = new Color[n + 1];
63     }
64     else
65     if (rnaHelices != null && rnaHelices.length <= n)
66     {
67       Color[] t = new Color[n + 1];
68       System.arraycopy(rnaHelices, 0, t, 0, rnaHelices.length);
69       j = rnaHelices.length;
70       rnaHelices = t;
71     }
72     else
73     {
74       return;
75     }
76     // Generate random colors and store
77     for (; j <= n; j++)
78     {
79       rnaHelices[j] = jalview.util.ColorUtils
80               .generateRandomColor(Color.white);
81     }
82   }
83   /**
84    * false if the scheme was constructed without a minColour and maxColour used
85    * to decide if existing colours should be taken from annotation elements when
86    * they exist
87    */
88   private boolean noGradient = false;
89   IdentityHashMap<SequenceI, AlignmentAnnotation> seqannot = null;
90
91   @Override
92   public ColourSchemeI applyTo(AnnotatedCollectionI sg,
93           Map<SequenceI, SequenceCollectionI> hiddenRepSequences)
94   {
95     AnnotationColourGradient acg = new AnnotationColourGradient(annotation,
96             colourScheme, aboveAnnotationThreshold);
97     acg.thresholdIsMinMax = thresholdIsMinMax;
98     acg.annotationThreshold = (annotationThreshold == null) ? null
99             : new GraphLine(annotationThreshold);
100     acg.r1 = r1;
101     acg.g1 = g1;
102     acg.b1 = b1;
103     acg.rr = rr;
104     acg.gg = gg;
105     acg.bb = bb;
106     acg.predefinedColours = predefinedColours;
107     acg.seqAssociated = seqAssociated;
108     acg.noGradient = noGradient;
109     return acg;
110   }
111
112   /**
113    * Creates a new AnnotationColourGradient object.
114    */
115   public AnnotationColourGradient(AlignmentAnnotation annotation,
116           ColourSchemeI originalColour, int aboveThreshold)
117   {
118     if (originalColour instanceof AnnotationColourGradient)
119     {
120       colourScheme = ((AnnotationColourGradient) originalColour).colourScheme;
121     }
122     else
123     {
124       colourScheme = originalColour;
125     }
126
127     this.annotation = annotation;
128
129     aboveAnnotationThreshold = aboveThreshold;
130
131     if (aboveThreshold != NO_THRESHOLD && annotation.threshold != null)
132     {
133       annotationThreshold = annotation.threshold;
134     }
135     // clear values so we don't get weird black bands...
136     r1 = 254;
137     g1 = 254;
138     b1 = 254;
139     rr = 0;
140     gg = 0;
141     bb = 0;
142
143     noGradient = true;
144   }
145
146   /**
147    * Creates a new AnnotationColourGradient object.
148    */
149   public AnnotationColourGradient(AlignmentAnnotation annotation,
150           Color minColour, Color maxColour, int aboveThreshold)
151   {
152     this.annotation = annotation;
153
154     aboveAnnotationThreshold = aboveThreshold;
155
156     if (aboveThreshold != NO_THRESHOLD && annotation.threshold != null)
157     {
158       annotationThreshold = annotation.threshold;
159     }
160
161     r1 = minColour.getRed();
162     g1 = minColour.getGreen();
163     b1 = minColour.getBlue();
164
165     rr = maxColour.getRed() - r1;
166     gg = maxColour.getGreen() - g1;
167     bb = maxColour.getBlue() - b1;
168
169     noGradient = false;
170     aamax = annotation.graphMax;
171     aamin = annotation.graphMin;
172     if (annotation.isRNA())
173     {
174       initRnaHelicesShading(1 + (int) aamax);
175     }
176   }
177
178   @Override
179   public void alignmentChanged(AnnotatedCollectionI alignment,
180           Map<SequenceI, SequenceCollectionI> hiddenReps)
181   {
182     super.alignmentChanged(alignment, hiddenReps);
183
184     if (seqAssociated && annotation.getCalcId() != null)
185     {
186       if (seqannot != null)
187       {
188         seqannot.clear();
189       }
190       else
191       {
192         seqannot = new IdentityHashMap<SequenceI, AlignmentAnnotation>();
193       }
194       // resolve the context containing all the annotation for the sequence
195       AnnotatedCollectionI alcontext = alignment instanceof AlignmentI ? alignment
196               : alignment.getContext();
197       boolean f = true,rna=false;
198       for (AlignmentAnnotation alan : alcontext.findAnnotation(annotation
199               .getCalcId()))
200       {
201         if (alan.sequenceRef != null
202                 && (alan.label != null && annotation != null && alan.label
203                         .equals(annotation.label)))
204         {
205           if (!rna && alan.isRNA())
206           {
207             rna = true;
208           }
209           seqannot.put(alan.sequenceRef, alan);
210           if (f || alan.graphMax > aamax)
211           {
212             aamax = alan.graphMax;
213           }
214           if (f || alan.graphMin < aamin)
215           {
216             aamin = alan.graphMin;
217           }
218           f = false;
219         }
220       }
221       if (rna)
222       {
223         initRnaHelicesShading(1 + (int) aamax);
224       }
225     }
226   }
227
228   float aamin = 0f, aamax = 0f;
229   public String getAnnotation()
230   {
231     return annotation.label;
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((int) r1, (int) g1, (int) b1);
254   }
255
256   public Color getMaxColour()
257   {
258     return new Color((int) (r1 + rr), (int) (g1 + gg), (int) (b1 + bb));
259   }
260
261   /**
262    * DOCUMENT ME!
263    * 
264    * @param n
265    *          DOCUMENT ME!
266    * 
267    * @return DOCUMENT ME!
268    */
269   public Color findColour(char c)
270   {
271     return Color.red;
272   }
273
274   /**
275    * DOCUMENT ME!
276    * 
277    * @param n
278    *          DOCUMENT ME!
279    * @param j
280    *          DOCUMENT ME!
281    * 
282    * @return DOCUMENT ME!
283    */
284   @Override
285   public Color findColour(char c, int j, SequenceI seq)
286   {
287     Color currentColour = Color.white;
288     AlignmentAnnotation annotation = (seqAssociated ? seqannot.get(seq)
289             : this.annotation);
290     if (annotation == null)
291     {
292       return currentColour;
293     }
294     if ((threshold == 0) || aboveThreshold(c, j))
295     {
296       if (annotation.annotations != null
297               && j < annotation.annotations.length
298               && annotation.annotations[j] != null
299               && !jalview.util.Comparison.isGap(c))
300       {
301         Annotation aj = annotation.annotations[j];
302         // 'use original colours' => colourScheme != null
303         // -> look up colour to be used
304         // predefined colours => preconfigured shading
305         // -> only use original colours reference if thresholding enabled &
306         // minmax exists
307         // annotation.hasIcons => null or black colours replaced with glyph
308         // colours
309         // -> reuse original colours if present
310         // -> if thresholding enabled then return colour on non-whitespace glyph
311
312         if (aboveAnnotationThreshold == NO_THRESHOLD
313                 || (annotationThreshold != null && (aboveAnnotationThreshold == ABOVE_THRESHOLD ? aj.value >= annotationThreshold.value
314                         : aj.value <= annotationThreshold.value)))
315         {
316           if (predefinedColours && aj.colour != null)
317           {
318             currentColour = aj.colour;
319           }
320           else if (annotation.hasIcons
321                   && annotation.graph == AlignmentAnnotation.NO_GRAPH)
322           {
323             if (aj.secondaryStructure > ' ' && aj.secondaryStructure != '.'
324                     && aj.secondaryStructure != '-')
325             {
326               if (colourScheme != null)
327               {
328                 currentColour = colourScheme.findColour(c, j, seq);
329               }
330               else
331               {
332                 if (annotation.isRNA())
333                 {
334                   currentColour = rnaHelices[(int) aj.value];
335                 }
336                 else
337                 {
338                   currentColour = annotation.annotations[j].secondaryStructure == 'H' ? jalview.renderer.AnnotationRenderer.HELIX_COLOUR
339                           : annotation.annotations[j].secondaryStructure == 'E' ? jalview.renderer.AnnotationRenderer.SHEET_COLOUR
340                                   : jalview.renderer.AnnotationRenderer.STEM_COLOUR;
341                 }
342               }
343             }
344             else
345             {
346               //
347               return Color.white;
348             }
349           }
350           else if (noGradient)
351           {
352             if (colourScheme != null)
353             {
354               currentColour = colourScheme.findColour(c, j, seq);
355             }
356             else
357             {
358               if (aj.colour != null)
359               {
360                 currentColour = aj.colour;
361               }
362             }
363           }
364           else
365           {
366             currentColour = shadeCalculation(annotation, j);
367           }
368         }
369         if (conservationColouring)
370         {
371           currentColour = applyConservation(currentColour, j);
372         }
373       }
374     }
375     return currentColour;
376   }
377
378   private Color shadeCalculation(AlignmentAnnotation annotation, int j)
379   {
380
381     // calculate a shade
382     float range = 1f;
383     if (thresholdIsMinMax
384             && annotation.threshold != null
385             && aboveAnnotationThreshold == ABOVE_THRESHOLD
386             && annotation.annotations[j].value >= annotation.threshold.value)
387     {
388       range = (annotation.annotations[j].value - annotation.threshold.value)
389               / (annotation.graphMax - annotation.threshold.value);
390     }
391     else if (thresholdIsMinMax && annotation.threshold != null
392             && aboveAnnotationThreshold == BELOW_THRESHOLD
393             && annotation.annotations[j].value >= annotation.graphMin)
394     {
395       range = (annotation.annotations[j].value - annotation.graphMin)
396               / (annotation.threshold.value - annotation.graphMin);
397     }
398     else
399     {
400       if (annotation.graphMax != annotation.graphMin)
401       {
402         range = (annotation.annotations[j].value - annotation.graphMin)
403                 / (annotation.graphMax - annotation.graphMin);
404       }
405       else
406       {
407         range = 0f;
408       }
409     }
410
411     int dr = (int) (rr * range + r1), dg = (int) (gg * range + g1), db = (int) (bb
412             * range + b1);
413
414     return new Color(dr, dg, db);
415
416   }
417   public boolean isPredefinedColours()
418   {
419     return predefinedColours;
420   }
421
422   public void setPredefinedColours(boolean predefinedColours)
423   {
424     this.predefinedColours = predefinedColours;
425   }
426
427   public boolean isSeqAssociated()
428   {
429     return seqAssociated;
430   }
431
432   public void setSeqAssociated(boolean sassoc)
433   {
434     seqAssociated = sassoc;
435   }
436 }