2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.renderer.seqfeatures;
23 import static org.testng.Assert.assertEquals;
24 import static org.testng.Assert.assertFalse;
25 import static org.testng.Assert.assertTrue;
27 import jalview.api.AlignViewportI;
28 import jalview.api.FeatureColourI;
29 import jalview.datamodel.SequenceFeature;
30 import jalview.datamodel.SequenceI;
31 import jalview.gui.AlignFrame;
32 import jalview.io.DataSourceType;
33 import jalview.io.FileLoader;
34 import jalview.schemes.FeatureColour;
36 import java.awt.Color;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.List;
42 import org.testng.annotations.Test;
44 public class FeatureRendererTest
47 @Test(groups = "Functional")
48 public void testFindAllFeatures()
50 String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n";
51 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
52 DataSourceType.PASTE);
53 AlignViewportI av = af.getViewport();
54 FeatureRenderer fr = new FeatureRenderer(av);
59 fr.findAllFeatures(true);
60 assertTrue(fr.getRenderOrder().isEmpty());
61 assertTrue(fr.getFeatureGroups().isEmpty());
63 List<SequenceI> seqs = av.getAlignment().getSequences();
65 // add a non-positional feature - should be ignored by FeatureRenderer
66 SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
68 seqs.get(0).addSequenceFeature(sf1);
69 fr.findAllFeatures(true);
70 // ? bug - types and groups added for non-positional features
71 List<String> types = fr.getRenderOrder();
72 List<String> groups = fr.getFeatureGroups();
73 assertEquals(types.size(), 0);
74 assertFalse(types.contains("Type"));
75 assertEquals(groups.size(), 0);
76 assertFalse(groups.contains("Group"));
78 // add some positional features
79 seqs.get(1).addSequenceFeature(
80 new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup"));
81 seqs.get(2).addSequenceFeature(
82 new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup"));
83 // bug in findAllFeatures - group not checked for a known feature type
84 seqs.get(2).addSequenceFeature(
85 new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN,
87 // existing feature type with null group
88 seqs.get(3).addSequenceFeature(
89 new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
90 // new feature type with null group
91 seqs.get(3).addSequenceFeature(
92 new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null));
93 // null value for type produces NullPointerException
94 fr.findAllFeatures(true);
95 types = fr.getRenderOrder();
96 groups = fr.getFeatureGroups();
97 assertEquals(types.size(), 3);
98 assertFalse(types.contains("Type"));
99 assertTrue(types.contains("Pfam"));
100 assertTrue(types.contains("Rfam"));
101 assertTrue(types.contains("Scop"));
102 assertEquals(groups.size(), 2);
103 assertFalse(groups.contains("Group"));
104 assertTrue(groups.contains("PfamGroup"));
105 assertTrue(groups.contains("RfamGroup"));
106 assertFalse(groups.contains(null)); // null group is ignored
109 * check min-max values
111 Map<String, float[][]> minMax = fr.getMinMax();
112 assertEquals(minMax.size(), 1); // non-positional and NaN not stored
113 assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min
114 assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max
116 // increase max for Pfam, add scores for Rfam
117 seqs.get(0).addSequenceFeature(
118 new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup"));
119 seqs.get(1).addSequenceFeature(
120 new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup"));
121 fr.findAllFeatures(true);
122 // note minMax is not a defensive copy, shouldn't expose this
123 assertEquals(minMax.size(), 2);
124 assertEquals(minMax.get("Pfam")[0][0], 1f);
125 assertEquals(minMax.get("Pfam")[0][1], 8f);
126 assertEquals(minMax.get("Rfam")[0][0], 6f);
127 assertEquals(minMax.get("Rfam")[0][1], 6f);
130 * check render order (last is on top)
132 List<String> renderOrder = fr.getRenderOrder();
133 assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
136 * change render order (todo: an easier way)
137 * nb here last comes first in the data array
139 Object[][] data = new Object[3][];
140 FeatureColourI colour = new FeatureColour(Color.RED);
141 data[0] = new Object[] { "Rfam", colour, true };
142 data[1] = new Object[] { "Pfam", colour, false };
143 data[2] = new Object[] { "Scop", colour, false };
144 fr.setFeaturePriority(data);
145 assertEquals(fr.getRenderOrder(), Arrays.asList("Scop", "Pfam", "Rfam"));
146 assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam"));
149 * add a new feature type: should go on top of render order as visible,
150 * other feature ordering and visibility should be unchanged
152 seqs.get(2).addSequenceFeature(
153 new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup"));
154 fr.findAllFeatures(true);
155 assertEquals(fr.getRenderOrder(),
156 Arrays.asList("Scop", "Pfam", "Rfam", "Metal"));
157 assertEquals(fr.getDisplayedFeatureTypes(),
158 Arrays.asList("Rfam", "Metal"));
161 @Test(groups = "Functional")
162 public void testFindFeaturesAtColumn()
164 String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
165 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
166 DataSourceType.PASTE);
167 AlignViewportI av = af.getViewport();
168 FeatureRenderer fr = new FeatureRenderer(av);
169 SequenceI seq = av.getAlignment().getSequenceAt(0);
174 List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
175 assertTrue(features.isEmpty());
180 SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
181 "Group"); // non-positional
182 seq.addSequenceFeature(sf1);
183 SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
185 seq.addSequenceFeature(sf2);
186 SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
188 seq.addSequenceFeature(sf3);
189 SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
190 null); // null group is always treated as visible
191 seq.addSequenceFeature(sf4);
194 * add contact features
196 SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
198 seq.addSequenceFeature(sf5);
199 SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
201 seq.addSequenceFeature(sf6);
202 SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
204 seq.addSequenceFeature(sf7);
206 // feature spanning B--C
207 SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
209 seq.addSequenceFeature(sf8);
210 // contact feature B/C
211 SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
213 seq.addSequenceFeature(sf9);
216 * let feature renderer discover features (and make visible)
218 fr.findAllFeatures(true);
219 features = fr.findFeaturesAtColumn(seq, 15); // all positional
220 assertEquals(features.size(), 6);
221 assertTrue(features.contains(sf2));
222 assertTrue(features.contains(sf3));
223 assertTrue(features.contains(sf4));
224 assertTrue(features.contains(sf5));
225 assertTrue(features.contains(sf6));
226 assertTrue(features.contains(sf7));
229 * at a non-contact position
231 features = fr.findFeaturesAtColumn(seq, 14);
232 assertEquals(features.size(), 3);
233 assertTrue(features.contains(sf2));
234 assertTrue(features.contains(sf3));
235 assertTrue(features.contains(sf4));
238 * make "Type2" not displayed
240 Object[][] data = new Object[4][];
241 FeatureColourI colour = new FeatureColour(Color.RED);
242 data[0] = new Object[] { "Type1", colour, true };
243 data[1] = new Object[] { "Type2", colour, false };
244 data[2] = new Object[] { "Type3", colour, true };
245 data[3] = new Object[] { "Disulphide Bond", colour, true };
246 fr.setFeaturePriority(data);
248 features = fr.findFeaturesAtColumn(seq, 15);
249 assertEquals(features.size(), 5); // no sf2
250 assertTrue(features.contains(sf3));
251 assertTrue(features.contains(sf4));
252 assertTrue(features.contains(sf5));
253 assertTrue(features.contains(sf6));
254 assertTrue(features.contains(sf7));
257 * make "Group2" not displayed
259 fr.setGroupVisibility("Group2", false);
261 features = fr.findFeaturesAtColumn(seq, 15);
262 assertEquals(features.size(), 3); // no sf2, sf3, sf6
263 assertTrue(features.contains(sf4));
264 assertTrue(features.contains(sf5));
265 assertTrue(features.contains(sf7));
267 // features 'at' a gap between b and c
268 // - returns enclosing feature BC but not contact feature B/C
269 features = fr.findFeaturesAtColumn(seq, 4);
270 assertEquals(features.size(), 1);
271 assertTrue(features.contains(sf8));
272 features = fr.findFeaturesAtColumn(seq, 5);
273 assertEquals(features.size(), 1);
274 assertTrue(features.contains(sf8));
277 @Test(groups = "Functional")
278 public void testFilterFeaturesForDisplay()
280 String seqData = ">s1\nabcdef\n";
281 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
282 DataSourceType.PASTE);
283 AlignViewportI av = af.getViewport();
284 FeatureRenderer fr = new FeatureRenderer(av);
286 List<SequenceFeature> features = new ArrayList<>();
287 fr.filterFeaturesForDisplay(features, null); // empty list, does nothing
289 SequenceI seq = av.getAlignment().getSequenceAt(0);
290 SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
292 seq.addSequenceFeature(sf1);
293 SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
295 seq.addSequenceFeature(sf2);
296 SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
298 seq.addSequenceFeature(sf3);
299 SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
301 seq.addSequenceFeature(sf4);
302 SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
304 seq.addSequenceFeature(sf5);
306 fr.findAllFeatures(true);
308 features = seq.getSequenceFeatures();
309 assertEquals(features.size(), 5);
310 assertTrue(features.contains(sf1));
311 assertTrue(features.contains(sf2));
312 assertTrue(features.contains(sf3));
313 assertTrue(features.contains(sf4));
314 assertTrue(features.contains(sf5));
317 * filter out duplicate (co-located) features
318 * note: which gets removed is not guaranteed
320 fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
321 assertEquals(features.size(), 3);
322 assertTrue(features.contains(sf1) || features.contains(sf4));
323 assertFalse(features.contains(sf1) && features.contains(sf4));
324 assertTrue(features.contains(sf2) || features.contains(sf3));
325 assertFalse(features.contains(sf2) && features.contains(sf3));
326 assertTrue(features.contains(sf5));
329 * hide group 3 - sf3 is removed, sf2 is retained
331 fr.setGroupVisibility("group3", false);
332 features = seq.getSequenceFeatures();
333 fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
334 assertEquals(features.size(), 3);
335 assertTrue(features.contains(sf1) || features.contains(sf4));
336 assertFalse(features.contains(sf1) && features.contains(sf4));
337 assertTrue(features.contains(sf2));
338 assertFalse(features.contains(sf3));
339 assertTrue(features.contains(sf5));
342 * hide group 2, show group 3 - sf2 is removed, sf3 is retained
344 fr.setGroupVisibility("group2", false);
345 fr.setGroupVisibility("group3", true);
346 features = seq.getSequenceFeatures();
347 fr.filterFeaturesForDisplay(features, null);
348 assertEquals(features.size(), 3);
349 assertTrue(features.contains(sf1) || features.contains(sf4));
350 assertFalse(features.contains(sf1) && features.contains(sf4));
351 assertFalse(features.contains(sf2));
352 assertTrue(features.contains(sf3));
353 assertTrue(features.contains(sf5));
356 * no filtering of co-located features with graduated colour scheme
357 * filterFeaturesForDisplay does _not_ check colour threshold
358 * sf2 is removed as its group is hidden
360 features = seq.getSequenceFeatures();
361 fr.filterFeaturesForDisplay(features, new FeatureColour(Color.black,
362 Color.white, 0f, 1f));
363 assertEquals(features.size(), 4);
364 assertFalse(features.contains(sf2));
367 * co-located features with colour by label
368 * should not get filtered
370 features = seq.getSequenceFeatures();
371 FeatureColour fc = new FeatureColour(Color.black);
372 fc.setColourByLabel(true);
373 fr.filterFeaturesForDisplay(features, fc);
374 assertEquals(features.size(), 4);
375 assertTrue(features.contains(sf1));
376 assertTrue(features.contains(sf3));
377 assertTrue(features.contains(sf4));
378 assertTrue(features.contains(sf5));
381 * no filtering if transparency is applied
383 fr.setTransparency(0.5f);
384 features = seq.getSequenceFeatures();
385 fr.setGroupVisibility("group2", true);
386 fr.filterFeaturesForDisplay(features, new FeatureColour(Color.RED));
387 assertEquals(features.size(), 5);