Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.schemes

File AnnotationColourGradient.java

 

Coverage histogram

../../img/srcFileCovDistChart8.png
20% of files have more coverage

Code metrics

78
139
23
1
558
391
94
0.68
6.04
23
4.09

Classes

Class Line # Actions
AnnotationColourGradient 38 139 94
0.7333333573.3%
 

Contributing tests

This file is covered by 9 tests. .

Source view

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