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;
27 import jalview.util.matcher.KeyedMatcherI;
29 import java.awt.Color;
30 import java.util.StringTokenizer;
31 import java.util.function.Function;
34 * A class that wraps either a simple colour or a graduated colour
36 public class FeatureColour implements FeatureColourI
38 private static final String BAR = "|";
40 final private Color colour;
42 final private Color minColour;
44 final private Color maxColour;
46 private boolean graduatedColour;
48 private boolean colourByLabel;
50 private float threshold;
56 private boolean belowThreshold;
58 private boolean aboveThreshold;
60 private boolean thresholdIsMinOrMax;
62 private boolean isHighToLow;
64 private boolean autoScaled;
66 final private float minRed;
68 final private float minGreen;
70 final private float minBlue;
72 final private float deltaRed;
74 final private float deltaGreen;
76 final private float deltaBlue;
79 * optional filter by attribute values
81 private KeyedMatcherI attributeFilters;
84 * Parses a Jalview features file format colour descriptor
85 * [label|][mincolour|maxcolour
86 * |[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] Examples:
92 * <li>label|||0.0|0.0|above|12.5</li>
93 * <li>label|||0.0|0.0|below|12.5</li>
94 * <li>red|green|12.0|26.0|none</li>
95 * <li>a28bbb|3eb555|12.0|26.0|above|12.5</li>
96 * <li>a28bbb|3eb555|abso|12.0|26.0|below|12.5</li>
101 * @throws IllegalArgumentException
104 public static FeatureColour parseJalviewFeatureColour(String descriptor)
106 StringTokenizer gcol = new StringTokenizer(descriptor, "|", true);
107 float min = Float.MIN_VALUE;
108 float max = Float.MAX_VALUE;
109 boolean labelColour = false;
111 String mincol = gcol.nextToken();
114 throw new IllegalArgumentException(
115 "Expected either 'label' or a colour specification in the line: "
118 String maxcol = null;
119 if (mincol.toLowerCase().indexOf("label") == 0)
122 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
124 mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
127 if (!labelColour && !gcol.hasMoreTokens())
130 * only a simple colour specification - parse it
132 Color colour = ColorUtils.parseColourString(descriptor);
135 throw new IllegalArgumentException(
136 "Invalid colour descriptor: " + descriptor);
138 return new FeatureColour(colour);
142 * autoScaled == true: colours range over actual score range
143 * autoScaled == false ('abso'): colours range over min/max range
145 boolean autoScaled = true;
146 String tok = null, minval, maxval;
149 // at least four more tokens
150 if (mincol.equals("|"))
156 gcol.nextToken(); // skip next '|'
158 maxcol = gcol.nextToken();
159 if (maxcol.equals("|"))
165 gcol.nextToken(); // skip next '|'
167 tok = gcol.nextToken();
168 gcol.nextToken(); // skip next '|'
169 if (tok.toLowerCase().startsWith("abso"))
171 minval = gcol.nextToken();
172 gcol.nextToken(); // skip next '|'
179 maxval = gcol.nextToken();
180 if (gcol.hasMoreTokens())
182 gcol.nextToken(); // skip next '|'
186 if (minval.length() > 0)
188 min = new Float(minval).floatValue();
190 } catch (Exception e)
192 throw new IllegalArgumentException(
193 "Couldn't parse the minimum value for graduated colour ("
198 if (maxval.length() > 0)
200 max = new Float(maxval).floatValue();
202 } catch (Exception e)
204 throw new IllegalArgumentException(
205 "Couldn't parse the maximum value for graduated colour ("
211 // add in some dummy min/max colours for the label-only
218 * construct the FeatureColour
220 FeatureColour featureColour;
223 Color minColour = ColorUtils.parseColourString(mincol);
224 Color maxColour = ColorUtils.parseColourString(maxcol);
225 featureColour = new FeatureColour(minColour, maxColour, min, max);
226 featureColour.setColourByLabel(labelColour);
227 featureColour.setAutoScaled(autoScaled);
228 // add in any additional parameters
229 String ttype = null, tval = null;
230 if (gcol.hasMoreTokens())
232 // threshold type and possibly a threshold value
233 ttype = gcol.nextToken();
234 if (ttype.toLowerCase().startsWith("below"))
236 featureColour.setBelowThreshold(true);
238 else if (ttype.toLowerCase().startsWith("above"))
240 featureColour.setAboveThreshold(true);
244 if (!ttype.toLowerCase().startsWith("no"))
247 "Ignoring unrecognised threshold type : " + ttype);
251 if (featureColour.hasThreshold())
256 tval = gcol.nextToken();
257 featureColour.setThreshold(new Float(tval).floatValue());
258 } catch (Exception e)
260 System.err.println("Couldn't parse threshold value as a float: ("
264 if (gcol.hasMoreTokens())
267 "Ignoring additional tokens in parameters in graduated colour specification\n");
268 while (gcol.hasMoreTokens())
270 System.err.println("|" + gcol.nextToken());
272 System.err.println("\n");
274 return featureColour;
275 } catch (Exception e)
277 throw new IllegalArgumentException(e.getMessage());
282 * Default constructor
284 public FeatureColour()
290 * Constructor given a simple colour
294 public FeatureColour(Color c)
296 minColour = Color.WHITE;
297 maxColour = Color.BLACK;
308 * Constructor given a colour range and a score range
315 public FeatureColour(Color low, Color high, float min, float max)
325 graduatedColour = true;
329 threshold = Float.NaN;
330 isHighToLow = min >= max;
331 minRed = low.getRed() / 255f;
332 minGreen = low.getGreen() / 255f;
333 minBlue = low.getBlue() / 255f;
334 deltaRed = (high.getRed() / 255f) - minRed;
335 deltaGreen = (high.getGreen() / 255f) - minGreen;
336 deltaBlue = (high.getBlue() / 255f) - minBlue;
354 public FeatureColour(FeatureColour fc)
356 graduatedColour = fc.graduatedColour;
358 minColour = fc.minColour;
359 maxColour = fc.maxColour;
361 minGreen = fc.minGreen;
362 minBlue = fc.minBlue;
363 deltaRed = fc.deltaRed;
364 deltaGreen = fc.deltaGreen;
365 deltaBlue = fc.deltaBlue;
368 isHighToLow = fc.isHighToLow;
369 attributeFilters = fc.attributeFilters;
370 setAboveThreshold(fc.isAboveThreshold());
371 setBelowThreshold(fc.isBelowThreshold());
372 setThreshold(fc.getThreshold());
373 setAutoScaled(fc.isAutoScaled());
374 setColourByLabel(fc.isColourByLabel());
378 * Copy constructor with new min/max ranges
384 public FeatureColour(FeatureColour fc, float min, float max)
387 graduatedColour = true;
388 updateBounds(min, max);
392 public boolean isGraduatedColour()
394 return graduatedColour;
398 * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
401 void setGraduatedColour(boolean b)
406 setColourByLabel(false);
411 public Color getColour()
417 public Color getMinColour()
423 public Color getMaxColour()
429 public boolean isColourByLabel()
431 return colourByLabel;
435 * Sets the 'colour by label' flag. If true, also sets 'graduated colour' to
439 public void setColourByLabel(boolean b)
444 setGraduatedColour(false);
449 public boolean isBelowThreshold()
451 return belowThreshold;
455 public void setBelowThreshold(boolean b)
460 setAboveThreshold(false);
465 public boolean isAboveThreshold()
467 return aboveThreshold;
471 public void setAboveThreshold(boolean b)
476 setBelowThreshold(false);
481 public boolean isThresholdMinMax()
483 return thresholdIsMinOrMax;
487 public void setThresholdMinMax(boolean b)
489 thresholdIsMinOrMax = b;
493 public float getThreshold()
499 public void setThreshold(float f)
505 public boolean isAutoScaled()
511 public void setAutoScaled(boolean b)
517 * Updates the base and range appropriately for the given minmax range
523 public void updateBounds(float min, float max)
540 * Returns the colour for the given instance of the feature. This may be a
541 * simple colour, a colour generated from the feature description (if
542 * isColourByLabel()), or a colour derived from the feature score (if
543 * isGraduatedColour()).
549 public Color getColor(SequenceFeature feature)
551 if (!matchesFilters(feature))
556 if (isColourByLabel())
558 return ColorUtils.createColourFromName(feature.getDescription());
561 if (!isGraduatedColour())
567 * graduated colour case, optionally with threshold
568 * Float.NaN is assigned minimum visible score colour
570 float scr = feature.getScore();
571 if (Float.isNaN(scr))
573 return getMinColour();
575 if (isAboveThreshold() && scr <= threshold)
579 if (isBelowThreshold() && scr >= threshold)
585 return getMaxColour();
587 float scl = (scr - base) / range;
600 return new Color(minRed + scl * deltaRed, minGreen + scl * deltaGreen,
601 minBlue + scl * deltaBlue);
605 * Answers true if there are any attribute value filters defined, and the
606 * feature matches all of the filter conditions
612 boolean matchesFilters(SequenceFeature feature)
614 Function<String, String> valueProvider = key -> feature.otherDetails == null ? null
615 : (feature.otherDetails.containsKey(key) ? feature.otherDetails
616 .get(key).toString() : null);
617 return attributeFilters == null ? true : attributeFilters
618 .matches(valueProvider);
622 * Returns the maximum score of the graduated colour range
627 public float getMax()
629 // regenerate the original values passed in to the constructor
630 return (isHighToLow) ? base : (base + range);
634 * Returns the minimum score of the graduated colour range
639 public float getMin()
641 // regenerate the original value passed in to the constructor
642 return (isHighToLow) ? (base + range) : base;
646 public boolean isSimpleColour()
648 return (!isColourByLabel() && !isGraduatedColour());
652 public boolean hasThreshold()
654 return isAboveThreshold() || isBelowThreshold();
658 public String toJalviewFormat(String featureType)
660 String colourString = null;
661 if (isSimpleColour())
663 colourString = Format.getHexString(getColour());
667 StringBuilder sb = new StringBuilder(32);
668 if (isColourByLabel())
673 sb.append(BAR).append(BAR).append(BAR);
676 if (isGraduatedColour())
678 sb.append(Format.getHexString(getMinColour())).append(BAR);
679 sb.append(Format.getHexString(getMaxColour())).append(BAR);
682 sb.append("abso").append(BAR);
685 if (hasThreshold() || isGraduatedColour())
687 sb.append(getMin()).append(BAR);
688 sb.append(getMax()).append(BAR);
689 if (isBelowThreshold())
691 sb.append("below").append(BAR).append(getThreshold());
693 else if (isAboveThreshold())
695 sb.append("above").append(BAR).append(getThreshold());
702 colourString = sb.toString();
704 return String.format("%s\t%s", featureType, colourString);
708 * Adds an attribute filter
714 public void setAttributeFilters(KeyedMatcherI matcher)
716 attributeFilters = matcher;
720 public KeyedMatcherI getAttributeFilters()
722 return attributeFilters;