2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.schemes;
23 import jalview.api.FeatureColourI;
24 import jalview.datamodel.SequenceFeature;
25 import jalview.util.ColorUtils;
26 import jalview.util.Format;
28 import java.awt.Color;
29 import java.util.StringTokenizer;
32 * A class that represents a colour scheme for a feature type. Options supported
35 * <li>a simple colour e.g. Red</li>
36 * <li>colour by label - a colour is generated from the feature description</li>
37 * <li>graduated colour by feature score</li>
39 * <li>minimum and maximum score range must be provided</li>
40 * <li>minimum and maximum value colours should be specified</li>
41 * <li>a colour for 'no value' may optionally be provided</li>
42 * <li>colours for intermediate scores are interpolated RGB values</li>
43 * <li>there is an optional threshold above/below which to colour values</li>
44 * <li>the range may be the full value range, or may be limited by the threshold
47 * <li>colour by (text) value of a named attribute</li> <li>graduated colour by
48 * (numeric) value of a named attribute</li> </ul>
50 public class FeatureColour implements FeatureColourI
52 static final Color DEFAULT_NO_COLOUR = Color.LIGHT_GRAY;
54 private static final String BAR = "|";
56 final private Color colour;
58 final private Color minColour;
60 final private Color maxColour;
63 * colour to use for colour by attribute when the
64 * attribute value is absent
66 final private Color noColour;
69 * if true, then colour has a gradient based on a numerical
70 * range (either feature score, or an attribute value)
72 private boolean graduatedColour;
75 * if true, colour values are generated from a text string,
76 * either feature description, or an attribute value
78 private boolean colourByLabel;
81 * if not null, the value of this named attribute is used for
82 * colourByLabel or graduatedColour
84 private String byAttributeName;
86 private float threshold;
92 private boolean belowThreshold;
94 private boolean aboveThreshold;
96 private boolean thresholdIsMinOrMax;
98 private boolean isHighToLow;
100 private boolean autoScaled;
102 final private float minRed;
104 final private float minGreen;
106 final private float minBlue;
108 final private float deltaRed;
110 final private float deltaGreen;
112 final private float deltaBlue;
115 * Parses a Jalview features file format colour descriptor
116 * [label|][mincolour|maxcolour
117 * |[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] Examples:
121 * <li>25,125,213</li>
123 * <li>label|||0.0|0.0|above|12.5</li>
124 * <li>label|||0.0|0.0|below|12.5</li>
125 * <li>red|green|12.0|26.0|none</li>
126 * <li>a28bbb|3eb555|12.0|26.0|above|12.5</li>
127 * <li>a28bbb|3eb555|abso|12.0|26.0|below|12.5</li>
132 * @throws IllegalArgumentException
135 public static FeatureColour parseJalviewFeatureColour(String descriptor)
137 StringTokenizer gcol = new StringTokenizer(descriptor, "|", true);
138 float min = Float.MIN_VALUE;
139 float max = Float.MAX_VALUE;
140 boolean labelColour = false;
142 String mincol = gcol.nextToken();
145 throw new IllegalArgumentException(
146 "Expected either 'label' or a colour specification in the line: "
149 String maxcol = null;
150 if (mincol.toLowerCase().indexOf("label") == 0)
153 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
155 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
158 if (!labelColour && !gcol.hasMoreTokens())
161 * only a simple colour specification - parse it
163 Color colour = ColorUtils.parseColourString(descriptor);
166 throw new IllegalArgumentException(
167 "Invalid colour descriptor: " + descriptor);
169 return new FeatureColour(colour);
173 * autoScaled == true: colours range over actual score range
174 * autoScaled == false ('abso'): colours range over min/max range
176 boolean autoScaled = true;
177 String tok = null, minval, maxval;
180 // at least four more tokens
181 if (mincol.equals("|"))
187 gcol.nextToken(); // skip next '|'
189 maxcol = gcol.nextToken();
190 if (maxcol.equals("|"))
196 gcol.nextToken(); // skip next '|'
198 tok = gcol.nextToken();
199 gcol.nextToken(); // skip next '|'
200 if (tok.toLowerCase().startsWith("abso"))
202 minval = gcol.nextToken();
203 gcol.nextToken(); // skip next '|'
210 maxval = gcol.nextToken();
211 if (gcol.hasMoreTokens())
213 gcol.nextToken(); // skip next '|'
217 if (minval.length() > 0)
219 min = new Float(minval).floatValue();
221 } catch (Exception e)
223 throw new IllegalArgumentException(
224 "Couldn't parse the minimum value for graduated colour ("
229 if (maxval.length() > 0)
231 max = new Float(maxval).floatValue();
233 } catch (Exception e)
235 throw new IllegalArgumentException(
236 "Couldn't parse the maximum value for graduated colour ("
242 // add in some dummy min/max colours for the label-only
249 * construct the FeatureColour
251 FeatureColour featureColour;
254 Color minColour = ColorUtils.parseColourString(mincol);
255 Color maxColour = ColorUtils.parseColourString(maxcol);
256 featureColour = new FeatureColour(minColour, maxColour, min, max);
257 featureColour.setColourByLabel(labelColour);
258 featureColour.setAutoScaled(autoScaled);
259 // add in any additional parameters
260 String ttype = null, tval = null;
261 if (gcol.hasMoreTokens())
263 // threshold type and possibly a threshold value
264 ttype = gcol.nextToken();
265 if (ttype.toLowerCase().startsWith("below"))
267 featureColour.setBelowThreshold(true);
269 else if (ttype.toLowerCase().startsWith("above"))
271 featureColour.setAboveThreshold(true);
275 if (!ttype.toLowerCase().startsWith("no"))
278 "Ignoring unrecognised threshold type : " + ttype);
282 if (featureColour.hasThreshold())
287 tval = gcol.nextToken();
288 featureColour.setThreshold(new Float(tval).floatValue());
289 } catch (Exception e)
291 System.err.println("Couldn't parse threshold value as a float: ("
295 if (gcol.hasMoreTokens())
298 "Ignoring additional tokens in parameters in graduated colour specification\n");
299 while (gcol.hasMoreTokens())
301 System.err.println("|" + gcol.nextToken());
303 System.err.println("\n");
305 return featureColour;
306 } catch (Exception e)
308 throw new IllegalArgumentException(e.getMessage());
313 * Default constructor
315 public FeatureColour()
321 * Constructor given a simple colour
325 public FeatureColour(Color c)
327 minColour = Color.WHITE;
328 maxColour = Color.BLACK;
329 noColour = DEFAULT_NO_COLOUR;
340 * Constructor given a colour range and a score range, defaulting 'no value
341 * colour' to be the same as minimum colour
348 public FeatureColour(Color low, Color high, float min, float max)
350 this(low, high, low, min, max);
358 public FeatureColour(FeatureColour fc)
360 graduatedColour = fc.graduatedColour;
362 minColour = fc.minColour;
363 maxColour = fc.maxColour;
364 noColour = fc.noColour;
366 minGreen = fc.minGreen;
367 minBlue = fc.minBlue;
368 deltaRed = fc.deltaRed;
369 deltaGreen = fc.deltaGreen;
370 deltaBlue = fc.deltaBlue;
373 isHighToLow = fc.isHighToLow;
374 byAttributeName = fc.byAttributeName;
375 setAboveThreshold(fc.isAboveThreshold());
376 setBelowThreshold(fc.isBelowThreshold());
377 setThreshold(fc.getThreshold());
378 setAutoScaled(fc.isAutoScaled());
379 setColourByLabel(fc.isColourByLabel());
383 * Copy constructor with new min/max ranges
389 public FeatureColour(FeatureColour fc, float min, float max)
392 setGraduatedColour(true);
393 updateBounds(min, max);
396 public FeatureColour(Color low, Color high, Color noValueColour,
397 float min, float max)
407 graduatedColour = true;
411 noColour = noValueColour;
412 threshold = Float.NaN;
413 isHighToLow = min >= max;
414 minRed = low.getRed() / 255f;
415 minGreen = low.getGreen() / 255f;
416 minBlue = low.getBlue() / 255f;
417 deltaRed = (high.getRed() / 255f) - minRed;
418 deltaGreen = (high.getGreen() / 255f) - minGreen;
419 deltaBlue = (high.getBlue() / 255f) - minBlue;
433 public boolean isGraduatedColour()
435 return graduatedColour;
439 * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
442 void setGraduatedColour(boolean b)
447 setColourByLabel(false);
452 public Color getColour()
458 public Color getMinColour()
464 public Color getMaxColour()
470 public Color getNoColour()
476 public boolean isColourByLabel()
478 return colourByLabel;
482 * Sets the 'colour by label' flag. If true, also sets 'graduated colour' to
486 public void setColourByLabel(boolean b)
491 setGraduatedColour(false);
496 public boolean isBelowThreshold()
498 return belowThreshold;
502 public void setBelowThreshold(boolean b)
507 setAboveThreshold(false);
512 public boolean isAboveThreshold()
514 return aboveThreshold;
518 public void setAboveThreshold(boolean b)
523 setBelowThreshold(false);
528 public boolean isThresholdMinMax()
530 return thresholdIsMinOrMax;
534 public void setThresholdMinMax(boolean b)
536 thresholdIsMinOrMax = b;
540 public float getThreshold()
546 public void setThreshold(float f)
552 public boolean isAutoScaled()
558 public void setAutoScaled(boolean b)
567 public void updateBounds(float min, float max)
584 * Returns the colour for the given instance of the feature. This may be a
585 * simple colour, a colour generated from the feature description (if
586 * isColourByLabel()), or a colour derived from the feature score (if
587 * isGraduatedColour()).
593 public Color getColor(SequenceFeature feature)
595 if (isColourByLabel())
597 String label = byAttributeName == null ? feature.getDescription()
598 : feature.getValueAsString(byAttributeName);
599 return label == null ? noColour : ColorUtils
600 .createColourFromName(label);
603 if (!isGraduatedColour())
609 * graduated colour case, optionally with threshold
610 * may be based on feature score on an attribute value
611 * Float.NaN is assigned minimum visible score colour
612 * no such attribute is assigned the 'no value' colour
614 float scr = feature.getScore();
615 if (byAttributeName != null)
619 String attVal = feature.getValueAsString(byAttributeName);
620 scr = Float.valueOf(attVal);
621 } catch (Throwable e)
626 if (Float.isNaN(scr))
631 if (isAboveThreshold() && scr <= threshold)
636 if (isBelowThreshold() && scr >= threshold)
642 return getMaxColour();
644 float scl = (scr - base) / range;
657 return new Color(minRed + scl * deltaRed, minGreen + scl * deltaGreen,
658 minBlue + scl * deltaBlue);
662 * Returns the maximum score of the graduated colour range
667 public float getMax()
669 // regenerate the original values passed in to the constructor
670 return (isHighToLow) ? base : (base + range);
674 * Returns the minimum score of the graduated colour range
679 public float getMin()
681 // regenerate the original value passed in to the constructor
682 return (isHighToLow) ? (base + range) : base;
686 public boolean isSimpleColour()
688 return (!isColourByLabel() && !isGraduatedColour());
692 public boolean hasThreshold()
694 return isAboveThreshold() || isBelowThreshold();
698 public String toJalviewFormat(String featureType)
700 String colourString = null;
701 if (isSimpleColour())
703 colourString = Format.getHexString(getColour());
707 StringBuilder sb = new StringBuilder(32);
708 if (isColourByLabel())
713 sb.append(BAR).append(BAR).append(BAR);
716 if (isGraduatedColour())
718 sb.append(Format.getHexString(getMinColour())).append(BAR);
719 sb.append(Format.getHexString(getMaxColour())).append(BAR);
722 sb.append("abso").append(BAR);
725 if (hasThreshold() || isGraduatedColour())
727 sb.append(getMin()).append(BAR);
728 sb.append(getMax()).append(BAR);
729 if (isBelowThreshold())
731 sb.append("below").append(BAR).append(getThreshold());
733 else if (isAboveThreshold())
735 sb.append("above").append(BAR).append(getThreshold());
742 colourString = sb.toString();
744 return String.format("%s\t%s", featureType, colourString);
748 public boolean isColourByAttribute()
750 return byAttributeName != null;
754 public String getAttributeName()
756 return byAttributeName;
760 public void setAttributeName(String name)
762 byAttributeName = name;