From: gmungoc Date: Thu, 25 Feb 2016 12:43:11 +0000 (+0000) Subject: JAL-2015 parse/write Jalview feature format pushed into FeatureColour, X-Git-Tag: Release_2_10_0~161^2~1^2~1 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=3e943322f9d66e8718316ba8bd03669adce65149;p=jalview.git JAL-2015 parse/write Jalview feature format pushed into FeatureColour, tests added for same --- diff --git a/src/jalview/api/FeatureColourI.java b/src/jalview/api/FeatureColourI.java index 0a333ca..2e82afd 100644 --- a/src/jalview/api/FeatureColourI.java +++ b/src/jalview/api/FeatureColourI.java @@ -146,4 +146,11 @@ public interface FeatureColourI * @param max */ void updateBounds(float min, float max); + + /** + * Returns the colour in Jalview features file format + * + * @return + */ + String toJalviewFormat(String featureType); } diff --git a/src/jalview/io/FeaturesFile.java b/src/jalview/io/FeaturesFile.java index 6bc0374..d1c33ce 100755 --- a/src/jalview/io/FeaturesFile.java +++ b/src/jalview/io/FeaturesFile.java @@ -27,10 +27,8 @@ import jalview.datamodel.AlignmentI; import jalview.datamodel.SequenceDummy; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; -import jalview.schemes.AnnotationColourGradient; import jalview.schemes.FeatureColour; import jalview.schemes.UserColourScheme; -import jalview.util.Format; import jalview.util.MapList; import java.io.IOException; @@ -218,14 +216,12 @@ public class FeaturesFile extends AlignFile * GFF3 file */ ArrayList newseqs = new ArrayList(); - String type, desc, token = null; + String theType, desc, token = null; - int index, start, end; - float score; StringTokenizer st; SequenceFeature sf; String featureGroup = null, groupLink = null; - Map typeLink = new Hashtable(); + Map typeLink = new Hashtable(); /** * when true, assume GFF style features rather than Jalview style. */ @@ -233,6 +229,7 @@ public class FeaturesFile extends AlignFile Map gffProps = new HashMap(); while ((line = nextLine()) != null) { + int featureStart, featureEnd; // skip comments/process pragmas if (line.startsWith("#")) { @@ -258,8 +255,8 @@ public class FeaturesFile extends AlignFile if (st.countTokens() > 1 && st.countTokens() < 4) { GFFFile = false; - type = st.nextToken(); - if (type.equalsIgnoreCase("startgroup")) + theType = st.nextToken(); + if (theType.equalsIgnoreCase("startgroup")) { featureGroup = st.nextToken(); if (st.hasMoreElements()) @@ -268,7 +265,7 @@ public class FeaturesFile extends AlignFile featureLink.put(featureGroup, groupLink); } } - else if (type.equalsIgnoreCase("endgroup")) + else if (theType.equalsIgnoreCase("endgroup")) { // We should check whether this is the current group, // but at present theres no way of showing more than 1 group @@ -278,207 +275,29 @@ public class FeaturesFile extends AlignFile } else { - FeatureColourI colour = null; String colscheme = st.nextToken(); - if (colscheme.indexOf("|") > -1 - || colscheme.trim().equalsIgnoreCase("label")) + try { - // Parse '|' separated graduated colourscheme fields: - // [label|][mincolour|maxcolour|[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] - // can either provide 'label' only, first is optional, next two - // colors are required (but may be - // left blank), next is optional, nxt two min/max are required. - // first is either 'label' - // first/second and third are both hexadecimal or word equivalent - // colour. - // next two are values parsed as floats. - // fifth is either 'above','below', or 'none'. - // sixth is a float value and only required when fifth is either - // 'above' or 'below'. - StringTokenizer gcol = new StringTokenizer(colscheme, "|", - true); - // set defaults - int threshtype = AnnotationColourGradient.NO_THRESHOLD; - float min = Float.MIN_VALUE, max = Float.MAX_VALUE, threshval = Float.NaN; - boolean labelCol = false; - // Parse spec line - String mincol = gcol.nextToken(); - if (mincol == "|") - { - System.err - .println("Expected either 'label' or a colour specification in the line: " - + line); - continue; - } - String maxcol = null; - if (mincol.toLowerCase().indexOf("label") == 0) - { - labelCol = true; - mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); // skip - // '|' - mincol = (gcol.hasMoreTokens() ? gcol.nextToken() : null); - } - String abso = null, minval, maxval; - if (mincol != null) - { - // at least four more tokens - if (mincol.equals("|")) - { - mincol = ""; - } - else - { - gcol.nextToken(); // skip next '|' - } - // continue parsing rest of line - maxcol = gcol.nextToken(); - if (maxcol.equals("|")) - { - maxcol = ""; - } - else - { - gcol.nextToken(); // skip next '|' - } - abso = gcol.nextToken(); - gcol.nextToken(); // skip next '|' - if (abso.toLowerCase().indexOf("abso") != 0) - { - minval = abso; - abso = null; - } - else - { - minval = gcol.nextToken(); - gcol.nextToken(); // skip next '|' - } - maxval = gcol.nextToken(); - if (gcol.hasMoreTokens()) - { - gcol.nextToken(); // skip next '|' - } - try - { - if (minval.length() > 0) - { - min = new Float(minval).floatValue(); - } - } catch (Exception e) - { - System.err - .println("Couldn't parse the minimum value for graduated colour for type (" - + colscheme - + ") - did you misspell 'auto' for the optional automatic colour switch ?"); - e.printStackTrace(); - } - try - { - if (maxval.length() > 0) - { - max = new Float(maxval).floatValue(); - } - } catch (Exception e) - { - System.err - .println("Couldn't parse the maximum value for graduated colour for type (" - + colscheme + ")"); - e.printStackTrace(); - } - } - else - { - // add in some dummy min/max colours for the label-only - // colourscheme. - mincol = "FFFFFF"; - maxcol = "000000"; - } - try - { - colour = new FeatureColour( - new UserColourScheme(mincol).findColour('A'), - new UserColourScheme(maxcol).findColour('A'), min, - max); - } catch (Exception e) + FeatureColourI colour = FeatureColour + .parseJalviewFeatureColour(colscheme); + if (colour != null) { - System.err - .println("Couldn't parse the graduated colour scheme (" - + colscheme + ")"); - e.printStackTrace(); + colours.put(theType, colour); } - if (colour != null) + if (st.hasMoreElements()) { - colour.setColourByLabel(labelCol); - colour.setAutoScaled(abso == null); - // 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")) - { - colour.setBelowThreshold(true); - } - else if (ttype.toLowerCase().startsWith("above")) - { - colour.setAboveThreshold(true); - } - else - { - if (!ttype.toLowerCase().startsWith("no")) - { - System.err - .println("Ignoring unrecognised threshold type : " - + ttype); - } - } - } - if (colour.hasThreshold()) + String link = st.nextToken(); + typeLink.put(theType, link); + if (featureLink == null) { - try - { - gcol.nextToken(); - tval = gcol.nextToken(); - colour.setThreshold(new Float(tval).floatValue()); - } catch (Exception e) - { - System.err - .println("Couldn't parse threshold value as a float: (" - + tval + ")"); - e.printStackTrace(); - } - } - // parse the thresh-is-min token ? - if (gcol.hasMoreTokens()) - { - System.err - .println("Ignoring additional tokens in parameters in graduated colour specification\n"); - while (gcol.hasMoreTokens()) - { - System.err.println("|" + gcol.nextToken()); - } - System.err.println("\n"); + featureLink = new Hashtable(); } + featureLink.put(theType, link); } - } - else + } catch (IllegalArgumentException e) { - UserColourScheme ucs = new UserColourScheme(colscheme); - colour = new FeatureColour(ucs.findColour('A')); - } - if (colour != null) - { - colours.put(type, colour); - } - if (st.hasMoreElements()) - { - String link = st.nextToken(); - typeLink.put(type, link); - if (featureLink == null) - { - featureLink = new Hashtable(); - } - featureLink.put(type, link); + System.err.println("Error parsing feature colour scheme " + + colscheme + " : " + e.getMessage()); } } continue; @@ -502,53 +321,54 @@ public class FeaturesFile extends AlignFile // could also be a source term rather than description line group = new String(desc); } - type = st.nextToken(); + theType = st.nextToken(); try { String stt = st.nextToken(); if (stt.length() == 0 || stt.equals("-")) { - start = 0; + featureStart = 0; } else { - start = Integer.parseInt(stt); + featureStart = Integer.parseInt(stt); } } catch (NumberFormatException ex) { - start = 0; + featureStart = 0; } try { String stt = st.nextToken(); if (stt.length() == 0 || stt.equals("-")) { - end = 0; + featureEnd = 0; } else { - end = Integer.parseInt(stt); + featureEnd = Integer.parseInt(stt); } } catch (NumberFormatException ex) { - end = 0; + featureEnd = 0; } // TODO: decide if non positional feature assertion for input data // where end==0 is generally valid - if (end == 0) + if (featureEnd == 0) { // treat as non-positional feature, regardless. - start = 0; + featureStart = 0; } + float score = 0f; try { score = new Float(st.nextToken()).floatValue(); } catch (NumberFormatException ex) { - score = 0; + // ignore } - sf = new SequenceFeature(type, desc, start, end, score, group); + sf = new SequenceFeature(theType, desc, featureStart, featureEnd, score, group); try { @@ -560,11 +380,12 @@ public class FeaturesFile extends AlignFile if (st.hasMoreTokens()) { - StringBuffer attributes = new StringBuffer(); + StringBuilder attributes = new StringBuilder(); boolean sep = false; while (st.hasMoreTokens()) { - attributes.append((sep ? "\t" : "") + st.nextElement()); + attributes.append(sep ? "\t" : "").append( + st.nextElement()); sep = true; } // TODO validate and split GFF2 attributes field ? parse out @@ -616,8 +437,8 @@ public class FeaturesFile extends AlignFile seqId = null; try { - index = Integer.parseInt(st.nextToken()); - seq = align.getSequenceAt(index); + int idx = Integer.parseInt(st.nextToken()); + seq = align.getSequenceAt(idx); } catch (NumberFormatException ex) { seq = null; @@ -630,27 +451,30 @@ public class FeaturesFile extends AlignFile break; } - start = Integer.parseInt(st.nextToken()); - end = Integer.parseInt(st.nextToken()); + featureStart = Integer.parseInt(st.nextToken()); + featureEnd = Integer.parseInt(st.nextToken()); - type = st.nextToken(); + theType = st.nextToken(); - if (!colours.containsKey(type)) + if (!colours.containsKey(theType)) { // Probably the old style groups file - UserColourScheme ucs = new UserColourScheme(type); - colours.put(type, ucs.findColour('A')); + colours.put( + theType, + new FeatureColour(UserColourScheme + .getColourFromString(theType))); } - sf = new SequenceFeature(type, desc, "", start, end, featureGroup); + sf = new SequenceFeature(theType, desc, "", featureStart, featureEnd, featureGroup); if (st.hasMoreTokens()) { + float score = 0f; try { score = new Float(st.nextToken()).floatValue(); // update colourgradient bounds if allowed to } catch (NumberFormatException ex) { - score = 0; + // ignore } sf.setScore(score); } @@ -659,9 +483,9 @@ public class FeaturesFile extends AlignFile sf.addLink(groupLink); sf.description += "%LINK%"; } - if (typeLink.containsKey(type) && removeHTML) + if (typeLink.containsKey(theType) && removeHTML) { - sf.addLink(typeLink.get(type).toString()); + sf.addLink(typeLink.get(theType)); sf.description += "%LINK%"; } @@ -1198,14 +1022,14 @@ public class FeaturesFile extends AlignFile Map visible, boolean visOnly, boolean nonpos) { - StringBuffer out = new StringBuffer(); - SequenceFeature[] next; - boolean featuresGen = false; if (visOnly && !nonpos && (visible == null || visible.size() < 1)) { // no point continuing. return "No Features Visible"; } + StringBuilder out = new StringBuilder(128); + SequenceFeature[] next; + boolean featuresGen = false; if (visible != null && visOnly) { @@ -1213,48 +1037,11 @@ public class FeaturesFile extends AlignFile // viewed features // TODO: decide if feature links should also be written here ? Iterator en = visible.keySet().iterator(); - String feature, color; while (en.hasNext()) { - feature = en.next(); - - FeatureColourI gc = visible.get(feature); - if (!gc.isSimpleColour()) - { - color = (gc.isColourByLabel() ? "label|" : "") - + Format.getHexString(gc.getMinColour()) + "|" - + Format.getHexString(gc.getMaxColour()) - + (gc.isAutoScaled() ? "|" : "|abso|") + gc.getMin() - + "|" - + gc.getMax() + "|"; - if (gc.isBelowThreshold()) - { - color += "below|" + gc.getThreshold(); - } - else if (gc.isAboveThreshold()) - { - color += "above|" + gc.getThreshold(); - } - else - { - color += "none"; - } - } - else - { - color = Format.getHexString(gc.getColour()); - } - // else - // { - // // legacy support for integer objects containing colour triplet - // values - // color = Format.getHexString(new java.awt.Color(Integer - // .parseInt(visible.get(type).toString()))); - // } - out.append(feature); - out.append("\t"); - out.append(color); - out.append(newline); + String featureType = en.next(); + FeatureColourI colour = visible.get(featureType); + out.append(colour.toJalviewFormat(featureType)).append(newline); } } // Work out which groups are both present and visible diff --git a/src/jalview/schemes/FeatureColour.java b/src/jalview/schemes/FeatureColour.java index ce382c3..bd58273 100644 --- a/src/jalview/schemes/FeatureColour.java +++ b/src/jalview/schemes/FeatureColour.java @@ -2,14 +2,18 @@ package jalview.schemes; import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; +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 { + private static final String BAR = "|"; + final private Color colour; final private Color minColour; @@ -49,6 +53,204 @@ public class FeatureColour implements FeatureColourI final private float deltaBlue; /** + * Parses a Jalview features file format colour descriptor + * [label|][mincolour|maxcolour + * |[absolute|]minvalue|maxvalue|thresholdtype|thresholdvalue] Examples: + *
    + *
  • red
  • + *
  • a28bbb
  • + *
  • 25,125,213
  • + *
  • label
  • + *
  • label|||0.0|0.0|above|12.5
  • + *
  • label|||0.0|0.0|below|12.5
  • + *
  • red|green|12.0|26.0|none
  • + *
  • a28bbb|3eb555|12.0|26.0|above|12.5
  • + *
  • a28bbb|3eb555|abso|12.0|26.0|below|12.5
  • + *
+ * + * @param descriptor + * @return + * @throws IllegalArgumentException + * if not parseable + */ + public static FeatureColour parseJalviewFeatureColour(String descriptor) + { + StringTokenizer gcol = new StringTokenizer(descriptor, "|", true); + float min = Float.MIN_VALUE; + float max = Float.MAX_VALUE; + boolean labelColour = false; + + String mincol = gcol.nextToken(); + if (mincol == "|") + { + 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 = UserColourScheme.getColourFromString(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 = false; + String tok = null, minval, maxval; + if (mincol != null) + { + // at least four more tokens + if (mincol.equals("|")) + { + mincol = ""; + } + else + { + gcol.nextToken(); // skip next '|' + } + maxcol = gcol.nextToken(); + if (maxcol.equals("|")) + { + maxcol = ""; + } + else + { + gcol.nextToken(); // skip next '|' + } + tok = gcol.nextToken(); + gcol.nextToken(); // skip next '|' + if (tok.toLowerCase().indexOf("abso") != 0) + { + minval = tok; + autoScaled = true; + } + else + { + minval = gcol.nextToken(); + gcol.nextToken(); // skip next '|' + } + maxval = gcol.nextToken(); + if (gcol.hasMoreTokens()) + { + gcol.nextToken(); // skip next '|' + } + try + { + if (minval.length() > 0) + { + min = new Float(minval).floatValue(); + } + } catch (Exception 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 (Exception 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 + { + featureColour = new FeatureColour( + new UserColourScheme(mincol).findColour('A'), + new UserColourScheme(maxcol).findColour('A'), min, max); + featureColour.setColourByLabel(labelColour); + featureColour.setAutoScaled(autoScaled); + // 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")) + { + 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 (Exception 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("|" + gcol.nextToken()); + } + System.err.println("\n"); + } + return featureColour; + } catch (Exception e) + { + throw new IllegalArgumentException(e.getMessage()); + } + } + + /** * Default constructor */ public FeatureColour() @@ -63,8 +265,8 @@ public class FeatureColour implements FeatureColourI */ public FeatureColour(Color c) { - minColour = null; - maxColour = null; + minColour = Color.WHITE; + maxColour = Color.BLACK; minRed = 0f; minGreen = 0f; minBlue = 0f; @@ -418,4 +620,54 @@ public class FeatureColour implements FeatureColourI 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(BAR).append(BAR).append(BAR); + } + } + if (isGraduatedColour()) + { + sb.append(Format.getHexString(getMinColour())).append(BAR); + sb.append(Format.getHexString(getMaxColour())).append(BAR); + if (isAutoScaled()) + { + sb.append("abso").append(BAR); + } + } + if (hasThreshold() || isGraduatedColour()) + { + sb.append(getMin()).append(BAR); + sb.append(getMax()).append(BAR); + if (isBelowThreshold()) + { + sb.append("below").append(BAR).append(getThreshold()); + } + else if (isAboveThreshold()) + { + sb.append("above").append(BAR).append(getThreshold()); + } + else + { + sb.append("none"); + } + } + colourString = sb.toString(); + } + return String.format("%s\t%s", featureType, colourString); + } + } diff --git a/src/jalview/schemes/UserColourScheme.java b/src/jalview/schemes/UserColourScheme.java index 2498208..1a2e417 100755 --- a/src/jalview/schemes/UserColourScheme.java +++ b/src/jalview/schemes/UserColourScheme.java @@ -101,6 +101,10 @@ public class UserColourScheme extends ResidueColourScheme public static Color getColourFromString(String colour) { + if (colour == null) + { + return null; + } colour = colour.trim(); Color col = null; diff --git a/test/jalview/schemes/FeatureColourTest.java b/test/jalview/schemes/FeatureColourTest.java index 81357fa..483ea5d 100644 --- a/test/jalview/schemes/FeatureColourTest.java +++ b/test/jalview/schemes/FeatureColourTest.java @@ -3,8 +3,10 @@ package jalview.schemes; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertTrue; +import static org.testng.AssertJUnit.fail; import jalview.datamodel.SequenceFeature; +import jalview.util.Format; import java.awt.Color; @@ -112,4 +114,187 @@ public class FeatureColourTest assertFalse(fc.isColored(sf)); assertEquals(new Color(204, 204, 204), fc.getColor(sf)); } + + /** + * Test output of feature colours to Jalview features file format + */ + @Test(groups = { "Functional" }) + public void testToJalviewFormat() + { + /* + * plain colour - to RGB hex code + */ + FeatureColour fc = new FeatureColour(Color.RED); + String redHex = Format.getHexString(Color.RED); + String hexColour = redHex; + assertEquals("domain\t" + hexColour, fc.toJalviewFormat("domain")); + + /* + * colour by label (no threshold) + */ + fc = new FeatureColour(); + fc.setColourByLabel(true); + assertEquals("domain\tlabel", fc.toJalviewFormat("domain")); + + /* + * colour by label (autoscaled) (an odd state you can reach by selecting + * 'above threshold', then deselecting 'threshold is min/max' then 'colour + * by label') + */ + fc.setAutoScaled(true); + assertEquals("domain\tlabel", fc.toJalviewFormat("domain")); + + /* + * colour by label (above threshold) (min/max values are output though not + * used by this scheme) + */ + fc.setAutoScaled(false); + fc.setThreshold(12.5f); + fc.setAboveThreshold(true); + assertEquals("domain\tlabel|||0.0|0.0|above|12.5", + fc.toJalviewFormat("domain")); + + /* + * colour by label (below threshold) + */ + fc.setBelowThreshold(true); + assertEquals("domain\tlabel|||0.0|0.0|below|12.5", + fc.toJalviewFormat("domain")); + + /* + * graduated colour, no threshold + */ + fc = new FeatureColour(Color.GREEN, Color.RED, 12f, 25f); + String greenHex = Format.getHexString(Color.GREEN); + String expected = String.format("domain\t%s|%s|12.0|25.0|none", + greenHex, redHex); + assertEquals(expected, fc.toJalviewFormat("domain")); + + /* + * colour ranges over the actual score ranges (not min/max) + */ + fc.setAutoScaled(true); + expected = String.format("domain\t%s|%s|abso|12.0|25.0|none", greenHex, + redHex); + assertEquals(expected, fc.toJalviewFormat("domain")); + + /* + * graduated colour below threshold + */ + fc.setThreshold(12.5f); + fc.setBelowThreshold(true); + expected = String.format("domain\t%s|%s|abso|12.0|25.0|below|12.5", + greenHex, redHex); + assertEquals(expected, fc.toJalviewFormat("domain")); + + /* + * graduated colour above threshold + */ + fc.setThreshold(12.5f); + fc.setAboveThreshold(true); + expected = String.format("domain\t%s|%s|abso|12.0|25.0|above|12.5", + greenHex, redHex); + assertEquals(expected, fc.toJalviewFormat("domain")); + } + + /** + * Test parsing of feature colours from Jalview features file format + */ + @Test(groups = { "Functional" }) + public void testParseJalviewFeatureColour() + { + /* + * simple colour by name + */ + FeatureColour fc = FeatureColour.parseJalviewFeatureColour("red"); + assertTrue(fc.isSimpleColour()); + assertEquals(Color.RED, fc.getColour()); + + /* + * simple colour by hex code + */ + fc = FeatureColour.parseJalviewFeatureColour(Format + .getHexString(Color.RED)); + assertTrue(fc.isSimpleColour()); + assertEquals(Color.RED, fc.getColour()); + + /* + * simple colour by rgb triplet + */ + fc = FeatureColour.parseJalviewFeatureColour("255,0,0"); + assertTrue(fc.isSimpleColour()); + assertEquals(Color.RED, fc.getColour()); + + /* + * malformed colour + */ + try + { + fc = FeatureColour.parseJalviewFeatureColour("oops"); + fail("expected exception"); + } catch (IllegalArgumentException e) + { + assertEquals("Invalid colour descriptor: oops", e.getMessage()); + } + + /* + * colour by label (no threshold) + */ + fc = FeatureColour.parseJalviewFeatureColour("label"); + assertTrue(fc.isColourByLabel()); + assertFalse(fc.hasThreshold()); + + /* + * colour by label (with threshold) + */ + fc = FeatureColour + .parseJalviewFeatureColour("label|||0.0|0.0|above|12.0"); + assertTrue(fc.isColourByLabel()); + assertTrue(fc.isAboveThreshold()); + assertEquals(12.0f, fc.getThreshold()); + + /* + * graduated colour (by name) (no threshold) + */ + fc = FeatureColour.parseJalviewFeatureColour("red|green|10.0|20.0"); + assertTrue(fc.isGraduatedColour()); + assertFalse(fc.hasThreshold()); + assertEquals(Color.RED, fc.getMinColour()); + assertEquals(Color.GREEN, fc.getMaxColour()); + assertEquals(10f, fc.getMin()); + assertEquals(20f, fc.getMax()); + assertTrue(fc.isAutoScaled()); + + /* + * graduated colour (by hex code) (above threshold) + */ + String descriptor = String.format("%s|%s|10.0|20.0|above|15", + Format.getHexString(Color.RED), + Format.getHexString(Color.GREEN)); + fc = FeatureColour.parseJalviewFeatureColour(descriptor); + assertTrue(fc.isGraduatedColour()); + assertTrue(fc.hasThreshold()); + assertTrue(fc.isAboveThreshold()); + assertEquals(15f, fc.getThreshold()); + assertEquals(Color.RED, fc.getMinColour()); + assertEquals(Color.GREEN, fc.getMaxColour()); + assertEquals(10f, fc.getMin()); + assertEquals(20f, fc.getMax()); + assertTrue(fc.isAutoScaled()); + + /* + * graduated colour (by RGB triplet) (below threshold), absolute scale + */ + descriptor = String.format("255,0,0|0,255,0|abso|10.0|20.0|below|15"); + fc = FeatureColour.parseJalviewFeatureColour(descriptor); + assertTrue(fc.isGraduatedColour()); + assertFalse(fc.isAutoScaled()); + assertTrue(fc.hasThreshold()); + assertTrue(fc.isBelowThreshold()); + assertEquals(15f, fc.getThreshold()); + assertEquals(Color.RED, fc.getMinColour()); + assertEquals(Color.GREEN, fc.getMaxColour()); + assertEquals(10f, fc.getMin()); + assertEquals(20f, fc.getMax()); + } } diff --git a/test/jalview/schemes/UserColourSchemeTest.java b/test/jalview/schemes/UserColourSchemeTest.java new file mode 100644 index 0000000..88f4331 --- /dev/null +++ b/test/jalview/schemes/UserColourSchemeTest.java @@ -0,0 +1,50 @@ +package jalview.schemes; + +import static org.testng.AssertJUnit.assertEquals; +import static org.testng.AssertJUnit.assertNull; +import static org.testng.AssertJUnit.assertSame; + +import java.awt.Color; + +import org.testng.annotations.Test; +public class UserColourSchemeTest +{ + + @Test(groups = "functional") + public void testGetColourFromString() + { + /* + * by colour name - if known to AWT, and included in + * + * @see ColourSchemeProperty.getAWTColorFromName() + */ + assertSame(Color.RED, UserColourScheme.getColourFromString("red")); + assertSame(Color.RED, UserColourScheme.getColourFromString("Red")); + assertSame(Color.RED, UserColourScheme.getColourFromString(" RED ")); + + /* + * by RGB hex code + */ + String hexColour = Integer.toHexString(Color.RED.getRGB() & 0xffffff); + assertEquals(Color.RED, UserColourScheme.getColourFromString(hexColour)); + // 'hex' prefixes _not_ wanted here + assertNull(UserColourScheme.getColourFromString("0x" + hexColour)); + assertNull(UserColourScheme.getColourFromString("#" + hexColour)); + + /* + * by RGB triplet + */ + String rgb = String.format("%d,%d,%d", Color.red.getRed(), + Color.red.getGreen(), Color.red.getBlue()); + assertEquals(Color.RED, UserColourScheme.getColourFromString(rgb)); + + /* + * odds and ends + */ + assertNull(UserColourScheme.getColourFromString(null)); + assertNull(UserColourScheme.getColourFromString("rubbish")); + assertEquals(Color.WHITE, UserColourScheme.getColourFromString("-1")); + assertNull(UserColourScheme.getColourFromString(String + .valueOf(Integer.MAX_VALUE))); + } +}