1 package jalview.schemes;
3 import jalview.api.FeatureColourI;
4 import jalview.datamodel.SequenceFeature;
5 import jalview.util.Format;
8 import java.util.StringTokenizer;
11 * A class that wraps either a simple colour or a graduated colour
13 public class FeatureColour implements FeatureColourI
15 private static final String BAR = "|";
17 final private Color colour;
19 final private Color minColour;
21 final private Color maxColour;
23 private boolean graduatedColour;
25 private boolean colourByLabel;
27 private float threshold;
33 private boolean belowThreshold;
35 private boolean aboveThreshold;
37 private boolean thresholdIsMinOrMax;
39 private boolean isHighToLow;
41 private boolean autoScaled;
43 final private float minRed;
45 final private float minGreen;
47 final private float minBlue;
49 final private float deltaRed;
51 final private float deltaGreen;
53 final private float deltaBlue;
56 * Parses a Jalview features file format colour descriptor
57 * [label|][mincolour|maxcolour
58 * |[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] Examples:
64 * <li>label|||0.0|0.0|above|12.5</li>
65 * <li>label|||0.0|0.0|below|12.5</li>
66 * <li>red|green|12.0|26.0|none</li>
67 * <li>a28bbb|3eb555|12.0|26.0|above|12.5</li>
68 * <li>a28bbb|3eb555|abso|12.0|26.0|below|12.5</li>
73 * @throws IllegalArgumentException
76 public static FeatureColour parseJalviewFeatureColour(String descriptor)
78 StringTokenizer gcol = new StringTokenizer(descriptor, "|", true);
79 float min = Float.MIN_VALUE;
80 float max = Float.MAX_VALUE;
81 boolean labelColour = false;
83 String mincol = gcol.nextToken();
86 throw new IllegalArgumentException(
87 "Expected either 'label' or a colour specification in the line: "
91 if (mincol.toLowerCase().indexOf("label") == 0)
94 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
96 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
99 if (!labelColour && !gcol.hasMoreTokens())
102 * only a simple colour specification - parse it
104 Color colour = UserColourScheme.getColourFromString(descriptor);
107 throw new IllegalArgumentException("Invalid colour descriptor: "
110 return new FeatureColour(colour);
114 * autoScaled == true: colours range over actual score range
115 * autoScaled == false ('abso'): colours range over min/max range
117 boolean autoScaled = true;
118 String tok = null, minval, maxval;
121 // at least four more tokens
122 if (mincol.equals("|"))
128 gcol.nextToken(); // skip next '|'
130 maxcol = gcol.nextToken();
131 if (maxcol.equals("|"))
137 gcol.nextToken(); // skip next '|'
139 tok = gcol.nextToken();
140 gcol.nextToken(); // skip next '|'
141 if (tok.toLowerCase().startsWith("abso"))
143 minval = gcol.nextToken();
144 gcol.nextToken(); // skip next '|'
151 maxval = gcol.nextToken();
152 if (gcol.hasMoreTokens())
154 gcol.nextToken(); // skip next '|'
158 if (minval.length() > 0)
160 min = new Float(minval).floatValue();
162 } catch (Exception e)
164 throw new IllegalArgumentException(
165 "Couldn't parse the minimum value for graduated colour ("
170 if (maxval.length() > 0)
172 max = new Float(maxval).floatValue();
174 } catch (Exception e)
176 throw new IllegalArgumentException(
177 "Couldn't parse the maximum value for graduated colour ("
183 // add in some dummy min/max colours for the label-only
190 * construct the FeatureColour
192 FeatureColour featureColour;
195 featureColour = new FeatureColour(
196 new UserColourScheme(mincol).findColour('A'),
197 new UserColourScheme(maxcol).findColour('A'), min, max);
198 featureColour.setColourByLabel(labelColour);
199 featureColour.setAutoScaled(autoScaled);
200 // add in any additional parameters
201 String ttype = null, tval = null;
202 if (gcol.hasMoreTokens())
204 // threshold type and possibly a threshold value
205 ttype = gcol.nextToken();
206 if (ttype.toLowerCase().startsWith("below"))
208 featureColour.setBelowThreshold(true);
210 else if (ttype.toLowerCase().startsWith("above"))
212 featureColour.setAboveThreshold(true);
216 if (!ttype.toLowerCase().startsWith("no"))
218 System.err.println("Ignoring unrecognised threshold type : "
223 if (featureColour.hasThreshold())
228 tval = gcol.nextToken();
229 featureColour.setThreshold(new Float(tval).floatValue());
230 } catch (Exception e)
232 System.err.println("Couldn't parse threshold value as a float: ("
236 if (gcol.hasMoreTokens())
239 .println("Ignoring additional tokens in parameters in graduated colour specification\n");
240 while (gcol.hasMoreTokens())
242 System.err.println("|" + gcol.nextToken());
244 System.err.println("\n");
246 return featureColour;
247 } catch (Exception e)
249 throw new IllegalArgumentException(e.getMessage());
254 * Default constructor
256 public FeatureColour()
262 * Constructor given a simple colour
266 public FeatureColour(Color c)
268 minColour = Color.WHITE;
269 maxColour = Color.BLACK;
280 * Constructor given a colour range and a score range
287 public FeatureColour(Color low, Color high, float min, float max)
289 graduatedColour = true;
293 threshold = Float.NaN;
294 isHighToLow = min >= max;
295 minRed = low.getRed() / 255f;
296 minGreen = low.getGreen() / 255f;
297 minBlue = low.getBlue() / 255f;
298 deltaRed = (high.getRed() / 255f) - minRed;
299 deltaGreen = (high.getGreen() / 255f) - minGreen;
300 deltaBlue = (high.getBlue() / 255f) - minBlue;
318 public FeatureColour(FeatureColour fc)
320 graduatedColour = fc.graduatedColour;
322 minColour = fc.minColour;
323 maxColour = fc.maxColour;
325 minGreen = fc.minGreen;
326 minBlue = fc.minBlue;
327 deltaRed = fc.deltaRed;
328 deltaGreen = fc.deltaGreen;
329 deltaBlue = fc.deltaBlue;
332 isHighToLow = fc.isHighToLow;
333 setAboveThreshold(fc.isAboveThreshold());
334 setBelowThreshold(fc.isBelowThreshold());
335 setThreshold(fc.getThreshold());
336 setAutoScaled(fc.isAutoScaled());
337 setColourByLabel(fc.isColourByLabel());
341 * Copy constructor with new min/max ranges
347 public FeatureColour(FeatureColour fc, float min, float max)
350 graduatedColour = true;
351 updateBounds(min, max);
355 public boolean isGraduatedColour()
357 return graduatedColour;
361 * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
364 void setGraduatedColour(boolean b)
369 setColourByLabel(false);
374 public Color getColour()
380 public Color getMinColour()
386 public Color getMaxColour()
392 public boolean isColourByLabel()
394 return colourByLabel;
398 * Sets the 'colour by label' flag. If true, also sets 'graduated colour' to
402 public void setColourByLabel(boolean b)
407 setGraduatedColour(false);
412 public boolean isBelowThreshold()
414 return belowThreshold;
418 public void setBelowThreshold(boolean b)
423 setAboveThreshold(false);
428 public boolean isAboveThreshold()
430 return aboveThreshold;
434 public void setAboveThreshold(boolean b)
439 setBelowThreshold(false);
444 public boolean isThresholdMinMax()
446 return thresholdIsMinOrMax;
450 public void setThresholdMinMax(boolean b)
452 thresholdIsMinOrMax = b;
456 public float getThreshold()
462 public void setThreshold(float f)
468 public boolean isAutoScaled()
474 public void setAutoScaled(boolean b)
480 * Updates the base and range appropriately for the given minmax range
486 public void updateBounds(float min, float max)
503 * Returns the colour for the given instance of the feature. This may be a
504 * simple colour, a colour generated from the feature description (if
505 * isColourByLabel()), or a colour derived from the feature score (if
506 * isGraduatedColour()).
512 public Color getColor(SequenceFeature feature)
514 if (isColourByLabel())
516 return UserColourScheme
517 .createColourFromName(feature.getDescription());
520 if (!isGraduatedColour())
525 // todo should we check for above/below threshold here?
528 return getMaxColour();
530 float scr = feature.getScore();
531 if (Float.isNaN(scr))
533 return getMinColour();
535 float scl = (scr - base) / range;
548 return new Color(minRed + scl * deltaRed, minGreen + scl * deltaGreen,
549 minBlue + scl * deltaBlue);
553 * Returns the maximum score of the graduated colour range
558 public float getMax()
560 // regenerate the original values passed in to the constructor
561 return (isHighToLow) ? base : (base + range);
565 * Returns the minimum score of the graduated colour range
570 public float getMin()
572 // regenerate the original value passed in to the constructor
573 return (isHighToLow) ? (base + range) : base;
577 * Answers true if the feature has a simple colour, or is coloured by label,
578 * or has a graduated colour and the score of this feature instance is within
579 * the range to render (if any), i.e. does not lie below or above any
586 public boolean isColored(SequenceFeature feature)
588 if (isColourByLabel() || !isGraduatedColour())
593 float val = feature.getScore();
594 if (Float.isNaN(val))
598 if (Float.isNaN(this.threshold))
603 if (isAboveThreshold() && val <= threshold)
607 if (isBelowThreshold() && val >= threshold)
615 public boolean isSimpleColour()
617 return (!isColourByLabel() && !isGraduatedColour());
621 public boolean hasThreshold()
623 return isAboveThreshold() || isBelowThreshold();
627 public String toJalviewFormat(String featureType)
629 String colourString = null;
630 if (isSimpleColour())
632 colourString = Format.getHexString(getColour());
636 StringBuilder sb = new StringBuilder(32);
637 if (isColourByLabel())
642 sb.append(BAR).append(BAR).append(BAR);
645 if (isGraduatedColour())
647 sb.append(Format.getHexString(getMinColour())).append(BAR);
648 sb.append(Format.getHexString(getMaxColour())).append(BAR);
651 sb.append("abso").append(BAR);
654 if (hasThreshold() || isGraduatedColour())
656 sb.append(getMin()).append(BAR);
657 sb.append(getMax()).append(BAR);
658 if (isBelowThreshold())
660 sb.append("below").append(BAR).append(getThreshold());
662 else if (isAboveThreshold())
664 sb.append("above").append(BAR).append(getThreshold());
671 colourString = sb.toString();
673 return String.format("%s\t%s", featureType, colourString);