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