2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.schemes;
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertNull;
26 import static org.testng.AssertJUnit.assertTrue;
27 import static org.testng.AssertJUnit.fail;
28 import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
30 import jalview.api.FeatureColourI;
31 import jalview.datamodel.SequenceFeature;
32 import jalview.gui.JvOptionPane;
33 import jalview.util.ColorUtils;
34 import jalview.util.Format;
36 import java.awt.Color;
38 import org.testng.annotations.BeforeClass;
39 import org.testng.annotations.Test;
41 public class FeatureColourTest
44 @BeforeClass(alwaysRun = true)
45 public void setUpJvOptionPane()
47 JvOptionPane.setInteractiveMode(false);
48 JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
51 @Test(groups = { "Functional" })
52 public void testConstructors()
54 FeatureColourI fc = new FeatureColour();
55 assertNull(fc.getColour());
56 assertTrue(fc.isSimpleColour());
57 assertFalse(fc.isColourByLabel());
58 assertFalse(fc.isGraduatedColour());
59 assertFalse(fc.isColourByAttribute());
60 assertEquals(Color.white, fc.getMinColour());
61 assertEquals(Color.black, fc.getMaxColour());
63 fc = new FeatureColour(Color.RED);
64 assertEquals(Color.red, fc.getColour());
65 assertTrue(fc.isSimpleColour());
66 assertFalse(fc.isColourByLabel());
67 assertFalse(fc.isGraduatedColour());
68 assertFalse(fc.isColourByAttribute());
69 assertEquals(ColorUtils.bleachColour(Color.RED, 0.9f),
71 assertEquals(Color.RED, fc.getMaxColour());
75 @Test(groups = { "Functional" })
76 public void testCopyConstructor()
81 FeatureColour fc = new FeatureColour(Color.RED);
82 FeatureColour fc1 = new FeatureColour(fc);
83 assertTrue(fc1.getColour().equals(Color.RED));
84 assertFalse(fc1.isGraduatedColour());
85 assertFalse(fc1.isColourByLabel());
86 assertFalse(fc1.isColourByAttribute());
87 assertNull(fc1.getAttributeName());
92 fc = new FeatureColour(null, Color.gray, Color.black, Color.gray, 10f,
94 fc.setAboveThreshold(true);
96 fc1 = new FeatureColour(fc);
97 assertTrue(fc1.isGraduatedColour());
98 assertFalse(fc1.isColourByLabel());
99 assertTrue(fc1.isAboveThreshold());
100 assertFalse(fc1.isColourByAttribute());
101 assertNull(fc1.getAttributeName());
102 assertEquals(12f, fc1.getThreshold());
103 assertEquals(Color.gray, fc1.getMinColour());
104 assertEquals(Color.black, fc1.getMaxColour());
105 assertEquals(Color.gray, fc1.getNoColour());
106 assertEquals(10f, fc1.getMin());
107 assertEquals(20f, fc1.getMax());
110 * min-max-noValue colour
112 fc = new FeatureColour(Color.red, Color.gray, Color.black, Color.green,
114 fc.setAboveThreshold(true);
115 fc.setThreshold(12f);
116 fc1 = new FeatureColour(fc);
117 assertTrue(fc1.isGraduatedColour());
118 assertFalse(fc1.isColourByLabel());
119 assertFalse(fc1.isSimpleColour());
120 assertFalse(fc1.isColourByAttribute());
121 assertNull(fc1.getAttributeName());
122 assertTrue(fc1.isAboveThreshold());
123 assertEquals(12f, fc1.getThreshold());
124 assertEquals(Color.gray, fc1.getMinColour());
125 assertEquals(Color.black, fc1.getMaxColour());
126 assertEquals(Color.green, fc1.getNoColour());
127 assertEquals(Color.red, fc1.getColour());
128 assertEquals(10f, fc1.getMin());
129 assertEquals(20f, fc1.getMax());
134 fc = new FeatureColour();
135 fc.setColourByLabel(true);
136 fc1 = new FeatureColour(fc);
137 assertTrue(fc1.isColourByLabel());
138 assertFalse(fc1.isGraduatedColour());
139 assertFalse(fc1.isColourByAttribute());
140 assertNull(fc1.getAttributeName());
143 * colour by attribute (label)
145 fc = new FeatureColour();
146 fc.setColourByLabel(true);
147 fc.setAttributeName("AF");
148 fc1 = new FeatureColour(fc);
149 assertTrue(fc1.isColourByLabel());
150 assertFalse(fc1.isGraduatedColour());
151 assertTrue(fc1.isColourByAttribute());
152 assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName());
155 * colour by attribute (value)
157 fc = new FeatureColour(Color.yellow, Color.gray, Color.black,
158 Color.green, 10f, 20f);
159 fc.setAboveThreshold(true);
160 fc.setThreshold(12f);
161 fc.setAttributeName("AF");
162 fc1 = new FeatureColour(fc);
163 assertTrue(fc1.isGraduatedColour());
164 assertFalse(fc1.isColourByLabel());
165 assertTrue(fc1.isColourByAttribute());
166 assertFalse(fc1.isSimpleColour());
167 assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName());
168 assertTrue(fc1.isAboveThreshold());
169 assertEquals(12f, fc1.getThreshold());
170 assertEquals(Color.gray, fc1.getMinColour());
171 assertEquals(Color.black, fc1.getMaxColour());
172 assertEquals(Color.green, fc1.getNoColour());
173 assertEquals(Color.yellow, fc1.getColour());
174 assertEquals(10f, fc1.getMin());
175 assertEquals(20f, fc1.getMax());
178 @Test(groups = { "Functional" })
179 public void testGetColor_simpleColour()
181 FeatureColour fc = new FeatureColour(Color.RED);
182 assertEquals(Color.RED,
183 fc.getColor(new SequenceFeature("Cath", "", 1, 2, 0f, null)));
186 @Test(groups = { "Functional" })
187 public void testGetColor_colourByLabel()
189 FeatureColour fc = new FeatureColour();
190 fc.setColourByLabel(true);
191 SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
193 Color expected = ColorUtils.createColourFromName("desc");
194 assertEquals(expected, fc.getColor(sf));
197 @Test(groups = { "Functional" })
198 public void testGetColor_Graduated()
201 * graduated colour from
203 * gray(128, 128, 128) to red(255, 0, 0)
205 FeatureColour fc = new FeatureColour(null, Color.GRAY, Color.RED, null,
207 // feature score is 75 which is 3/4 of the way from GRAY to RED
208 SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
210 // the colour gradient is computed in float values from 0-1 (where 1 == 255)
211 float red = 128 / 255f + 3 / 4f * (255 - 128) / 255f;
212 float green = 128 / 255f + 3 / 4f * (0 - 128) / 255f;
213 float blue = 128 / 255f + 3 / 4f * (0 - 128) / 255f;
214 Color expected = new Color(red, green, blue);
215 assertEquals(expected, fc.getColor(sf));
218 @Test(groups = { "Functional" })
219 public void testGetColor_aboveBelowThreshold()
221 // gradient from [50, 150] from WHITE(255, 255, 255) to BLACK(0, 0, 0)
222 FeatureColour fc = new FeatureColour(null, Color.WHITE, Color.BLACK,
223 Color.white, 50f, 150f);
224 SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 70f,
228 * feature with score of Float.NaN is always assigned minimum colour
230 SequenceFeature sf2 = new SequenceFeature("type", "desc", 0, 20,
233 fc.setThreshold(100f); // ignore for now
234 assertEquals(new Color(204, 204, 204), fc.getColor(sf));
235 assertEquals(Color.white, fc.getColor(sf2));
237 fc.setAboveThreshold(true); // feature lies below threshold
238 assertNull(fc.getColor(sf));
239 assertEquals(Color.white, fc.getColor(sf2));
241 fc.setBelowThreshold(true);
242 fc.setThreshold(70f);
243 assertNull(fc.getColor(sf)); // feature score == threshold - hidden
244 assertEquals(Color.white, fc.getColor(sf2));
245 fc.setThreshold(69f);
246 assertNull(fc.getColor(sf)); // feature score > threshold - hidden
247 assertEquals(Color.white, fc.getColor(sf2));
251 * Test output of feature colours to Jalview features file format
253 @Test(groups = { "Functional" })
254 public void testToJalviewFormat()
257 * plain colour - to RGB hex code
259 FeatureColour fc = new FeatureColour(Color.RED);
260 String redHex = Format.getHexString(Color.RED);
261 String hexColour = redHex;
262 assertEquals("domain\t" + hexColour, fc.toJalviewFormat("domain"));
265 * colour by label (no threshold)
267 fc = new FeatureColour();
268 fc.setColourByLabel(true);
269 assertEquals("domain\tlabel", fc.toJalviewFormat("domain"));
272 * colour by attribute text (no threshold)
274 fc = new FeatureColour();
275 fc.setColourByLabel(true);
276 fc.setAttributeName("CLIN_SIG");
277 assertEquals("domain\tattribute|CLIN_SIG", fc.toJalviewFormat("domain"));
280 * colour by label (autoscaled) (an odd state you can reach by selecting
281 * 'above threshold', then deselecting 'threshold is min/max' then 'colour
284 fc.setAttributeName((String[]) null);
285 fc.setAutoScaled(true);
286 assertEquals("domain\tlabel", fc.toJalviewFormat("domain"));
289 * colour by label (above threshold)
291 fc.setAutoScaled(false);
292 fc.setThreshold(12.5f);
293 fc.setAboveThreshold(true);
294 // min/max values are output though not used by this scheme
295 assertEquals("domain\tlabel|||0.0|0.0|above|12.5",
296 fc.toJalviewFormat("domain"));
299 * colour by label (below threshold)
301 fc.setBelowThreshold(true);
302 assertEquals("domain\tlabel|||0.0|0.0|below|12.5",
303 fc.toJalviewFormat("domain"));
306 * colour by attributes text (below threshold)
308 fc.setBelowThreshold(true);
309 fc.setAttributeName("CSQ", "Consequence");
310 assertEquals("domain\tattribute|CSQ:Consequence|||0.0|0.0|below|12.5",
311 fc.toJalviewFormat("domain"));
314 * graduated colour by score, no threshold
315 * - default constructor sets noValueColor = minColor
317 fc = new FeatureColour(null, Color.GREEN, Color.RED, Color.GREEN, 12f,
319 String greenHex = Format.getHexString(Color.GREEN);
320 String expected = String.format(
321 "domain\tscore|%s|%s|noValueMin|abso|12.0|25.0|none", greenHex,
323 assertEquals(expected, fc.toJalviewFormat("domain"));
326 * graduated colour by score, no threshold, no value gets min colour
328 fc = new FeatureColour(Color.RED, Color.GREEN, Color.RED, Color.GREEN,
330 expected = String.format(
331 "domain\tscore|%s|%s|noValueMin|abso|12.0|25.0|none", greenHex,
333 assertEquals(expected, fc.toJalviewFormat("domain"));
336 * graduated colour by score, no threshold, no value gets max colour
338 fc = new FeatureColour(Color.RED, Color.GREEN, Color.RED, Color.RED,
340 expected = String.format(
341 "domain\tscore|%s|%s|noValueMax|abso|12.0|25.0|none", greenHex,
343 assertEquals(expected, fc.toJalviewFormat("domain"));
346 * colour ranges over the actual score ranges (not min/max)
348 fc.setAutoScaled(true);
349 expected = String.format(
350 "domain\tscore|%s|%s|noValueMax|12.0|25.0|none", greenHex,
352 assertEquals(expected, fc.toJalviewFormat("domain"));
355 * graduated colour by score, below threshold
357 fc.setThreshold(12.5f);
358 fc.setBelowThreshold(true);
359 expected = String.format(
360 "domain\tscore|%s|%s|noValueMax|12.0|25.0|below|12.5",
362 assertEquals(expected, fc.toJalviewFormat("domain"));
365 * graduated colour by score, above threshold
367 fc.setThreshold(12.5f);
368 fc.setAboveThreshold(true);
369 fc.setAutoScaled(false);
370 expected = String.format(
371 "domain\tscore|%s|%s|noValueMax|abso|12.0|25.0|above|12.5",
373 assertEquals(expected, fc.toJalviewFormat("domain"));
376 * graduated colour by attribute, above threshold
378 fc.setAttributeName("CSQ", "AF");
379 fc.setAboveThreshold(true);
380 fc.setAutoScaled(false);
381 expected = String.format(
382 "domain\tattribute|CSQ:AF|%s|%s|noValueMax|abso|12.0|25.0|above|12.5",
384 assertEquals(expected, fc.toJalviewFormat("domain"));
388 * Test parsing of feature colours from Jalview features file format
390 @Test(groups = { "Functional" })
391 public void testParseJalviewFeatureColour()
394 * simple colour by name
396 FeatureColourI fc = FeatureColour.parseJalviewFeatureColour("red");
397 assertTrue(fc.isSimpleColour());
398 assertEquals(Color.RED, fc.getColour());
401 * simple colour by hex code
403 fc = FeatureColour.parseJalviewFeatureColour(Format
404 .getHexString(Color.RED));
405 assertTrue(fc.isSimpleColour());
406 assertEquals(Color.RED, fc.getColour());
409 * simple colour by rgb triplet
411 fc = FeatureColour.parseJalviewFeatureColour("255,0,0");
412 assertTrue(fc.isSimpleColour());
413 assertEquals(Color.RED, fc.getColour());
420 fc = FeatureColour.parseJalviewFeatureColour("oops");
421 fail("expected exception");
422 } catch (IllegalArgumentException e)
424 assertEquals("Invalid colour descriptor: oops", e.getMessage());
428 * colour by label (no threshold)
430 fc = FeatureColour.parseJalviewFeatureColour("label");
431 assertTrue(fc.isColourByLabel());
432 assertFalse(fc.hasThreshold());
435 * colour by label (with threshold)
438 .parseJalviewFeatureColour("label|||0.0|0.0|above|12.0");
439 assertTrue(fc.isColourByLabel());
440 assertTrue(fc.isAboveThreshold());
441 assertEquals(12.0f, fc.getThreshold());
444 * colour by attribute text (no threshold)
446 fc = FeatureColour.parseJalviewFeatureColour("attribute|CLIN_SIG");
447 assertTrue(fc.isColourByAttribute());
448 assertTrue(fc.isColourByLabel());
449 assertFalse(fc.hasThreshold());
450 assertArrayEquals(new String[] { "CLIN_SIG" }, fc.getAttributeName());
453 * colour by attributes text (with score threshold)
455 fc = FeatureColour.parseJalviewFeatureColour(
456 "attribute|CSQ:Consequence|||0.0|0.0|above|12.0");
457 assertTrue(fc.isColourByLabel());
458 assertTrue(fc.isColourByAttribute());
459 assertArrayEquals(new String[] { "CSQ", "Consequence" },
460 fc.getAttributeName());
461 assertTrue(fc.isAboveThreshold());
462 assertEquals(12.0f, fc.getThreshold());
465 * graduated colour by score (with colour names) (no threshold)
467 fc = FeatureColour.parseJalviewFeatureColour("red|green|10.0|20.0");
468 assertTrue(fc.isGraduatedColour());
469 assertFalse(fc.hasThreshold());
470 assertEquals(Color.RED, fc.getMinColour());
471 assertEquals(Color.GREEN, fc.getMaxColour());
472 assertEquals(10f, fc.getMin());
473 assertEquals(20f, fc.getMax());
474 assertTrue(fc.isAutoScaled());
477 * graduated colour (explicitly by 'score') (no threshold)
480 .parseJalviewFeatureColour("Score|red|green|10.0|20.0");
481 assertTrue(fc.isGraduatedColour());
482 assertFalse(fc.hasThreshold());
483 assertEquals(Color.RED, fc.getMinColour());
484 assertEquals(Color.GREEN, fc.getMaxColour());
485 assertEquals(10f, fc.getMin());
486 assertEquals(20f, fc.getMax());
487 assertTrue(fc.isAutoScaled());
490 * graduated colour by attribute (no threshold)
493 .parseJalviewFeatureColour("attribute|AF|red|green|10.0|20.0");
494 assertTrue(fc.isGraduatedColour());
495 assertTrue(fc.isColourByAttribute());
496 assertArrayEquals(new String[] { "AF" }, fc.getAttributeName());
497 assertFalse(fc.hasThreshold());
498 assertEquals(Color.RED, fc.getMinColour());
499 assertEquals(Color.GREEN, fc.getMaxColour());
500 assertEquals(10f, fc.getMin());
501 assertEquals(20f, fc.getMax());
502 assertTrue(fc.isAutoScaled());
505 * graduated colour by score (colours by hex code) (above threshold)
507 String descriptor = String.format("%s|%s|10.0|20.0|above|15",
508 Format.getHexString(Color.RED),
509 Format.getHexString(Color.GREEN));
510 fc = FeatureColour.parseJalviewFeatureColour(descriptor);
511 assertTrue(fc.isGraduatedColour());
512 assertTrue(fc.hasThreshold());
513 assertTrue(fc.isAboveThreshold());
514 assertEquals(15f, fc.getThreshold());
515 assertEquals(Color.RED, fc.getMinColour());
516 assertEquals(Color.GREEN, fc.getMaxColour());
517 assertEquals(10f, fc.getMin());
518 assertEquals(20f, fc.getMax());
519 assertTrue(fc.isAutoScaled());
522 * graduated colour by attributes (below threshold)
524 fc = FeatureColour.parseJalviewFeatureColour(
525 "attribute|CSQ:AF|red|green|10.0|20.0|below|13");
526 assertTrue(fc.isGraduatedColour());
527 assertTrue(fc.isColourByAttribute());
528 assertArrayEquals(new String[] { "CSQ", "AF" }, fc.getAttributeName());
529 assertTrue(fc.hasThreshold());
530 assertTrue(fc.isBelowThreshold());
531 assertEquals(13f, fc.getThreshold());
532 assertEquals(Color.RED, fc.getMinColour());
533 assertEquals(Color.GREEN, fc.getMaxColour());
534 assertEquals(10f, fc.getMin());
535 assertEquals(20f, fc.getMax());
536 assertTrue(fc.isAutoScaled());
539 * graduated colour (by RGB triplet) (below threshold), absolute scale
541 descriptor = "255,0,0|0,255,0|abso|10.0|20.0|below|15";
542 fc = FeatureColour.parseJalviewFeatureColour(descriptor);
543 assertTrue(fc.isGraduatedColour());
544 assertFalse(fc.isAutoScaled());
545 assertTrue(fc.hasThreshold());
546 assertTrue(fc.isBelowThreshold());
547 assertEquals(15f, fc.getThreshold());
548 assertEquals(Color.RED, fc.getMinColour());
549 assertEquals(Color.GREEN, fc.getMaxColour());
550 assertEquals(10f, fc.getMin());
551 assertEquals(20f, fc.getMax());
553 descriptor = "blue|255,0,255|absolute|20.0|95.0|below|66.0";
554 fc = FeatureColour.parseJalviewFeatureColour(descriptor);
555 assertTrue(fc.isGraduatedColour());
558 @Test(groups = { "Functional" })
559 public void testGetColor_colourByAttributeText()
561 FeatureColour fc = new FeatureColour();
562 fc.setColourByLabel(true);
563 fc.setAttributeName("consequence");
564 SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
568 * if feature has no such attribute, use 'no value' colour
570 assertEquals(FeatureColour.DEFAULT_NO_COLOUR, fc.getColor(sf));
573 * if feature has attribute, generate colour from value
575 sf.setValue("consequence", "benign");
576 Color expected = ColorUtils.createColourFromName("benign");
577 assertEquals(expected, fc.getColor(sf));
580 @Test(groups = { "Functional" })
581 public void testGetColor_GraduatedByAttributeValue()
584 * graduated colour based on attribute value for AF
585 * given a min-max range of 0-100
587 FeatureColour fc = new FeatureColour(Color.white,
588 new Color(50, 100, 150), new Color(150, 200, 250), Color.yellow,
590 String attName = "AF";
591 fc.setAttributeName(attName);
594 * first case: feature lacks the attribute - use 'no value' colour
596 SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
598 assertEquals(Color.yellow, fc.getColor(sf));
601 * second case: attribute present but not numeric - treat as if absent
603 sf.setValue(attName, "twelve");
604 assertEquals(Color.yellow, fc.getColor(sf));
607 * third case: valid attribute value
609 sf.setValue(attName, "20.0");
610 Color expected = new Color(70, 120, 170);
611 assertEquals(expected, fc.getColor(sf));