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