X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fschemes%2FFeatureColour.java;h=c73e32bdfea0551c4c267a5e6fe6978e64f2f47b;hb=3c8a25936a2d805e7e3d7ab82f83b13135406d18;hp=168ab543262c93287d40dd5c9b566babbd74caf3;hpb=586870ac526e851e3589fd8880a4a2fc090bbcc3;p=jalview.git diff --git a/src/jalview/schemes/FeatureColour.java b/src/jalview/schemes/FeatureColour.java index 168ab54..c73e32b 100644 --- a/src/jalview/schemes/FeatureColour.java +++ b/src/jalview/schemes/FeatureColour.java @@ -22,6 +22,7 @@ package jalview.schemes; import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; +import jalview.datamodel.features.FeatureMatcher; import jalview.util.ColorUtils; import jalview.util.Format; @@ -49,7 +50,28 @@ import java.util.StringTokenizer; */ 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 = "|"; @@ -78,10 +100,10 @@ public class FeatureColour implements FeatureColourI 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; @@ -93,8 +115,6 @@ public class FeatureColour implements FeatureColourI private boolean aboveThreshold; - private boolean thresholdIsMinOrMax; - private boolean isHighToLow; private boolean autoScaled; @@ -113,16 +133,29 @@ public class FeatureColour implements FeatureColourI /** * Parses a Jalview features file format colour descriptor - * [label|][mincolour|maxcolour - * |[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] Examples: + *

+ * + * [label|score|[attribute|attributeName]|][mincolour|maxcolour| + * [absolute|]minvalue|maxvalue|[noValueOption|]thresholdtype|thresholdvalue] + *

+ * '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 noValueMin, noValueMax, noValueNone + * with default noValueMin. + *

+ * Examples: *

@@ -132,34 +165,71 @@ public class FeatureColour implements FeatureColourI * @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) { @@ -170,34 +240,64 @@ public class FeatureColour implements FeatureColourI } /* + * 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 '|' @@ -221,8 +321,8 @@ public class FeatureColour implements FeatureColourI } catch (Exception e) { throw new IllegalArgumentException( - "Couldn't parse the minimum value for graduated colour (" - + descriptor + ")"); + "Couldn't parse the minimum value for graduated colour ('" + + minval + "')"); } try { @@ -239,34 +339,45 @@ public class FeatureColour implements FeatureColourI } 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(maxColour, 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); } @@ -298,7 +409,7 @@ public class FeatureColour implements FeatureColourI "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"); } @@ -318,36 +429,25 @@ public class FeatureColour implements FeatureColourI } /** - * Constructor given a simple colour + * Constructor given a simple colour. This also 'primes' a graduated colour + * range, where the maximum colour is the given simple colour, and the minimum + * colour a paler shade of it. This is for convenience when switching from a + * simple colour to a graduated colour scheme. * * @param c */ public FeatureColour(Color c) { - minColour = Color.WHITE; - maxColour = Color.BLACK; - noColour = DEFAULT_NO_COLOUR; - minRed = 0f; - minGreen = 0f; - minBlue = 0f; - deltaRed = 0f; - deltaGreen = 0f; - deltaBlue = 0f; - colour = c; - } + /* + * set max colour to the simple colour, min colour to a paler shade of it + */ + this(c, c == null ? Color.white : ColorUtils.bleachColour(c, 0.9f), + c == null ? Color.black : c, DEFAULT_NO_COLOUR, 0, 0); - /** - * Constructor given a colour range and a score range, defaulting 'no value - * colour' to be the same as minimum colour - * - * @param low - * @param high - * @param min - * @param max - */ - public FeatureColour(Color low, Color high, float min, float max) - { - this(low, high, low, min, max); + /* + * but enforce simple colour for now! + */ + setGraduatedColour(false); } /** @@ -371,7 +471,7 @@ public class FeatureColour implements FeatureColourI base = fc.base; range = fc.range; isHighToLow = fc.isHighToLow; - byAttributeName = fc.byAttributeName; + attributeName = fc.attributeName; setAboveThreshold(fc.isAboveThreshold()); setBelowThreshold(fc.isBelowThreshold()); setThreshold(fc.getThreshold()); @@ -380,20 +480,23 @@ public class FeatureColour implements FeatureColourI } /** - * Copy constructor with new min/max ranges + * Constructor that sets both simple and graduated colour values. This allows + * alternative colour schemes to be 'preserved' while switching between them + * to explore their effects on the visualisation. + *

+ * This sets the colour scheme to 'graduated' by default. Override this if + * wanted by calling setGraduatedColour(false) for a simple + * colour, or setColourByLabel(true) for colour by label. * - * @param fc + * @param myColour + * @param low + * @param high + * @param noValueColour * @param min * @param max */ - public FeatureColour(FeatureColour fc, float min, float max) - { - this(fc); - updateBounds(min, max); - } - - public FeatureColour(Color low, Color high, Color noValueColour, - float min, float max) + public FeatureColour(Color myColour, Color low, Color high, + Color noValueColour, float min, float max) { if (low == null) { @@ -403,10 +506,10 @@ public class FeatureColour implements FeatureColourI { high = Color.black; } - graduatedColour = true; - colour = null; + colour = myColour; minColour = low; maxColour = high; + setGraduatedColour(true); noColour = noValueColour; threshold = Float.NaN; isHighToLow = min >= max; @@ -438,7 +541,7 @@ public class FeatureColour implements FeatureColourI * Sets the 'graduated colour' flag. If true, also sets 'colour by label' to * false. */ - void setGraduatedColour(boolean b) + public void setGraduatedColour(boolean b) { graduatedColour = b; if (b) @@ -524,18 +627,6 @@ public class FeatureColour implements FeatureColourI } @Override - public boolean isThresholdMinMax() - { - return thresholdIsMinOrMax; - } - - @Override - public void setThresholdMinMax(boolean b) - { - thresholdIsMinOrMax = b; - } - - @Override public float getThreshold() { return threshold; @@ -581,9 +672,12 @@ public class FeatureColour implements FeatureColourI /** * 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()). + * simple colour, a colour generated from the feature description or other + * attribute (if isColourByLabel()), or a colour derived from the feature + * score or other attribute (if isGraduatedColour()). + *

+ * Answers null if feature score (or attribute) value lies outside a + * configured threshold. * * @param feature * @return @@ -593,8 +687,8 @@ public class FeatureColour implements FeatureColourI { 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); } @@ -607,15 +701,14 @@ public class FeatureColour implements FeatureColourI /* * 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) { @@ -704,21 +797,54 @@ public class FeatureColour implements FeatureColourI 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); + + /* + * 'no value' colour should be null, min or max colour; + * if none of these, coerce to minColour + */ + String noValue = NO_VALUE_MIN; + if (maxColour.equals(noColour)) + { + noValue = NO_VALUE_MAX; + } + if (noColour == null) + { + noValue = 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()) @@ -727,11 +853,11 @@ public class FeatureColour implements FeatureColourI 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 { @@ -746,19 +872,47 @@ public class FeatureColour implements FeatureColourI @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; + } + + @Override + public boolean isOutwithThreshold(SequenceFeature feature) + { + if (!isGraduatedColour()) + { + return false; + } + float scr = feature.getScore(); + if (attributeName != null) + { + try + { + String attVal = feature.getValueAsString(attributeName); + scr = Float.valueOf(attVal); + } catch (Throwable e) + { + scr = Float.NaN; + } + } + if (Float.isNaN(scr)) + { + return false; + } + + return ((isAboveThreshold() && scr <= threshold) + || (isBelowThreshold() && scr >= threshold)); } }