+/*
+ * 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 static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
+import static org.testng.AssertJUnit.fail;
import jalview.datamodel.SequenceFeature;
+import jalview.gui.JvOptionPane;
+import jalview.util.ColorUtils;
+import jalview.util.Format;
import java.awt.Color;
+import junit.extensions.PA;
+
+import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class FeatureColourTest
{
- @Test(groups = { "Functional" })
- public void testIsColored_simpleColour()
+
+ @BeforeClass(alwaysRun = true)
+ public void setUpJvOptionPane()
{
- FeatureColour fc = new FeatureColour(Color.RED);
- assertTrue(fc.isColored(new SequenceFeature()));
+ JvOptionPane.setInteractiveMode(false);
+ JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
}
@Test(groups = { "Functional" })
- public void testIsColored_colourByLabel()
+ public void testCopyConstructor()
{
- FeatureColour fc = new FeatureColour();
+ /*
+ * plain colour
+ */
+ FeatureColour fc = new FeatureColour(Color.RED);
+ FeatureColour fc1 = new FeatureColour(fc);
+ assertTrue(fc1.getColour().equals(Color.RED));
+ assertFalse(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+
+ /*
+ * min-max colour
+ */
+ fc = new FeatureColour(Color.gray, Color.black, 10f, 20f);
+ fc.setAboveThreshold(true);
+ fc.setThreshold(12f);
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertTrue(fc1.isAboveThreshold());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(12f, fc1.getThreshold());
+ assertEquals(Color.gray, fc1.getMinColour());
+ assertEquals(Color.black, fc1.getMaxColour());
+ assertEquals(Color.gray, fc1.getNoColour());
+ assertEquals(10f, fc1.getMin());
+ assertEquals(20f, fc1.getMax());
+
+ /*
+ * min-max-noValue colour
+ */
+ fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
+ fc.setAboveThreshold(true);
+ fc.setThreshold(12f);
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertTrue(fc1.isAboveThreshold());
+ assertEquals(12f, fc1.getThreshold());
+ assertEquals(Color.gray, fc1.getMinColour());
+ assertEquals(Color.black, fc1.getMaxColour());
+ assertEquals(Color.green, fc1.getNoColour());
+ assertEquals(10f, fc1.getMin());
+ assertEquals(20f, fc1.getMax());
+
+ /*
+ * colour by label
+ */
+ fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isColourByLabel());
+ assertFalse(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+
+ /*
+ * colour by attribute (label)
+ */
+ fc = new FeatureColour();
fc.setColourByLabel(true);
- assertTrue(fc.isColored(new SequenceFeature()));
+ fc.setAttributeName("AF");
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isColourByLabel());
+ assertFalse(fc1.isGraduatedColour());
+ assertTrue(fc1.isColourByAttribute());
+ assertEquals("AF", fc1.getAttributeName());
+
+ /*
+ * colour by attribute (value)
+ */
+ fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
+ fc.setAboveThreshold(true);
+ fc.setThreshold(12f);
+ fc.setAttributeName("AF");
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertTrue(fc1.isColourByAttribute());
+ assertEquals("AF", fc1.getAttributeName());
+ assertTrue(fc1.isAboveThreshold());
+ assertEquals(12f, fc1.getThreshold());
+ assertEquals(Color.gray, fc1.getMinColour());
+ assertEquals(Color.black, fc1.getMaxColour());
+ assertEquals(Color.green, fc1.getNoColour());
+ assertEquals(10f, fc1.getMin());
+ assertEquals(20f, fc1.getMax());
}
@Test(groups = { "Functional" })
- public void testIsColored_aboveThreshold()
+ public void testCopyConstructor_minMax()
{
- // graduated colour range from score 20 to 100
- FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 20f,
- 100f);
+ /*
+ * graduated colour
+ */
+ FeatureColour fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
+ assertTrue(fc.isGraduatedColour());
+ assertFalse(fc.isColourByLabel());
+ assertFalse(fc.isColourByAttribute());
+ assertNull(fc.getAttributeName());
+ assertEquals(1f, fc.getMin());
+ assertEquals(5f, fc.getMax());
- // score 0 is adjusted to bottom of range
- SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 0f,
- null);
- assertTrue(fc.isColored(sf));
- assertEquals(Color.WHITE, fc.getColor(sf));
-
- // score 120 is adjusted to top of range
- sf.setScore(120f);
- assertEquals(Color.BLACK, fc.getColor(sf));
-
- // value below threshold is still rendered
- // setting threshold has no effect yet...
- fc.setThreshold(60f);
- sf.setScore(36f);
- assertTrue(fc.isColored(sf));
- assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+ /*
+ * update min-max bounds
+ */
+ FeatureColour fc1 = new FeatureColour(fc, 2f, 6f);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(2f, fc1.getMin());
+ assertEquals(6f, fc1.getMax());
+ assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
- // now apply threshold:
- fc.setAboveThreshold(true);
- assertFalse(fc.isColored(sf));
- // colour is still returned though ?!?
- assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+ /*
+ * update min-max bounds - high to low
+ */
+ fc1 = new FeatureColour(fc, 23f, 16f);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(23f, fc1.getMin());
+ assertEquals(16f, fc1.getMax());
+ assertTrue((boolean) PA.getValue(fc1, "isHighToLow"));
+
+ /*
+ * graduated colour by attribute
+ */
+ fc1.setAttributeName("AF");
+ fc1 = new FeatureColour(fc1, 13f, 36f);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertTrue(fc1.isColourByAttribute());
+ assertEquals("AF", fc1.getAttributeName());
+ assertEquals(13f, fc1.getMin());
+ assertEquals(36f, fc1.getMax());
+ assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
- sf.setScore(84); // above threshold now
- assertTrue(fc.isColored(sf));
- assertEquals(new Color(51, 51, 51), fc.getColor(sf));
+ /*
+ * colour by label
+ */
+ fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
+ fc.setColourByLabel(true);
+ assertFalse(fc.isGraduatedColour());
+ assertTrue(fc.isColourByLabel());
+ assertFalse(fc.isColourByAttribute());
+ assertNull(fc.getAttributeName());
+ assertEquals(1f, fc.getMin());
+ assertEquals(5f, fc.getMax());
+
+ /*
+ * update min-max bounds
+ */
+ fc1 = new FeatureColour(fc, 2f, 6f);
+ assertFalse(fc1.isGraduatedColour());
+ assertTrue(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(2f, fc1.getMin());
+ assertEquals(6f, fc1.getMax());
+
+ /*
+ * colour by attribute text
+ */
+ fc1.setAttributeName("AC");
+ fc1 = new FeatureColour(fc1, 13f, 36f);
+ assertFalse(fc1.isGraduatedColour());
+ assertTrue(fc1.isColourByLabel());
+ assertTrue(fc1.isColourByAttribute());
+ assertEquals("AC", fc1.getAttributeName());
+ assertEquals(13f, fc1.getMin());
+ assertEquals(36f, fc1.getMax());
}
@Test(groups = { "Functional" })
public void testGetColor_simpleColour()
{
FeatureColour fc = new FeatureColour(Color.RED);
- assertEquals(Color.RED, fc.getColor(new SequenceFeature()));
+ assertEquals(Color.RED,
+ fc.getColor(new SequenceFeature("Cath", "", 1, 2, 0f, null)));
}
@Test(groups = { "Functional" })
fc.setColourByLabel(true);
SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
null);
- Color expected = UserColourScheme.createColourFromName("desc");
+ Color expected = ColorUtils.createColourFromName("desc");
assertEquals(expected, fc.getColor(sf));
}
@Test(groups = { "Functional" })
public void testGetColor_Graduated()
{
- // graduated colour from score 0 to 100, gray(128, 128, 128) to red(255, 0, 0)
+ /*
+ * graduated colour from
+ * score 0 to 100
+ * gray(128, 128, 128) to red(255, 0, 0)
+ */
FeatureColour fc = new FeatureColour(Color.GRAY, Color.RED, 0f, 100f);
// feature score is 75 which is 3/4 of the way from GRAY to RED
SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
}
@Test(groups = { "Functional" })
- public void testGetColor_belowThreshold()
+ public void testGetColor_aboveBelowThreshold()
{
// gradient from [50, 150] from WHITE(255, 255, 255) to BLACK(0, 0, 0)
FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 50f,
150f);
SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 70f,
null);
+
+ /*
+ * feature with score of Float.NaN is always assigned minimum colour
+ */
+ SequenceFeature sf2 = new SequenceFeature("type", "desc", 0, 20,
+ Float.NaN, null);
+
fc.setThreshold(100f); // ignore for now
- assertTrue(fc.isColored(sf));
assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+ assertEquals(Color.white, fc.getColor(sf2));
fc.setAboveThreshold(true); // feature lies below threshold
- assertFalse(fc.isColored(sf));
- assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+ assertNull(fc.getColor(sf));
+ assertEquals(Color.white, fc.getColor(sf2));
+
+ fc.setBelowThreshold(true);
+ fc.setThreshold(70f);
+ assertNull(fc.getColor(sf)); // feature score == threshold - hidden
+ assertEquals(Color.white, fc.getColor(sf2));
+ fc.setThreshold(69f);
+ assertNull(fc.getColor(sf)); // feature score > threshold - hidden
+ assertEquals(Color.white, fc.getColor(sf2));
+ }
+
+ /**
+ * 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|abso|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|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|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);
+ fc.setAutoScaled(false);
+ 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());
+
+ descriptor = String
+ .format("blue|255,0,255|absolute|20.0|95.0|below|66.0");
+ fc = FeatureColour.parseJalviewFeatureColour(descriptor);
+ assertTrue(fc.isGraduatedColour());
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_colourByAttributeText()
+ {
+ FeatureColour fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ fc.setAttributeName("consequence");
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
+ null);
+
+ /*
+ * if feature has no such attribute, use 'no value' colour
+ */
+ assertEquals(FeatureColour.DEFAULT_NO_COLOUR, fc.getColor(sf));
+
+ /*
+ * if feature has attribute, generate colour from value
+ */
+ sf.setValue("consequence", "benign");
+ Color expected = ColorUtils.createColourFromName("benign");
+ assertEquals(expected, fc.getColor(sf));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_GraduatedByAttributeValue()
+ {
+ /*
+ * graduated colour based on attribute value for AF
+ * given a min-max range of 0-100
+ */
+ FeatureColour fc = new FeatureColour(new Color(50, 100, 150),
+ new Color(150, 200, 250), Color.yellow, 0f, 100f);
+ String attName = "AF";
+ fc.setAttributeName(attName);
+
+ /*
+ * first case: feature lacks the attribute - use 'no value' colour
+ */
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
+ null);
+ assertEquals(Color.yellow, fc.getColor(sf));
+
+ /*
+ * second case: attribute present but not numeric - treat as if absent
+ */
+ sf.setValue(attName, "twelve");
+ assertEquals(Color.yellow, fc.getColor(sf));
+
+ /*
+ * third case: valid attribute value
+ */
+ sf.setValue(attName, "20.0");
+ Color expected = new Color(70, 120, 170);
+ assertEquals(expected, fc.getColor(sf));
}
}