import jalview.api.FeatureColourI;
import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.features.FeatureMatcher;
import jalview.util.ColorUtils;
import jalview.util.Format;
*/
public class FeatureColour implements FeatureColourI
{
- static final Color DEFAULT_NO_COLOUR = Color.LIGHT_GRAY;
+ private static final String ABSOLUTE = "abso";
+
+ private static final String ABOVE = "above";
+
+ private static final String BELOW = "below";
+
+ /*
+ * constants used to read or write a Jalview Features file
+ */
+ private static final String LABEL = "label";
+
+ private static final String SCORE = "score";
+
+ private static final String ATTRIBUTE = "attribute";
+
+ private static final String NO_VALUE_MIN = "noValueMin";
+
+ private static final String NO_VALUE_MAX = "noValueMax";
+
+ private static final String NO_VALUE_NONE = "noValueNone";
+
+ static final Color DEFAULT_NO_COLOUR = null;
private static final String BAR = "|";
private boolean colourByLabel;
/*
- * if not null, the value of this named attribute is used for
- * colourByLabel or graduatedColour
+ * if not null, the value of [attribute, [sub-attribute] ...]
+ * is used for colourByLabel or graduatedColour
*/
- private String byAttributeName;
+ private String[] attributeName;
private float threshold;
private boolean aboveThreshold;
- private boolean thresholdIsMinOrMax;
-
private boolean isHighToLow;
private boolean autoScaled;
/**
* Parses a Jalview features file format colour descriptor
- * [label|][mincolour|maxcolour
- * |[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] Examples:
+ * <p>
+ * <code>
+ * [label|score|[attribute|attributeName]|][mincolour|maxcolour|
+ * [absolute|]minvalue|maxvalue|[noValueOption|]thresholdtype|thresholdvalue]</code>
+ * <p>
+ * 'Score' is optional (default) for a graduated colour. An attribute with
+ * sub-attribute should be written as (for example) CSQ:Consequence.
+ * noValueOption is one of <code>noValueMin, noValueMax, noValueNone</code>
+ * with default noValueMin.
+ * <p>
+ * Examples:
* <ul>
* <li>red</li>
* <li>a28bbb</li>
* <li>25,125,213</li>
* <li>label</li>
+ * <li>attribute|CSQ:PolyPhen</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>score|red|green|12.0|26.0|none</li>
+ * <li>attribute|AF|red|green|12.0|26.0|none</li>
+ * <li>attribute|AF|red|green|noValueNone|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>
* @throws IllegalArgumentException
* if not parseable
*/
- public static FeatureColour parseJalviewFeatureColour(String descriptor)
+ public static FeatureColourI parseJalviewFeatureColour(String descriptor)
{
- StringTokenizer gcol = new StringTokenizer(descriptor, "|", true);
+ StringTokenizer gcol = new StringTokenizer(descriptor, BAR, true);
float min = Float.MIN_VALUE;
float max = Float.MAX_VALUE;
- boolean labelColour = false;
+ boolean byLabel = false;
+ boolean byAttribute = false;
+ String attName = null;
+ String mincol = null;
+ String maxcol = null;
- String mincol = gcol.nextToken();
- if (mincol == "|")
+ /*
+ * first token should be 'label', or 'score', or an
+ * attribute name, or simple colour, or minimum colour
+ */
+ String nextToken = gcol.nextToken();
+ if (nextToken == BAR)
{
throw new IllegalArgumentException(
"Expected either 'label' or a colour specification in the line: "
+ descriptor);
}
- String maxcol = null;
- if (mincol.toLowerCase().indexOf("label") == 0)
+ if (nextToken.toLowerCase().startsWith(LABEL))
{
- labelColour = true;
+ byLabel = true;
+ // get the token after the next delimiter:
mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
- // skip '|'
mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
}
+ else if (nextToken.toLowerCase().startsWith(SCORE))
+ {
+ mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ }
+ else if (nextToken.toLowerCase().startsWith(ATTRIBUTE))
+ {
+ byAttribute = true;
+ attName = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ attName = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null);
+ }
+ else
+ {
+ mincol = nextToken;
+ }
- if (!labelColour && !gcol.hasMoreTokens())
+ /*
+ * if only one token, it can validly be label, attributeName,
+ * or a plain colour value
+ */
+ if (!gcol.hasMoreTokens())
{
- /*
- * only a simple colour specification - parse it
- */
+ if (byLabel || byAttribute)
+ {
+ FeatureColourI fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ if (byAttribute)
+ {
+ fc.setAttributeName(
+ FeatureMatcher.fromAttributeDisplayName(attName));
+ }
+ return fc;
+ }
+
Color colour = ColorUtils.parseColourString(descriptor);
if (colour == null)
{
}
/*
+ * continue parsing for min/max/no colour (if graduated)
+ * and for threshold (colour by text or graduated)
+ */
+
+ /*
* autoScaled == true: colours range over actual score range
* autoScaled == false ('abso'): colours range over min/max range
*/
boolean autoScaled = true;
String tok = null, minval, maxval;
+ String noValueColour = NO_VALUE_MIN;
+
if (mincol != null)
{
// at least four more tokens
- if (mincol.equals("|"))
+ if (mincol.equals(BAR))
{
- mincol = "";
+ mincol = null;
}
else
{
gcol.nextToken(); // skip next '|'
}
maxcol = gcol.nextToken();
- if (maxcol.equals("|"))
+ if (maxcol.equals(BAR))
{
- maxcol = "";
+ maxcol = null;
}
else
{
gcol.nextToken(); // skip next '|'
}
tok = gcol.nextToken();
+
+ /*
+ * check for specifier for colour for no attribute value
+ * (new in 2.11, defaults to minColour if not specified)
+ */
+ if (tok.equalsIgnoreCase(NO_VALUE_MIN))
+ {
+ tok = gcol.nextToken();
+ tok = gcol.nextToken();
+ }
+ else if (tok.equalsIgnoreCase(NO_VALUE_MAX))
+ {
+ noValueColour = NO_VALUE_MAX;
+ tok = gcol.nextToken();
+ tok = gcol.nextToken();
+ }
+ else if (tok.equalsIgnoreCase(NO_VALUE_NONE))
+ {
+ noValueColour = NO_VALUE_NONE;
+ tok = gcol.nextToken();
+ tok = gcol.nextToken();
+ }
+
gcol.nextToken(); // skip next '|'
- if (tok.toLowerCase().startsWith("abso"))
+ if (tok.toLowerCase().startsWith(ABSOLUTE))
{
minval = gcol.nextToken();
gcol.nextToken(); // skip next '|'
}
else
{
- // add in some dummy min/max colours for the label-only
- // colourscheme.
- mincol = "FFFFFF";
- maxcol = "000000";
+ /*
+ * dummy min/max colours for colour by text
+ * (label or attribute value)
+ */
+ mincol = "white";
+ maxcol = "black";
+ byLabel = true;
}
/*
- * construct the FeatureColour
+ * 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);
+ Color noColour = noValueColour.equals(NO_VALUE_MAX) ? maxColour
+ : (noValueColour.equals(NO_VALUE_NONE) ? null : minColour);
+ featureColour = new FeatureColour(minColour, maxColour, noColour, min,
+ max);
+ featureColour.setColourByLabel(minColour == null);
featureColour.setAutoScaled(autoScaled);
+ if (byAttribute)
+ {
+ featureColour.setAttributeName(
+ FeatureMatcher.fromAttributeDisplayName(attName));
+ }
// add in any additional parameters
String ttype = null, tval = null;
if (gcol.hasMoreTokens())
{
// threshold type and possibly a threshold value
ttype = gcol.nextToken();
- if (ttype.toLowerCase().startsWith("below"))
+ if (ttype.toLowerCase().startsWith(BELOW))
{
featureColour.setBelowThreshold(true);
}
- else if (ttype.toLowerCase().startsWith("above"))
+ else if (ttype.toLowerCase().startsWith(ABOVE))
{
featureColour.setAboveThreshold(true);
}
"Ignoring additional tokens in parameters in graduated colour specification\n");
while (gcol.hasMoreTokens())
{
- System.err.println("|" + gcol.nextToken());
+ System.err.println(BAR + gcol.nextToken());
}
System.err.println("\n");
}
base = fc.base;
range = fc.range;
isHighToLow = fc.isHighToLow;
- byAttributeName = fc.byAttributeName;
+ attributeName = fc.attributeName;
setAboveThreshold(fc.isAboveThreshold());
setBelowThreshold(fc.isBelowThreshold());
setThreshold(fc.getThreshold());
updateBounds(min, max);
}
+ /**
+ * Constructor for a graduated colour
+ *
+ * @param low
+ * @param high
+ * @param noValueColour
+ * @param min
+ * @param max
+ */
public FeatureColour(Color low, Color high, Color noValueColour,
float min, float max)
{
}
@Override
- public boolean isThresholdMinMax()
- {
- return thresholdIsMinOrMax;
- }
-
- @Override
- public void setThresholdMinMax(boolean b)
- {
- thresholdIsMinOrMax = b;
- }
-
- @Override
public float getThreshold()
{
return threshold;
{
if (isColourByLabel())
{
- String label = byAttributeName == null ? feature.getDescription()
- : feature.getValueAsString(byAttributeName);
+ String label = attributeName == null ? feature.getDescription()
+ : feature.getValueAsString(attributeName);
return label == null ? noColour : ColorUtils
.createColourFromName(label);
}
/*
* graduated colour case, optionally with threshold
* may be based on feature score on an attribute value
- * Float.NaN is assigned minimum visible score colour
- * no such attribute is assigned the 'no value' colour
+ * Float.NaN, or no value, is assigned the 'no value' colour
*/
float scr = feature.getScore();
- if (byAttributeName != null)
+ if (attributeName != null)
{
try
{
- String attVal = feature.getValueAsString(byAttributeName);
+ String attVal = feature.getValueAsString(attributeName);
scr = Float.valueOf(attVal);
} catch (Throwable e)
{
else
{
StringBuilder sb = new StringBuilder(32);
- if (isColourByLabel())
+ if (isColourByAttribute())
{
- sb.append("label");
- if (hasThreshold())
- {
- sb.append(BAR).append(BAR).append(BAR);
- }
+ sb.append(ATTRIBUTE).append(BAR);
+ sb.append(
+ FeatureMatcher.toAttributeDisplayName(getAttributeName()));
+ }
+ else if (isColourByLabel())
+ {
+ sb.append(LABEL);
+ }
+ else
+ {
+ sb.append(SCORE);
}
if (isGraduatedColour())
{
- sb.append(Format.getHexString(getMinColour())).append(BAR);
+ sb.append(BAR).append(Format.getHexString(getMinColour()))
+ .append(BAR);
sb.append(Format.getHexString(getMaxColour())).append(BAR);
+ String noValue = minColour.equals(noColour) ? NO_VALUE_MIN
+ : (maxColour.equals(noColour) ? NO_VALUE_MAX
+ : NO_VALUE_NONE);
+ sb.append(noValue).append(BAR);
if (!isAutoScaled())
{
- sb.append("abso").append(BAR);
+ sb.append(ABSOLUTE).append(BAR);
+ }
+ }
+ else
+ {
+ /*
+ * colour by text with score threshold: empty fields for
+ * minColour and maxColour (not used)
+ */
+ if (hasThreshold())
+ {
+ sb.append(BAR).append(BAR).append(BAR);
}
}
if (hasThreshold() || isGraduatedColour())
sb.append(getMax()).append(BAR);
if (isBelowThreshold())
{
- sb.append("below").append(BAR).append(getThreshold());
+ sb.append(BELOW).append(BAR).append(getThreshold());
}
else if (isAboveThreshold())
{
- sb.append("above").append(BAR).append(getThreshold());
+ sb.append(ABOVE).append(BAR).append(getThreshold());
}
else
{
@Override
public boolean isColourByAttribute()
{
- return byAttributeName != null;
+ return attributeName != null;
}
@Override
- public String getAttributeName()
+ public String[] getAttributeName()
{
- return byAttributeName;
+ return attributeName;
}
@Override
- public void setAttributeName(String name)
+ public void setAttributeName(String... name)
{
- byAttributeName = name;
+ attributeName = name;
}
}