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