+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
package jalview.schemes;
import jalview.api.FeatureColourI;
import jalview.datamodel.SequenceFeature;
+import jalview.util.ColorUtils;
+import jalview.util.Format;
import java.awt.Color;
+import java.util.StringTokenizer;
/**
* A class that wraps either a simple colour or a graduated colour
*/
public class FeatureColour implements FeatureColourI
{
- final private Color colour;
+ private static final float _255F = 255f;
+
+ private static final String ABOVE = "above";
+
+ private static final String BELOW = "below";
+
+ private static final String ABSO = "abso";
+
+ private static final String LABEL = "label";
- final private Color minColour;
+ private static final String PIPE = "|";
- final private Color maxColour;
+ private final Color colour;
+
+ private final Color minColour;
+
+ private final Color maxColour;
private boolean graduatedColour;
private boolean autoScaled;
- final private float minRed;
+ private final float minRed;
- final private float minGreen;
+ private final float minGreen;
- final private float minBlue;
+ private final float minBlue;
- final private float deltaRed;
+ private final float deltaRed;
- final private float deltaGreen;
+ private final float deltaGreen;
- final private float deltaBlue;
+ private final float deltaBlue;
/**
* Default constructor
*/
public FeatureColour(Color c)
{
- minColour = null;
- maxColour = null;
+ minColour = Color.WHITE;
+ maxColour = Color.BLACK;
minRed = 0f;
minGreen = 0f;
minBlue = 0f;
{
graduatedColour = true;
colour = null;
- minColour = low;
- maxColour = high;
+ minColour = low == null ? Color.WHITE : low;
+ maxColour = high == null ? Color.BLACK : high;
threshold = Float.NaN;
isHighToLow = min >= max;
- minRed = low.getRed() / 255f;
- minGreen = low.getGreen() / 255f;
- minBlue = low.getBlue() / 255f;
- deltaRed = (high.getRed() / 255f) - minRed;
- deltaGreen = (high.getGreen() / 255f) - minGreen;
- deltaBlue = (high.getBlue() / 255f) - minBlue;
+ minRed = minColour.getRed() / _255F;
+ minGreen = minColour.getGreen() / _255F;
+ minBlue = minColour.getBlue() / _255F;
+ deltaRed = (maxColour.getRed() / _255F) - minRed;
+ deltaGreen = (maxColour.getGreen() / _255F) - minGreen;
+ deltaBlue = (maxColour.getBlue() / _255F) - minBlue;
if (isHighToLow)
{
base = max;
*/
public FeatureColour(FeatureColour fc)
{
+ graduatedColour = fc.graduatedColour;
colour = fc.colour;
minColour = fc.minColour;
maxColour = fc.maxColour;
setAutoScaled(fc.isAutoScaled());
setColourByLabel(fc.isColourByLabel());
}
-
+
/**
* Copy constructor with new min/max ranges
+ *
* @param fc
* @param min
* @param max
updateBounds(min, max);
}
+ /**
+ * Parses a Jalview features file format colour descriptor
+ * [label|][mincolour|maxcolour
+ * |[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] Examples:
+ * <ul>
+ * <li>red</li>
+ * <li>a28bbb</li>
+ * <li>25,125,213</li>
+ * <li>label</li>
+ * <li>label|||0.0|0.0|above|12.5</li>
+ * <li>label|||0.0|0.0|below|12.5</li>
+ * <li>red|green|12.0|26.0|none</li>
+ * <li>a28bbb|3eb555|12.0|26.0|above|12.5</li>
+ * <li>a28bbb|3eb555|abso|12.0|26.0|below|12.5</li>
+ * </ul>
+ *
+ * @param descriptor
+ * @return
+ * @throws IllegalArgumentException
+ * if not parseable
+ */
+ public static FeatureColour parseJalviewFeatureColour(String descriptor)
+ {
+ StringTokenizer gcol = new StringTokenizer(descriptor, PIPE, true);
+ float min = Float.MIN_VALUE;
+ float max = Float.MAX_VALUE;
+ boolean labelColour = false;
+
+ String mincol = gcol.nextToken();
+ if (mincol == PIPE)
+ {
+ throw new IllegalArgumentException(
+ "Expected either 'label' or a colour specification in the line: "
+ + descriptor);
+ }
+ String maxcol = null;
+ if (mincol.toLowerCase().indexOf(LABEL) == 0)
+ {
+ labelColour = true;
+ mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ // skip '|'
+ mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ }
+
+ if (!labelColour && !gcol.hasMoreTokens())
+ {
+ /*
+ * only a simple colour specification - parse it
+ */
+ Color colour = ColorUtils.parseColourString(descriptor);
+ if (colour == null)
+ {
+ throw new IllegalArgumentException(
+ "Invalid colour descriptor: " + descriptor);
+ }
+ return new FeatureColour(colour);
+ }
+
+ /*
+ * autoScaled == true: colours range over actual score range
+ * autoScaled == false ('abso'): colours range over min/max range
+ */
+ boolean autoScaled = true;
+ String tok = null;
+ String minval;
+ String maxval;
+ if (mincol != null)
+ {
+ // at least four more tokens
+ if (mincol.equals(PIPE))
+ {
+ mincol = "";
+ }
+ else
+ {
+ gcol.nextToken(); // skip next '|'
+ }
+ maxcol = gcol.nextToken();
+ if (maxcol.equals(PIPE))
+ {
+ maxcol = "";
+ }
+ else
+ {
+ gcol.nextToken(); // skip next '|'
+ }
+ tok = gcol.nextToken();
+ gcol.nextToken(); // skip next '|'
+ if (tok.toLowerCase().startsWith(ABSO))
+ {
+ minval = gcol.nextToken();
+ gcol.nextToken(); // skip next '|'
+ autoScaled = false;
+ }
+ else
+ {
+ minval = tok;
+ }
+ maxval = gcol.nextToken();
+ if (gcol.hasMoreTokens())
+ {
+ gcol.nextToken(); // skip next '|'
+ }
+ try
+ {
+ if (minval.length() > 0)
+ {
+ min = new Float(minval).floatValue();
+ }
+ } catch (NumberFormatException e)
+ {
+ throw new IllegalArgumentException(
+ "Couldn't parse the minimum value for graduated colour ("
+ + descriptor + ")");
+ }
+ try
+ {
+ if (maxval.length() > 0)
+ {
+ max = new Float(maxval).floatValue();
+ }
+ } catch (NumberFormatException e)
+ {
+ throw new IllegalArgumentException(
+ "Couldn't parse the maximum value for graduated colour ("
+ + descriptor + ")");
+ }
+ }
+ else
+ {
+ // add in some dummy min/max colours for the label-only
+ // colourscheme.
+ mincol = "FFFFFF";
+ maxcol = "000000";
+ }
+
+ /*
+ * construct the FeatureColour
+ */
+ FeatureColour featureColour;
+ try
+ {
+ Color minColour = ColorUtils.parseColourString(mincol);
+ Color maxColour = ColorUtils.parseColourString(maxcol);
+ featureColour = new FeatureColour(minColour, maxColour, min, max);
+ featureColour.setColourByLabel(labelColour);
+ featureColour.setAutoScaled(autoScaled);
+ // add in any additional parameters
+ String ttype = null;
+ String tval = null;
+ if (gcol.hasMoreTokens())
+ {
+ // threshold type and possibly a threshold value
+ ttype = gcol.nextToken();
+ if (ttype.toLowerCase().startsWith(BELOW))
+ {
+ featureColour.setBelowThreshold(true);
+ }
+ else if (ttype.toLowerCase().startsWith(ABOVE))
+ {
+ featureColour.setAboveThreshold(true);
+ }
+ else
+ {
+ if (!ttype.toLowerCase().startsWith("no"))
+ {
+ System.err.println(
+ "Ignoring unrecognised threshold type : " + ttype);
+ }
+ }
+ }
+ if (featureColour.hasThreshold())
+ {
+ try
+ {
+ gcol.nextToken();
+ tval = gcol.nextToken();
+ featureColour.setThreshold(new Float(tval).floatValue());
+ } catch (NumberFormatException e)
+ {
+ System.err.println("Couldn't parse threshold value as a float: ("
+ + tval + ")");
+ }
+ }
+ if (gcol.hasMoreTokens())
+ {
+ System.err.println(
+ "Ignoring additional tokens in parameters in graduated colour specification\n");
+ while (gcol.hasMoreTokens())
+ {
+ System.err.println(PIPE + gcol.nextToken());
+ }
+ System.err.println("\n");
+ }
+ return featureColour;
+ } catch (Exception e)
+ {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+
@Override
public boolean isGraduatedColour()
{
* Sets the 'graduated colour' flag. If true, also sets 'colour by label' to
* false.
*/
- @Override
- public void setGraduatedColour(boolean b)
+ void setGraduatedColour(boolean b)
{
graduatedColour = b;
if (b)
setGraduatedColour(false);
}
}
+
@Override
public boolean isBelowThreshold()
{
}
/**
- * Returns the colour for the given instance of the feature. This may be a
- * simple colour, a colour generated from the feature description (if
- * isColourByLabel()), or a colour derived from the feature score (if
- * isGraduatedColour()).
+ * {@inheritDoc}
*
* @param feature
* @return
*/
@Override
- public Color getColor(SequenceFeature feature)
+ public Color getColour(SequenceFeature feature)
{
- if (isColourByLabel())
- {
- return UserColourScheme
- .createColourFromName(feature.getDescription());
- }
-
- if (!isGraduatedColour())
+ if (isSimpleColour())
{
return getColour();
}
- // todo should we check for above/below threshold here?
- if (range == 0.0)
+ /*
+ * return null if score is outwith any threshold
+ * (for graduated colour or colour by label)
+ */
+ float scr = feature.getScore();
+ boolean isNan = Float.isNaN(scr);
+ if (!isNan)
{
- return getMaxColour();
+ boolean isAbove = isAboveThreshold() && scr <= threshold;
+ boolean isBelow = !isNan && isBelowThreshold() && scr >= threshold;
+ if (isAbove || isBelow)
+ {
+ return null;
+ }
}
- float scr = feature.getScore();
- if (Float.isNaN(scr))
+
+ if (isColourByLabel())
{
- return getMinColour();
+ return ColorUtils.createColourFromName(feature.getDescription());
}
- float scl = (scr - base) / range;
- if (isHighToLow)
+
+ Color result = null;
+
+ if (isNan)
{
- scl = -scl;
+ result = getMinColour();
}
- if (scl < 0f)
+ else if (range == 0.0)
{
- scl = 0f;
+ result = getMaxColour();
}
- if (scl > 1f)
+ else
{
- scl = 1f;
+ float scl = (scr - base) / range;
+ if (isHighToLow)
+ {
+ scl = -scl;
+ }
+ if (scl < 0f)
+ {
+ scl = 0f;
+ }
+ if (scl > 1f)
+ {
+ scl = 1f;
+ }
+ result = new Color(minRed + scl * deltaRed,
+ minGreen + scl * deltaGreen, minBlue + scl * deltaBlue);
}
- return new Color(minRed + scl * deltaRed, minGreen + scl * deltaGreen, minBlue + scl * deltaBlue);
+
+ return result;
}
/**
return (isHighToLow) ? (base + range) : base;
}
- /**
- * Answers true if the feature has a simple colour, or is coloured by label,
- * or has a graduated colour and the score of this feature instance is within
- * the range to render (if any), i.e. does not lie below or above any
- * threshold set.
- *
- * @param feature
- * @return
- */
- @Override
- public boolean isColored(SequenceFeature feature)
- {
- if (isColourByLabel() || !isGraduatedColour())
- {
- return true;
- }
-
- float val = feature.getScore();
- if (Float.isNaN(val))
- {
- return true;
- }
- if (Float.isNaN(this.threshold))
- {
- return true;
- }
-
- if (isAboveThreshold() && val <= threshold)
- {
- return false;
- }
- if (isBelowThreshold() && val >= threshold)
- {
- return false;
- }
- return true;
- }
-
@Override
public boolean isSimpleColour()
{
return isAboveThreshold() || isBelowThreshold();
}
+ @Override
+ public String toJalviewFormat(String featureType)
+ {
+ String colourString = null;
+ if (isSimpleColour())
+ {
+ colourString = Format.getHexString(getColour());
+ }
+ else
+ {
+ StringBuilder sb = new StringBuilder(32);
+ if (isColourByLabel())
+ {
+ sb.append(LABEL);
+ if (hasThreshold())
+ {
+ sb.append(PIPE).append(PIPE).append(PIPE);
+ }
+ }
+ if (isGraduatedColour())
+ {
+ sb.append(Format.getHexString(getMinColour())).append(PIPE);
+ sb.append(Format.getHexString(getMaxColour())).append(PIPE);
+ if (!isAutoScaled())
+ {
+ sb.append(ABSO).append(PIPE);
+ }
+ }
+ if (hasThreshold() || isGraduatedColour())
+ {
+ sb.append(getMin()).append(PIPE);
+ sb.append(getMax()).append(PIPE);
+ if (isBelowThreshold())
+ {
+ sb.append(BELOW).append(PIPE).append(getThreshold());
+ }
+ else if (isAboveThreshold())
+ {
+ sb.append(ABOVE).append(PIPE).append(getThreshold());
+ }
+ else
+ {
+ sb.append("none");
+ }
+ }
+ colourString = sb.toString();
+ }
+ return String.format("%s\t%s", featureType, colourString);
+ }
+
}