--- /dev/null
+package jalview.renderer.seqfeatures;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import jalview.api.AlignViewportI;
+import jalview.api.FeatureColourI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+import jalview.schemes.FeatureColour;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+public class FeatureRendererTest
+{
+
+ @Test(groups = "Functional")
+ public void testFindAllFeatures()
+ {
+ String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n";
+ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
+ DataSourceType.PASTE);
+ AlignViewportI av = af.getViewport();
+ FeatureRenderer fr = new FeatureRenderer(av);
+
+ /*
+ * with no features
+ */
+ fr.findAllFeatures(true);
+ assertTrue(fr.getRenderOrder().isEmpty());
+ assertTrue(fr.getFeatureGroups().isEmpty());
+
+ List<SequenceI> seqs = av.getAlignment().getSequences();
+
+ // add a non-positional feature - should be ignored by FeatureRenderer
+ SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
+ "Group");
+ seqs.get(0).addSequenceFeature(sf1);
+ fr.findAllFeatures(true);
+ // ? bug - types and groups added for non-positional features
+ List<String> types = fr.getRenderOrder();
+ List<String> groups = fr.getFeatureGroups();
+ assertEquals(types.size(), 0);
+ assertFalse(types.contains("Type"));
+ assertEquals(groups.size(), 0);
+ assertFalse(groups.contains("Group"));
+
+ // add some positional features
+ seqs.get(1).addSequenceFeature(
+ new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup"));
+ seqs.get(2).addSequenceFeature(
+ new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup"));
+ // bug in findAllFeatures - group not checked for a known feature type
+ seqs.get(2).addSequenceFeature(
+ new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN,
+ "RfamGroup"));
+ // existing feature type with null group
+ seqs.get(3).addSequenceFeature(
+ new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
+ // new feature type with null group
+ seqs.get(3).addSequenceFeature(
+ new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null));
+ // null value for type produces NullPointerException
+ fr.findAllFeatures(true);
+ types = fr.getRenderOrder();
+ groups = fr.getFeatureGroups();
+ assertEquals(types.size(), 3);
+ assertFalse(types.contains("Type"));
+ assertTrue(types.contains("Pfam"));
+ assertTrue(types.contains("Rfam"));
+ assertTrue(types.contains("Scop"));
+ assertEquals(groups.size(), 2);
+ assertFalse(groups.contains("Group"));
+ assertTrue(groups.contains("PfamGroup"));
+ assertTrue(groups.contains("RfamGroup"));
+ assertFalse(groups.contains(null)); // null group is ignored
+
+ /*
+ * check min-max values
+ */
+ Map<String, float[][]> minMax = fr.getMinMax();
+ assertEquals(minMax.size(), 1); // non-positional and NaN not stored
+ assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min
+ assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max
+
+ // increase max for Pfam, add scores for Rfam
+ seqs.get(0).addSequenceFeature(
+ new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup"));
+ seqs.get(1).addSequenceFeature(
+ new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup"));
+ fr.findAllFeatures(true);
+ // note minMax is not a defensive copy, shouldn't expose this
+ assertEquals(minMax.size(), 2);
+ assertEquals(minMax.get("Pfam")[0][0], 1f);
+ assertEquals(minMax.get("Pfam")[0][1], 8f);
+ assertEquals(minMax.get("Rfam")[0][0], 6f);
+ assertEquals(minMax.get("Rfam")[0][1], 6f);
+
+ /*
+ * check render order (last is on top)
+ */
+ List<String> renderOrder = fr.getRenderOrder();
+ assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
+
+ /*
+ * change render order (todo: an easier way)
+ * nb here last comes first in the data array
+ */
+ Object[][] data = new Object[3][];
+ FeatureColourI colour = new FeatureColour(Color.RED);
+ data[0] = new Object[] { "Rfam", colour, true };
+ data[1] = new Object[] { "Pfam", colour, false };
+ data[2] = new Object[] { "Scop", colour, false };
+ fr.setFeaturePriority(data);
+ assertEquals(fr.getRenderOrder(), Arrays.asList("Scop", "Pfam", "Rfam"));
+ assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam"));
+
+ /*
+ * add a new feature type: should go on top of render order as visible,
+ * other feature ordering and visibility should be unchanged
+ */
+ seqs.get(2).addSequenceFeature(
+ new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup"));
+ fr.findAllFeatures(true);
+ assertEquals(fr.getRenderOrder(),
+ Arrays.asList("Scop", "Pfam", "Rfam", "Metal"));
+ assertEquals(fr.getDisplayedFeatureTypes(),
+ Arrays.asList("Rfam", "Metal"));
+ }
+
+ @Test(groups = "Functional")
+ public void testFindFeaturesAtColumn()
+ {
+ String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
+ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
+ DataSourceType.PASTE);
+ AlignViewportI av = af.getViewport();
+ FeatureRenderer fr = new FeatureRenderer(av);
+ SequenceI seq = av.getAlignment().getSequenceAt(0);
+
+ /*
+ * with no features
+ */
+ List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
+ assertTrue(features.isEmpty());
+
+ /*
+ * add features
+ */
+ SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
+ "Group"); // non-positional
+ seq.addSequenceFeature(sf1);
+ SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
+ "Group1");
+ seq.addSequenceFeature(sf2);
+ SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
+ "Group2");
+ seq.addSequenceFeature(sf3);
+ SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
+ null); // null group is always treated as visible
+ seq.addSequenceFeature(sf4);
+
+ /*
+ * add contact features
+ */
+ SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
+ 15, 1f, "Group1");
+ seq.addSequenceFeature(sf5);
+ SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
+ 15, 1f, "Group2");
+ seq.addSequenceFeature(sf6);
+ SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
+ 15, 1f, null);
+ seq.addSequenceFeature(sf7);
+
+ // feature spanning B--C
+ SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
+ "Group");
+ seq.addSequenceFeature(sf8);
+ // contact feature B/C
+ SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
+ 6, 1f, "Group");
+ seq.addSequenceFeature(sf9);
+
+ /*
+ * let feature renderer discover features (and make visible)
+ */
+ fr.findAllFeatures(true);
+ features = fr.findFeaturesAtColumn(seq, 15); // all positional
+ assertEquals(features.size(), 6);
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ assertTrue(features.contains(sf6));
+ assertTrue(features.contains(sf7));
+
+ /*
+ * at a non-contact position
+ */
+ features = fr.findFeaturesAtColumn(seq, 14);
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+
+ /*
+ * make "Type2" not displayed
+ */
+ Object[][] data = new Object[4][];
+ FeatureColourI colour = new FeatureColour(Color.RED);
+ data[0] = new Object[] { "Type1", colour, true };
+ data[1] = new Object[] { "Type2", colour, false };
+ data[2] = new Object[] { "Type3", colour, true };
+ data[3] = new Object[] { "Disulphide Bond", colour, true };
+ fr.setFeaturePriority(data);
+
+ features = fr.findFeaturesAtColumn(seq, 15);
+ assertEquals(features.size(), 5); // no sf2
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ assertTrue(features.contains(sf6));
+ assertTrue(features.contains(sf7));
+
+ /*
+ * make "Group2" not displayed
+ */
+ fr.setGroupVisibility("Group2", false);
+
+ features = fr.findFeaturesAtColumn(seq, 15);
+ assertEquals(features.size(), 3); // no sf2, sf3, sf6
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ assertTrue(features.contains(sf7));
+
+ // features 'at' a gap between b and c
+ // - returns enclosing feature BC but not contact feature B/C
+ features = fr.findFeaturesAtColumn(seq, 4);
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf8));
+ features = fr.findFeaturesAtColumn(seq, 5);
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf8));
+ }
+
+ @Test(groups = "Functional")
+ public void testFilterFeaturesForDisplay()
+ {
+ String seqData = ">s1\nabcdef\n";
+ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
+ DataSourceType.PASTE);
+ AlignViewportI av = af.getViewport();
+ FeatureRenderer fr = new FeatureRenderer(av);
+
+ List<SequenceFeature> features = new ArrayList<>();
+ fr.filterFeaturesForDisplay(features, null); // empty list, does nothing
+
+ SequenceI seq = av.getAlignment().getSequenceAt(0);
+ SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
+ "group1");
+ seq.addSequenceFeature(sf1);
+ SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
+ "group2");
+ seq.addSequenceFeature(sf2);
+ SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
+ "group3");
+ seq.addSequenceFeature(sf3);
+ SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
+ "group4");
+ seq.addSequenceFeature(sf4);
+ SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
+ "group4");
+ seq.addSequenceFeature(sf5);
+
+ fr.findAllFeatures(true);
+
+ features = seq.getSequenceFeatures();
+ assertEquals(features.size(), 5);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * filter out duplicate (co-located) features
+ * note: which gets removed is not guaranteed
+ */
+ fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf1) || features.contains(sf4));
+ assertFalse(features.contains(sf1) && features.contains(sf4));
+ assertTrue(features.contains(sf2) || features.contains(sf3));
+ assertFalse(features.contains(sf2) && features.contains(sf3));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * hide group 3 - sf3 is removed, sf2 is retained
+ */
+ fr.setGroupVisibility("group3", false);
+ features = seq.getSequenceFeatures();
+ fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf1) || features.contains(sf4));
+ assertFalse(features.contains(sf1) && features.contains(sf4));
+ assertTrue(features.contains(sf2));
+ assertFalse(features.contains(sf3));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * hide group 2, show group 3 - sf2 is removed, sf3 is retained
+ */
+ fr.setGroupVisibility("group2", false);
+ fr.setGroupVisibility("group3", true);
+ features = seq.getSequenceFeatures();
+ fr.filterFeaturesForDisplay(features, null);
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf1) || features.contains(sf4));
+ assertFalse(features.contains(sf1) && features.contains(sf4));
+ assertFalse(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * no filtering of co-located features with graduated colour scheme
+ * filterFeaturesForDisplay does _not_ check colour threshold
+ * sf2 is removed as its group is hidden
+ */
+ features = seq.getSequenceFeatures();
+ fr.filterFeaturesForDisplay(features, new FeatureColour(Color.black,
+ Color.white, 0f, 1f));
+ assertEquals(features.size(), 4);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * co-located features with colour by label
+ * should not get filtered
+ */
+ features = seq.getSequenceFeatures();
+ FeatureColour fc = new FeatureColour(Color.black);
+ fc.setColourByLabel(true);
+ fr.filterFeaturesForDisplay(features, fc);
+ assertEquals(features.size(), 4);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ }
+}