JAL-2835 test updated for colour by 'nested attribute'
[jalview.git] / test / jalview / schemes / FeatureColourTest.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.schemes;
22
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;
29
30 import jalview.datamodel.SequenceFeature;
31 import jalview.gui.JvOptionPane;
32 import jalview.util.ColorUtils;
33 import jalview.util.Format;
34
35 import java.awt.Color;
36
37 import org.testng.annotations.BeforeClass;
38 import org.testng.annotations.Test;
39
40 import junit.extensions.PA;
41
42 public class FeatureColourTest
43 {
44
45   @BeforeClass(alwaysRun = true)
46   public void setUpJvOptionPane()
47   {
48     JvOptionPane.setInteractiveMode(false);
49     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
50   }
51
52   @Test(groups = { "Functional" })
53   public void testCopyConstructor()
54   {
55     /*
56      * plain colour
57      */
58     FeatureColour fc = new FeatureColour(Color.RED);
59     FeatureColour fc1 = new FeatureColour(fc);
60     assertTrue(fc1.getColour().equals(Color.RED));
61     assertFalse(fc1.isGraduatedColour());
62     assertFalse(fc1.isColourByLabel());
63     assertFalse(fc1.isColourByAttribute());
64     assertNull(fc1.getAttributeName());
65
66     /*
67      * min-max colour
68      */
69     fc = new FeatureColour(Color.gray, Color.black, 10f, 20f);
70     fc.setAboveThreshold(true);
71     fc.setThreshold(12f);
72     fc1 = new FeatureColour(fc);
73     assertTrue(fc1.isGraduatedColour());
74     assertFalse(fc1.isColourByLabel());
75     assertTrue(fc1.isAboveThreshold());
76     assertFalse(fc1.isColourByAttribute());
77     assertNull(fc1.getAttributeName());
78     assertEquals(12f, fc1.getThreshold());
79     assertEquals(Color.gray, fc1.getMinColour());
80     assertEquals(Color.black, fc1.getMaxColour());
81     assertEquals(Color.gray, fc1.getNoColour());
82     assertEquals(10f, fc1.getMin());
83     assertEquals(20f, fc1.getMax());
84
85     /*
86      * min-max-noValue colour
87      */
88     fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
89     fc.setAboveThreshold(true);
90     fc.setThreshold(12f);
91     fc1 = new FeatureColour(fc);
92     assertTrue(fc1.isGraduatedColour());
93     assertFalse(fc1.isColourByLabel());
94     assertFalse(fc1.isColourByAttribute());
95     assertNull(fc1.getAttributeName());
96     assertTrue(fc1.isAboveThreshold());
97     assertEquals(12f, fc1.getThreshold());
98     assertEquals(Color.gray, fc1.getMinColour());
99     assertEquals(Color.black, fc1.getMaxColour());
100     assertEquals(Color.green, fc1.getNoColour());
101     assertEquals(10f, fc1.getMin());
102     assertEquals(20f, fc1.getMax());
103
104     /*
105      * colour by label
106      */
107     fc = new FeatureColour();
108     fc.setColourByLabel(true);
109     fc1 = new FeatureColour(fc);
110     assertTrue(fc1.isColourByLabel());
111     assertFalse(fc1.isGraduatedColour());
112     assertFalse(fc1.isColourByAttribute());
113     assertNull(fc1.getAttributeName());
114
115     /*
116      * colour by attribute (label)
117      */
118     fc = new FeatureColour();
119     fc.setColourByLabel(true);
120     fc.setAttributeName("AF");
121     fc1 = new FeatureColour(fc);
122     assertTrue(fc1.isColourByLabel());
123     assertFalse(fc1.isGraduatedColour());
124     assertTrue(fc1.isColourByAttribute());
125     assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName());
126
127     /*
128      * colour by attribute (value)
129      */
130     fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
131     fc.setAboveThreshold(true);
132     fc.setThreshold(12f);
133     fc.setAttributeName("AF");
134     fc1 = new FeatureColour(fc);
135     assertTrue(fc1.isGraduatedColour());
136     assertFalse(fc1.isColourByLabel());
137     assertTrue(fc1.isColourByAttribute());
138     assertArrayEquals(new String[] { "AF" }, fc1.getAttributeName());
139     assertTrue(fc1.isAboveThreshold());
140     assertEquals(12f, fc1.getThreshold());
141     assertEquals(Color.gray, fc1.getMinColour());
142     assertEquals(Color.black, fc1.getMaxColour());
143     assertEquals(Color.green, fc1.getNoColour());
144     assertEquals(10f, fc1.getMin());
145     assertEquals(20f, fc1.getMax());
146   }
147
148   @Test(groups = { "Functional" })
149   public void testCopyConstructor_minMax()
150   {
151     /*
152      * graduated colour
153      */
154     FeatureColour fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
155     assertTrue(fc.isGraduatedColour());
156     assertFalse(fc.isColourByLabel());
157     assertFalse(fc.isColourByAttribute());
158     assertNull(fc.getAttributeName());
159     assertEquals(1f, fc.getMin());
160     assertEquals(5f, fc.getMax());
161
162     /*
163      * update min-max bounds
164      */
165     FeatureColour fc1 = new FeatureColour(fc, 2f, 6f);
166     assertTrue(fc1.isGraduatedColour());
167     assertFalse(fc1.isColourByLabel());
168     assertFalse(fc1.isColourByAttribute());
169     assertNull(fc1.getAttributeName());
170     assertEquals(2f, fc1.getMin());
171     assertEquals(6f, fc1.getMax());
172     assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
173
174     /*
175      * update min-max bounds - high to low
176      */
177     fc1 = new FeatureColour(fc, 23f, 16f);
178     assertTrue(fc1.isGraduatedColour());
179     assertFalse(fc1.isColourByLabel());
180     assertFalse(fc1.isColourByAttribute());
181     assertNull(fc1.getAttributeName());
182     assertEquals(23f, fc1.getMin());
183     assertEquals(16f, fc1.getMax());
184     assertTrue((boolean) PA.getValue(fc1, "isHighToLow"));
185
186     /*
187      * graduated colour by attribute
188      */
189     fc1.setAttributeName("AF");
190     fc1 = new FeatureColour(fc1, 13f, 36f);
191     assertTrue(fc1.isGraduatedColour());
192     assertFalse(fc1.isColourByLabel());
193     assertTrue(fc1.isColourByAttribute());
194     assertEquals(new String[] { "AF" }, fc1.getAttributeName());
195     assertEquals(13f, fc1.getMin());
196     assertEquals(36f, fc1.getMax());
197     assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
198
199     /*
200      * colour by label
201      */
202     fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
203     fc.setColourByLabel(true);
204     assertFalse(fc.isGraduatedColour());
205     assertTrue(fc.isColourByLabel());
206     assertFalse(fc.isColourByAttribute());
207     assertNull(fc.getAttributeName());
208     assertEquals(1f, fc.getMin());
209     assertEquals(5f, fc.getMax());
210
211     /*
212      * update min-max bounds
213      */
214     fc1 = new FeatureColour(fc, 2f, 6f);
215     assertFalse(fc1.isGraduatedColour());
216     assertTrue(fc1.isColourByLabel());
217     assertFalse(fc1.isColourByAttribute());
218     assertNull(fc1.getAttributeName());
219     assertEquals(2f, fc1.getMin());
220     assertEquals(6f, fc1.getMax());
221
222     /*
223      * colour by attribute text
224      */
225     fc1.setAttributeName("AC");
226     fc1 = new FeatureColour(fc1, 13f, 36f);
227     assertFalse(fc1.isGraduatedColour());
228     assertTrue(fc1.isColourByLabel());
229     assertTrue(fc1.isColourByAttribute());
230     assertEquals("AC", fc1.getAttributeName());
231     assertEquals(13f, fc1.getMin());
232     assertEquals(36f, fc1.getMax());
233   }
234
235   @Test(groups = { "Functional" })
236   public void testGetColor_simpleColour()
237   {
238     FeatureColour fc = new FeatureColour(Color.RED);
239     assertEquals(Color.RED,
240             fc.getColor(new SequenceFeature("Cath", "", 1, 2, 0f, null)));
241   }
242
243   @Test(groups = { "Functional" })
244   public void testGetColor_colourByLabel()
245   {
246     FeatureColour fc = new FeatureColour();
247     fc.setColourByLabel(true);
248     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
249             null);
250     Color expected = ColorUtils.createColourFromName("desc");
251     assertEquals(expected, fc.getColor(sf));
252   }
253
254   @Test(groups = { "Functional" })
255   public void testGetColor_Graduated()
256   {
257     /*
258      * graduated colour from 
259      * score 0 to 100
260      * gray(128, 128, 128) to red(255, 0, 0)
261      */
262     FeatureColour fc = new FeatureColour(Color.GRAY, Color.RED, 0f, 100f);
263     // feature score is 75 which is 3/4 of the way from GRAY to RED
264     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
265             null);
266     // the colour gradient is computed in float values from 0-1 (where 1 == 255)
267     float red = 128 / 255f + 3 / 4f * (255 - 128) / 255f;
268     float green = 128 / 255f + 3 / 4f * (0 - 128) / 255f;
269     float blue = 128 / 255f + 3 / 4f * (0 - 128) / 255f;
270     Color expected = new Color(red, green, blue);
271     assertEquals(expected, fc.getColor(sf));
272   }
273
274   @Test(groups = { "Functional" })
275   public void testGetColor_aboveBelowThreshold()
276   {
277     // gradient from [50, 150] from WHITE(255, 255, 255) to BLACK(0, 0, 0)
278     FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 50f,
279             150f);
280     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 70f,
281             null);
282
283     /*
284      * feature with score of Float.NaN is always assigned minimum colour
285      */
286     SequenceFeature sf2 = new SequenceFeature("type", "desc", 0, 20,
287             Float.NaN, null);
288
289     fc.setThreshold(100f); // ignore for now
290     assertEquals(new Color(204, 204, 204), fc.getColor(sf));
291     assertEquals(Color.white, fc.getColor(sf2));
292
293     fc.setAboveThreshold(true); // feature lies below threshold
294     assertNull(fc.getColor(sf));
295     assertEquals(Color.white, fc.getColor(sf2));
296
297     fc.setBelowThreshold(true);
298     fc.setThreshold(70f);
299     assertNull(fc.getColor(sf)); // feature score == threshold - hidden
300     assertEquals(Color.white, fc.getColor(sf2));
301     fc.setThreshold(69f);
302     assertNull(fc.getColor(sf)); // feature score > threshold - hidden
303     assertEquals(Color.white, fc.getColor(sf2));
304   }
305
306   /**
307    * Test output of feature colours to Jalview features file format
308    */
309   @Test(groups = { "Functional" })
310   public void testToJalviewFormat()
311   {
312     /*
313      * plain colour - to RGB hex code
314      */
315     FeatureColour fc = new FeatureColour(Color.RED);
316     String redHex = Format.getHexString(Color.RED);
317     String hexColour = redHex;
318     assertEquals("domain\t" + hexColour, fc.toJalviewFormat("domain"));
319
320     /*
321      * colour by label (no threshold)
322      */
323     fc = new FeatureColour();
324     fc.setColourByLabel(true);
325     assertEquals("domain\tlabel", fc.toJalviewFormat("domain"));
326
327     /*
328      * colour by label (autoscaled) (an odd state you can reach by selecting
329      * 'above threshold', then deselecting 'threshold is min/max' then 'colour
330      * by label')
331      */
332     fc.setAutoScaled(true);
333     assertEquals("domain\tlabel", fc.toJalviewFormat("domain"));
334
335     /*
336      * colour by label (above threshold) (min/max values are output though not
337      * used by this scheme)
338      */
339     fc.setAutoScaled(false);
340     fc.setThreshold(12.5f);
341     fc.setAboveThreshold(true);
342     assertEquals("domain\tlabel|||0.0|0.0|above|12.5",
343             fc.toJalviewFormat("domain"));
344
345     /*
346      * colour by label (below threshold)
347      */
348     fc.setBelowThreshold(true);
349     assertEquals("domain\tlabel|||0.0|0.0|below|12.5",
350             fc.toJalviewFormat("domain"));
351
352     /*
353      * graduated colour, no threshold
354      */
355     fc = new FeatureColour(Color.GREEN, Color.RED, 12f, 25f);
356     String greenHex = Format.getHexString(Color.GREEN);
357     String expected = String.format("domain\t%s|%s|abso|12.0|25.0|none",
358             greenHex, redHex);
359     assertEquals(expected, fc.toJalviewFormat("domain"));
360
361     /*
362      * colour ranges over the actual score ranges (not min/max)
363      */
364     fc.setAutoScaled(true);
365     expected = String.format("domain\t%s|%s|12.0|25.0|none", greenHex,
366             redHex);
367     assertEquals(expected, fc.toJalviewFormat("domain"));
368
369     /*
370      * graduated colour below threshold
371      */
372     fc.setThreshold(12.5f);
373     fc.setBelowThreshold(true);
374     expected = String.format("domain\t%s|%s|12.0|25.0|below|12.5",
375             greenHex, redHex);
376     assertEquals(expected, fc.toJalviewFormat("domain"));
377
378     /*
379      * graduated colour above threshold
380      */
381     fc.setThreshold(12.5f);
382     fc.setAboveThreshold(true);
383     fc.setAutoScaled(false);
384     expected = String.format("domain\t%s|%s|abso|12.0|25.0|above|12.5",
385             greenHex, redHex);
386     assertEquals(expected, fc.toJalviewFormat("domain"));
387   }
388
389   /**
390    * Test parsing of feature colours from Jalview features file format
391    */
392   @Test(groups = { "Functional" })
393   public void testParseJalviewFeatureColour()
394   {
395     /*
396      * simple colour by name
397      */
398     FeatureColour fc = FeatureColour.parseJalviewFeatureColour("red");
399     assertTrue(fc.isSimpleColour());
400     assertEquals(Color.RED, fc.getColour());
401
402     /*
403      * simple colour by hex code
404      */
405     fc = FeatureColour.parseJalviewFeatureColour(Format
406             .getHexString(Color.RED));
407     assertTrue(fc.isSimpleColour());
408     assertEquals(Color.RED, fc.getColour());
409
410     /*
411      * simple colour by rgb triplet
412      */
413     fc = FeatureColour.parseJalviewFeatureColour("255,0,0");
414     assertTrue(fc.isSimpleColour());
415     assertEquals(Color.RED, fc.getColour());
416
417     /*
418      * malformed colour
419      */
420     try
421     {
422       fc = FeatureColour.parseJalviewFeatureColour("oops");
423       fail("expected exception");
424     } catch (IllegalArgumentException e)
425     {
426       assertEquals("Invalid colour descriptor: oops", e.getMessage());
427     }
428
429     /*
430      * colour by label (no threshold)
431      */
432     fc = FeatureColour.parseJalviewFeatureColour("label");
433     assertTrue(fc.isColourByLabel());
434     assertFalse(fc.hasThreshold());
435
436     /*
437      * colour by label (with threshold)
438      */
439     fc = FeatureColour
440             .parseJalviewFeatureColour("label|||0.0|0.0|above|12.0");
441     assertTrue(fc.isColourByLabel());
442     assertTrue(fc.isAboveThreshold());
443     assertEquals(12.0f, fc.getThreshold());
444
445     /*
446      * graduated colour (by name) (no threshold)
447      */
448     fc = FeatureColour.parseJalviewFeatureColour("red|green|10.0|20.0");
449     assertTrue(fc.isGraduatedColour());
450     assertFalse(fc.hasThreshold());
451     assertEquals(Color.RED, fc.getMinColour());
452     assertEquals(Color.GREEN, fc.getMaxColour());
453     assertEquals(10f, fc.getMin());
454     assertEquals(20f, fc.getMax());
455     assertTrue(fc.isAutoScaled());
456
457     /*
458      * graduated colour (by hex code) (above threshold)
459      */
460     String descriptor = String.format("%s|%s|10.0|20.0|above|15",
461             Format.getHexString(Color.RED),
462             Format.getHexString(Color.GREEN));
463     fc = FeatureColour.parseJalviewFeatureColour(descriptor);
464     assertTrue(fc.isGraduatedColour());
465     assertTrue(fc.hasThreshold());
466     assertTrue(fc.isAboveThreshold());
467     assertEquals(15f, fc.getThreshold());
468     assertEquals(Color.RED, fc.getMinColour());
469     assertEquals(Color.GREEN, fc.getMaxColour());
470     assertEquals(10f, fc.getMin());
471     assertEquals(20f, fc.getMax());
472     assertTrue(fc.isAutoScaled());
473
474     /*
475      * graduated colour (by RGB triplet) (below threshold), absolute scale
476      */
477     descriptor = String.format("255,0,0|0,255,0|abso|10.0|20.0|below|15");
478     fc = FeatureColour.parseJalviewFeatureColour(descriptor);
479     assertTrue(fc.isGraduatedColour());
480     assertFalse(fc.isAutoScaled());
481     assertTrue(fc.hasThreshold());
482     assertTrue(fc.isBelowThreshold());
483     assertEquals(15f, fc.getThreshold());
484     assertEquals(Color.RED, fc.getMinColour());
485     assertEquals(Color.GREEN, fc.getMaxColour());
486     assertEquals(10f, fc.getMin());
487     assertEquals(20f, fc.getMax());
488
489     descriptor = String
490             .format("blue|255,0,255|absolute|20.0|95.0|below|66.0");
491     fc = FeatureColour.parseJalviewFeatureColour(descriptor);
492     assertTrue(fc.isGraduatedColour());
493   }
494
495   @Test(groups = { "Functional" })
496   public void testGetColor_colourByAttributeText()
497   {
498     FeatureColour fc = new FeatureColour();
499     fc.setColourByLabel(true);
500     fc.setAttributeName("consequence");
501     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
502             null);
503
504     /*
505      * if feature has no such attribute, use 'no value' colour
506      */
507     assertEquals(FeatureColour.DEFAULT_NO_COLOUR, fc.getColor(sf));
508
509     /*
510      * if feature has attribute, generate colour from value
511      */
512     sf.setValue("consequence", "benign");
513     Color expected = ColorUtils.createColourFromName("benign");
514     assertEquals(expected, fc.getColor(sf));
515   }
516
517   @Test(groups = { "Functional" })
518   public void testGetColor_GraduatedByAttributeValue()
519   {
520     /*
521      * graduated colour based on attribute value for AF
522      * given a min-max range of 0-100
523      */
524     FeatureColour fc = new FeatureColour(new Color(50, 100, 150),
525             new Color(150, 200, 250), Color.yellow, 0f, 100f);
526     String attName = "AF";
527     fc.setAttributeName(attName);
528
529     /*
530      * first case: feature lacks the attribute - use 'no value' colour
531      */
532     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
533             null);
534     assertEquals(Color.yellow, fc.getColor(sf));
535
536     /*
537      * second case: attribute present but not numeric - treat as if absent
538      */
539     sf.setValue(attName, "twelve");
540     assertEquals(Color.yellow, fc.getColor(sf));
541
542     /*
543      * third case: valid attribute value
544      */
545     sf.setValue(attName, "20.0");
546     Color expected = new Color(70, 120, 170);
547     assertEquals(expected, fc.getColor(sf));
548   }
549 }