Merge branch 'develop' into update_212_Dec_merge_with_21125_chamges
[jalview.git] / test / jalview / renderer / seqfeatures / FeatureRendererTest.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.renderer.seqfeatures;
22
23 import static org.testng.Assert.assertEquals;
24 import static org.testng.Assert.assertFalse;
25 import static org.testng.Assert.assertNotNull;
26 import static org.testng.Assert.assertNull;
27 import static org.testng.Assert.assertSame;
28 import static org.testng.Assert.assertTrue;
29
30 import java.awt.Color;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36
37 import org.testng.annotations.AfterMethod;
38 import org.testng.annotations.Test;
39
40 import jalview.analysis.GeneticCodes;
41 import jalview.api.AlignViewportI;
42 import jalview.api.FeatureColourI;
43 import jalview.bin.Jalview;
44 import jalview.datamodel.MappedFeatures;
45 import jalview.datamodel.SequenceFeature;
46 import jalview.datamodel.SequenceI;
47 import jalview.datamodel.features.FeatureMatcher;
48 import jalview.datamodel.features.FeatureMatcherSet;
49 import jalview.datamodel.features.FeatureMatcherSetI;
50 import jalview.gui.AlignFrame;
51 import jalview.gui.AlignViewport;
52 import jalview.gui.Desktop;
53 import jalview.io.DataSourceType;
54 import jalview.io.FileLoader;
55 import jalview.schemes.FeatureColour;
56 import jalview.util.matcher.Condition;
57 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
58
59 public class FeatureRendererTest
60 {
61   @AfterMethod(alwaysRun = true)
62   public void tearDown()
63   {
64     Desktop.getInstance().closeAll_actionPerformed(null);
65   }
66
67   @Test(groups = "Functional")
68   public void testFindAllFeatures()
69   {
70     String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n";
71     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
72             DataSourceType.PASTE);
73     AlignViewportI av = af.getViewport();
74     FeatureRenderer fr = new FeatureRenderer(av);
75
76     /*
77      * with no features
78      */
79     fr.findAllFeatures(true);
80     assertTrue(fr.getRenderOrder().isEmpty());
81     assertTrue(fr.getFeatureGroups().isEmpty());
82
83     List<SequenceI> seqs = av.getAlignment().getSequences();
84
85     // add a non-positional feature - should be ignored by FeatureRenderer
86     SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
87             "Group");
88     seqs.get(0).addSequenceFeature(sf1);
89     fr.findAllFeatures(true);
90     // ? bug - types and groups added for non-positional features
91     List<String> types = fr.getRenderOrder();
92     List<String> groups = fr.getFeatureGroups();
93     assertEquals(types.size(), 0);
94     assertFalse(types.contains("Type"));
95     assertEquals(groups.size(), 0);
96     assertFalse(groups.contains("Group"));
97
98     // add some positional features
99     seqs.get(1).addSequenceFeature(
100             new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup"));
101     seqs.get(2).addSequenceFeature(
102             new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup"));
103     // bug in findAllFeatures - group not checked for a known feature type
104     seqs.get(2).addSequenceFeature(new SequenceFeature("Rfam", "Desc", 5, 9,
105             Float.NaN, "RfamGroup"));
106     // existing feature type with null group
107     seqs.get(3).addSequenceFeature(
108             new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
109     // new feature type with null group
110     seqs.get(3).addSequenceFeature(
111             new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null));
112     // null value for type produces NullPointerException
113     fr.findAllFeatures(true);
114     types = fr.getRenderOrder();
115     groups = fr.getFeatureGroups();
116     assertEquals(types.size(), 3);
117     assertFalse(types.contains("Type"));
118     assertTrue(types.contains("Pfam"));
119     assertTrue(types.contains("Rfam"));
120     assertTrue(types.contains("Scop"));
121     assertEquals(groups.size(), 2);
122     assertFalse(groups.contains("Group"));
123     assertTrue(groups.contains("PfamGroup"));
124     assertTrue(groups.contains("RfamGroup"));
125     assertFalse(groups.contains(null)); // null group is ignored
126
127     /*
128      * check min-max values
129      */
130     Map<String, float[][]> minMax = fr.getMinMax();
131     assertEquals(minMax.size(), 1); // non-positional and NaN not stored
132     assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min
133     assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max
134
135     // increase max for Pfam, add scores for Rfam
136     seqs.get(0).addSequenceFeature(
137             new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup"));
138     seqs.get(1).addSequenceFeature(
139             new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup"));
140     fr.findAllFeatures(true);
141     // note minMax is not a defensive copy, shouldn't expose this
142     assertEquals(minMax.size(), 2);
143     assertEquals(minMax.get("Pfam")[0][0], 1f);
144     assertEquals(minMax.get("Pfam")[0][1], 8f);
145     assertEquals(minMax.get("Rfam")[0][0], 6f);
146     assertEquals(minMax.get("Rfam")[0][1], 6f);
147
148     /*
149      * check render order (last is on top)
150      */
151     List<String> renderOrder = fr.getRenderOrder();
152     assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
153
154     /*
155      * change render order (todo: an easier way)
156      * nb here last comes first in the data array
157      */
158     FeatureSettingsBean[] data = new FeatureSettingsBean[3];
159     FeatureColourI colour = new FeatureColour(Color.RED);
160     data[0] = new FeatureSettingsBean("Rfam", colour, null, true);
161     data[1] = new FeatureSettingsBean("Pfam", colour, null, false);
162     data[2] = new FeatureSettingsBean("Scop", colour, null, false);
163     fr.setFeaturePriority(data);
164     assertEquals(fr.getRenderOrder(),
165             Arrays.asList("Scop", "Pfam", "Rfam"));
166     assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam"));
167
168     /*
169      * add a new feature type: should go on top of render order as visible,
170      * other feature ordering and visibility should be unchanged
171      */
172     seqs.get(2).addSequenceFeature(
173             new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup"));
174     fr.findAllFeatures(true);
175     assertEquals(fr.getRenderOrder(),
176             Arrays.asList("Scop", "Pfam", "Rfam", "Metal"));
177     assertEquals(fr.getDisplayedFeatureTypes(),
178             Arrays.asList("Rfam", "Metal"));
179   }
180
181   @Test(groups = "Functional")
182   public void testFindFeaturesAtColumn()
183   {
184     String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
185     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
186             DataSourceType.PASTE);
187     AlignViewportI av = af.getViewport();
188     FeatureRenderer fr = new FeatureRenderer(av);
189     SequenceI seq = av.getAlignment().getSequenceAt(0);
190
191     /*
192      * with no features
193      */
194     List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
195     assertTrue(features.isEmpty());
196
197     /*
198      * add features
199      */
200     SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
201             "Group"); // non-positional
202     seq.addSequenceFeature(sf1);
203     SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
204             "Group1");
205     seq.addSequenceFeature(sf2);
206     SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
207             "Group2");
208     seq.addSequenceFeature(sf3);
209     SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
210             null); // null group is always treated as visible
211     seq.addSequenceFeature(sf4);
212
213     /*
214      * add contact features
215      */
216     SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
217             15, 1f, "Group1");
218     seq.addSequenceFeature(sf5);
219     SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
220             15, 1f, "Group2");
221     seq.addSequenceFeature(sf6);
222     SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
223             15, 1f, null);
224     seq.addSequenceFeature(sf7);
225
226     // feature spanning B--C
227     SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
228             "Group");
229     seq.addSequenceFeature(sf8);
230     // contact feature B/C
231     SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
232             6, 1f, "Group");
233     seq.addSequenceFeature(sf9);
234
235     /*
236      * let feature renderer discover features (and make visible)
237      */
238     fr.findAllFeatures(true);
239     features = fr.findFeaturesAtColumn(seq, 15); // all positional
240     assertEquals(features.size(), 6);
241     assertTrue(features.contains(sf2));
242     assertTrue(features.contains(sf3));
243     assertTrue(features.contains(sf4));
244     assertTrue(features.contains(sf5));
245     assertTrue(features.contains(sf6));
246     assertTrue(features.contains(sf7));
247
248     /*
249      * at a non-contact position
250      */
251     features = fr.findFeaturesAtColumn(seq, 14);
252     assertEquals(features.size(), 3);
253     assertTrue(features.contains(sf2));
254     assertTrue(features.contains(sf3));
255     assertTrue(features.contains(sf4));
256
257     /*
258      * make "Type2" not displayed
259      */
260     FeatureColourI colour = new FeatureColour(Color.RED);
261     FeatureSettingsBean[] data = new FeatureSettingsBean[4];
262     data[0] = new FeatureSettingsBean("Type1", colour, null, true);
263     data[1] = new FeatureSettingsBean("Type2", colour, null, false);
264     data[2] = new FeatureSettingsBean("Type3", colour, null, true);
265     data[3] = new FeatureSettingsBean("Disulphide Bond", colour, null,
266             true);
267     fr.setFeaturePriority(data);
268
269     features = fr.findFeaturesAtColumn(seq, 15);
270     assertEquals(features.size(), 5); // no sf2
271     assertTrue(features.contains(sf3));
272     assertTrue(features.contains(sf4));
273     assertTrue(features.contains(sf5));
274     assertTrue(features.contains(sf6));
275     assertTrue(features.contains(sf7));
276
277     /*
278      * make "Group2" not displayed
279      */
280     fr.setGroupVisibility("Group2", false);
281
282     features = fr.findFeaturesAtColumn(seq, 15);
283     assertEquals(features.size(), 3); // no sf2, sf3, sf6
284     assertTrue(features.contains(sf4));
285     assertTrue(features.contains(sf5));
286     assertTrue(features.contains(sf7));
287
288     // features 'at' a gap between b and c
289     // - returns enclosing feature BC but not contact feature B/C
290     features = fr.findFeaturesAtColumn(seq, 4);
291     assertEquals(features.size(), 1);
292     assertTrue(features.contains(sf8));
293     features = fr.findFeaturesAtColumn(seq, 5);
294     assertEquals(features.size(), 1);
295     assertTrue(features.contains(sf8));
296
297     /*
298      * give "Type3" features a graduated colour scheme
299      * - first with no threshold
300      */
301     FeatureColourI gc = new FeatureColour(Color.green, Color.yellow,
302             Color.red, null, 0f, 10f);
303     fr.getFeatureColours().put("Type3", gc);
304     features = fr.findFeaturesAtColumn(seq, 8);
305     assertTrue(features.contains(sf4));
306     // now with threshold > 2f - feature score of 1f is excluded
307     gc.setAboveThreshold(true);
308     gc.setThreshold(2f);
309     features = fr.findFeaturesAtColumn(seq, 8);
310     assertFalse(features.contains(sf4));
311
312     /*
313      * make "Type3" graduated colour by attribute "AF"
314      * - first with no attribute held - feature should be excluded
315      */
316     gc.setAttributeName("AF");
317     features = fr.findFeaturesAtColumn(seq, 8);
318     assertFalse(features.contains(sf4));
319     // now with the attribute above threshold - should be included
320     sf4.setValue("AF", "2.4");
321     features = fr.findFeaturesAtColumn(seq, 8);
322     assertTrue(features.contains(sf4));
323     // now with the attribute below threshold - should be excluded
324     sf4.setValue("AF", "1.4");
325     features = fr.findFeaturesAtColumn(seq, 8);
326     assertFalse(features.contains(sf4));
327   }
328
329   @Test(groups = "Functional")
330   public void testFilterFeaturesForDisplay()
331   {
332     String seqData = ">s1\nabcdef\n";
333     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
334             DataSourceType.PASTE);
335     AlignViewportI av = af.getViewport();
336     FeatureRenderer fr = new FeatureRenderer(av);
337
338     List<SequenceFeature> features = new ArrayList<>();
339     fr.filterFeaturesForDisplay(features); // empty list, does nothing
340
341     SequenceI seq = av.getAlignment().getSequenceAt(0);
342     SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
343             "group1");
344     SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
345             "group2");
346     SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
347             "group3");
348     SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
349             "group4");
350     SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
351             "group4");
352     seq.addSequenceFeature(sf1);
353     seq.addSequenceFeature(sf2);
354     seq.addSequenceFeature(sf3);
355     seq.addSequenceFeature(sf4);
356     seq.addSequenceFeature(sf5);
357
358     fr.findAllFeatures(true);
359
360     features = seq.getSequenceFeatures();
361     assertEquals(features.size(), 5);
362     assertTrue(features.contains(sf1));
363     assertTrue(features.contains(sf2));
364     assertTrue(features.contains(sf3));
365     assertTrue(features.contains(sf4));
366     assertTrue(features.contains(sf5));
367
368     /*
369      * filter out duplicate (co-located) features
370      * note: which gets removed is not guaranteed
371      */
372     fr.filterFeaturesForDisplay(features);
373     assertEquals(features.size(), 3);
374     assertTrue(features.contains(sf1) || features.contains(sf4));
375     assertFalse(features.contains(sf1) && features.contains(sf4));
376     assertTrue(features.contains(sf2) || features.contains(sf3));
377     assertFalse(features.contains(sf2) && features.contains(sf3));
378     assertTrue(features.contains(sf5));
379
380     /*
381      * features in hidden groups are removed
382      */
383     fr.setGroupVisibility("group2", false);
384     fr.setGroupVisibility("group3", false);
385     features = seq.getSequenceFeatures();
386     fr.filterFeaturesForDisplay(features);
387     assertEquals(features.size(), 2);
388     assertTrue(features.contains(sf1) || features.contains(sf4));
389     assertFalse(features.contains(sf1) && features.contains(sf4));
390     assertFalse(features.contains(sf2));
391     assertFalse(features.contains(sf3));
392     assertTrue(features.contains(sf5));
393
394     /*
395      * no filtering if transparency is applied
396      */
397     fr.setTransparency(0.5f);
398     features = seq.getSequenceFeatures();
399     fr.filterFeaturesForDisplay(features);
400     assertEquals(features.size(), 5);
401   }
402
403   @Test(groups = "Functional")
404   public void testGetColour()
405   {
406     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n",
407             DataSourceType.PASTE);
408     AlignViewportI av = af.getViewport();
409     FeatureRenderer fr = new FeatureRenderer(av);
410
411     /*
412      * simple colour, feature type and group displayed
413      */
414     FeatureColourI fc = new FeatureColour(Color.red);
415     fr.getFeatureColours().put("Cath", fc);
416     SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
417             "group1");
418     assertEquals(fr.getColour(sf1), Color.red);
419
420     /*
421      * hide feature type, then unhide
422      * - feature type visibility should not affect the result
423      */
424     FeatureSettingsBean[] data = new FeatureSettingsBean[1];
425     data[0] = new FeatureSettingsBean("Cath", fc, null, false);
426     fr.setFeaturePriority(data);
427     assertEquals(fr.getColour(sf1), Color.red);
428     data[0] = new FeatureSettingsBean("Cath", fc, null, true);
429     fr.setFeaturePriority(data);
430     assertEquals(fr.getColour(sf1), Color.red);
431
432     /*
433      * hide feature group, then unhide
434      */
435     fr.setGroupVisibility("group1", false);
436     assertNull(fr.getColour(sf1));
437     fr.setGroupVisibility("group1", true);
438     assertEquals(fr.getColour(sf1), Color.red);
439
440     /*
441      * graduated colour by score, no threshold, no score
442      * 
443      */
444     FeatureColourI gc = new FeatureColour(Color.red, Color.yellow,
445             Color.red, Color.green, 1f, 11f);
446     fr.getFeatureColours().put("Cath", gc);
447     assertEquals(fr.getColour(sf1), Color.green);
448
449     /*
450      * graduated colour by score, no threshold, with score value
451      */
452     SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f,
453             "group1");
454     // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0)
455     Color expected = new Color(255, 128, 0);
456     assertEquals(fr.getColour(sf2), expected);
457
458     /*
459      * above threshold, score is above threshold - no change
460      */
461     gc.setAboveThreshold(true);
462     gc.setThreshold(5f);
463     assertEquals(fr.getColour(sf2), expected);
464
465     /*
466      * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11
467      * or from yellow(255, 255, 0) to red(255, 0, 0)
468      */
469     gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green,
470             5f, 11f);
471     fr.getFeatureColours().put("Cath", gc);
472     gc.setAutoScaled(false); // this does little other than save a checkbox
473                              // setting!
474     assertEquals(fr.getColour(sf2), new Color(255, 213, 0));
475
476     /*
477      * feature score is below threshold - no colour
478      */
479     gc.setAboveThreshold(true);
480     gc.setThreshold(7f);
481     assertNull(fr.getColour(sf2));
482
483     /*
484      * feature score is above threshold - no colour
485      */
486     gc.setBelowThreshold(true);
487     gc.setThreshold(3f);
488     assertNull(fr.getColour(sf2));
489
490     /*
491      * colour by feature attribute value
492      * first with no value held
493      */
494     gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green,
495             1f, 11f);
496     fr.getFeatureColours().put("Cath", gc);
497     gc.setAttributeName("AF");
498     assertEquals(fr.getColour(sf2), Color.green);
499
500     // with non-numeric attribute value
501     sf2.setValue("AF", "Five");
502     assertEquals(fr.getColour(sf2), Color.green);
503
504     // with numeric attribute value
505     sf2.setValue("AF", "6");
506     assertEquals(fr.getColour(sf2), expected);
507
508     // with numeric value outwith threshold
509     gc.setAboveThreshold(true);
510     gc.setThreshold(10f);
511     assertNull(fr.getColour(sf2));
512
513     // with filter on AF < 4
514     gc.setAboveThreshold(false);
515     assertEquals(fr.getColour(sf2), expected);
516     FeatureMatcherSetI filter = new FeatureMatcherSet();
517     filter.and(FeatureMatcher.byAttribute(Condition.LT, "4.0", "AF"));
518     fr.setFeatureFilter("Cath", filter);
519     assertNull(fr.getColour(sf2));
520
521     // with filter on 'Consequence contains missense'
522     filter = new FeatureMatcherSet();
523     filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense",
524             "Consequence"));
525     fr.setFeatureFilter("Cath", filter);
526     // if feature has no Consequence attribute, no colour
527     assertNull(fr.getColour(sf2));
528     // if attribute does not match filter, no colour
529     sf2.setValue("Consequence", "Synonymous");
530     assertNull(fr.getColour(sf2));
531     // attribute matches filter
532     sf2.setValue("Consequence", "Missense variant");
533     assertEquals(fr.getColour(sf2), expected);
534
535     // with filter on CSQ:Feature contains "ENST01234"
536     filter = new FeatureMatcherSet();
537     filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234",
538             "CSQ", "Feature"));
539     fr.setFeatureFilter("Cath", filter);
540     // if feature has no CSQ data, no colour
541     assertNull(fr.getColour(sf2));
542     // if CSQ data does not include Feature, no colour
543     Map<String, String> csqData = new HashMap<>();
544     csqData.put("BIOTYPE", "Transcript");
545     sf2.setValue("CSQ", csqData);
546     assertNull(fr.getColour(sf2));
547     // if attribute does not match filter, no colour
548     csqData.put("Feature", "ENST9876");
549     assertNull(fr.getColour(sf2));
550     // attribute matches filter
551     csqData.put("Feature", "ENST01234");
552     assertEquals(fr.getColour(sf2), expected);
553   }
554
555   @Test(groups = "Functional")
556   public void testIsVisible()
557   {
558     String seqData = ">s1\nMLQGIFPRS\n";
559     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
560             DataSourceType.PASTE);
561     AlignViewportI av = af.getViewport();
562     FeatureRenderer fr = new FeatureRenderer(av);
563     SequenceI seq = av.getAlignment().getSequenceAt(0);
564     SequenceFeature sf = new SequenceFeature("METAL", "Desc", 10, 10, 1f,
565             "Group");
566     sf.setValue("AC", "11");
567     sf.setValue("CLIN_SIG", "Likely Pathogenic");
568     seq.addSequenceFeature(sf);
569
570     assertFalse(fr.isVisible(null));
571
572     /*
573      * initial state FeatureRenderer hasn't 'found' feature
574      * and so its feature type has not yet been set visible
575      */
576     assertFalse(fr.getDisplayedFeatureCols().containsKey("METAL"));
577     assertFalse(fr.isVisible(sf));
578
579     fr.findAllFeatures(true);
580     assertTrue(fr.isVisible(sf));
581
582     /*
583      * feature group not visible
584      */
585     fr.setGroupVisibility("Group", false);
586     assertFalse(fr.isVisible(sf));
587     fr.setGroupVisibility("Group", true);
588     assertTrue(fr.isVisible(sf));
589
590     /*
591      * feature score outwith colour threshold (score > 2)
592      */
593     FeatureColourI fc = new FeatureColour(null, Color.white, Color.black,
594             Color.white, 0, 10);
595     fc.setAboveThreshold(true);
596     fc.setThreshold(2f);
597     fr.setColour("METAL", fc);
598     assertFalse(fr.isVisible(sf)); // score 1 is not above threshold 2
599     fc.setBelowThreshold(true);
600     assertTrue(fr.isVisible(sf)); // score 1 is below threshold 2
601
602     /*
603      * colour with threshold on attribute AC (value is 11)
604      */
605     fc.setAttributeName("AC");
606     assertFalse(fr.isVisible(sf)); // value 11 is not below threshold 2
607     fc.setAboveThreshold(true);
608     assertTrue(fr.isVisible(sf)); // value 11 is above threshold 2
609
610     fc.setAttributeName("AF"); // attribute AF is absent in sf
611     assertTrue(fr.isVisible(sf)); // feature is not excluded by threshold
612
613     FeatureMatcherSetI filter = new FeatureMatcherSet();
614     filter.and(FeatureMatcher.byAttribute(Condition.Contains, "pathogenic",
615             "CLIN_SIG"));
616     fr.setFeatureFilter("METAL", filter);
617     assertTrue(fr.isVisible(sf)); // feature matches filter
618     filter.and(FeatureMatcher.byScore(Condition.LE, "0.4"));
619     assertFalse(fr.isVisible(sf)); // feature doesn't match filter
620   }
621
622   @Test(groups = "Functional")
623   public void testFindComplementFeaturesAtResidue()
624   {
625     Jalview.main(
626             new String[]
627             { "-nonews", "-props", "test/jalview/testProps.jvprops" });
628
629     // codons for MCWHSE
630     String cdsSeq = ">cds\nATGtgtTGGcacTCAgaa";
631     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(cdsSeq,
632             DataSourceType.PASTE);
633     af.showTranslation_actionPerformed(
634             GeneticCodes.getInstance().getStandardCodeTable());
635     af.closeMenuItem_actionPerformed(true);
636
637     /*
638      * find the complement frames (ugly)
639      */
640     AlignFrame[] frames = Desktop.getAlignFrames();
641     assertEquals(frames.length, 2);
642     AlignViewport av1 = frames[0].getViewport();
643     AlignViewport av2 = frames[1].getViewport();
644     AlignViewport cds = av1.getAlignment().isNucleotide() ? av1 : av2;
645     AlignViewport peptide = cds == av1 ? av2 : av1;
646     assertNotNull(cds);
647     assertNotNull(peptide);
648
649     /*
650      * add features to CDS at first codon, positions 2-3
651      */
652     SequenceI seq1 = cds.getAlignment().getSequenceAt(0);
653     SequenceFeature sf1 = new SequenceFeature("sequence_variant", "G,GT", 2,
654             2, "ensembl");
655     seq1.addSequenceFeature(sf1);
656     SequenceFeature sf2 = new SequenceFeature("sequence_variant", "C, CA",
657             3, 3, "ensembl");
658     seq1.addSequenceFeature(sf2);
659
660     /*
661      * 'find' mapped features from the peptide position
662      * - first with CDS features _not_ shown on peptide alignment
663      */
664     SequenceI seq2 = peptide.getAlignment().getSequenceAt(0);
665     FeatureRenderer frC = new FeatureRenderer(cds);
666     frC.featuresAdded();
667     MappedFeatures mf = frC.findComplementFeaturesAtResidue(seq2, 1);
668     assertNotNull(mf);
669     assertEquals(mf.features.size(), 2);
670     assertSame(mf.features.get(0), sf1);
671     assertSame(mf.features.get(1), sf2);
672
673     /*
674      * add exon feature and verify it is only returned once for a
675      * peptide position, even though it is on all 3 codon positions
676      */
677     SequenceFeature sf3 = new SequenceFeature("exon", "exon1", 4, 12,
678             "ensembl");
679     seq1.addSequenceFeature(sf3);
680     frC.featuresAdded();
681     mf = frC.findComplementFeaturesAtResidue(seq2, 3);
682     assertNotNull(mf);
683     assertEquals(mf.features.size(), 1);
684     assertSame(mf.features.get(0), sf3);
685   }
686 }