+
+ // 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));
+
+ /*
+ * give "Type3" features a graduated colour scheme
+ * - first with no threshold
+ */
+ FeatureColourI gc = new FeatureColour(Color.green, Color.yellow,
+ Color.red, null, 0f, 10f);
+ fr.getFeatureColours().put("Type3", gc);
+ features = fr.findFeaturesAtColumn(seq, 8);
+ assertTrue(features.contains(sf4));
+ // now with threshold > 2f - feature score of 1f is excluded
+ gc.setAboveThreshold(true);
+ gc.setThreshold(2f);
+ features = fr.findFeaturesAtColumn(seq, 8);
+ assertFalse(features.contains(sf4));
+
+ /*
+ * make "Type3" graduated colour by attribute "AF"
+ * - first with no attribute held - feature should be excluded
+ */
+ gc.setAttributeName("AF");
+ features = fr.findFeaturesAtColumn(seq, 8);
+ assertFalse(features.contains(sf4));
+ // now with the attribute above threshold - should be included
+ sf4.setValue("AF", "2.4");
+ features = fr.findFeaturesAtColumn(seq, 8);
+ assertTrue(features.contains(sf4));
+ // now with the attribute below threshold - should be excluded
+ sf4.setValue("AF", "1.4");
+ features = fr.findFeaturesAtColumn(seq, 8);
+ assertFalse(features.contains(sf4));
+ }
+
+ @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); // empty list, does nothing
+
+ SequenceI seq = av.getAlignment().getSequenceAt(0);
+ SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
+ "group1");
+ SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
+ "group2");
+ SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
+ "group3");
+ SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
+ "group4");
+ SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
+ "group4");
+ seq.addSequenceFeature(sf1);
+ seq.addSequenceFeature(sf2);
+ seq.addSequenceFeature(sf3);
+ seq.addSequenceFeature(sf4);
+ 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);
+ 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));
+
+ /*
+ * features in hidden groups are removed
+ */
+ fr.setGroupVisibility("group2", false);
+ fr.setGroupVisibility("group3", false);
+ features = seq.getSequenceFeatures();
+ fr.filterFeaturesForDisplay(features);
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf1) || features.contains(sf4));
+ assertFalse(features.contains(sf1) && features.contains(sf4));
+ assertFalse(features.contains(sf2));
+ assertFalse(features.contains(sf3));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * no filtering if transparency is applied
+ */
+ fr.setTransparency(0.5f);
+ features = seq.getSequenceFeatures();
+ fr.filterFeaturesForDisplay(features);
+ assertEquals(features.size(), 5);
+ }
+
+ @Test(groups = "Functional")
+ public void testGetColour()
+ {
+ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n",
+ DataSourceType.PASTE);
+ AlignViewportI av = af.getViewport();
+ FeatureRenderer fr = new FeatureRenderer(av);
+
+ /*
+ * simple colour, feature type and group displayed
+ */
+ FeatureColourI fc = new FeatureColour(Color.red);
+ fr.getFeatureColours().put("Cath", fc);
+ SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
+ "group1");
+ assertEquals(fr.getColour(sf1), Color.red);
+
+ /*
+ * hide feature type, then unhide
+ * - feature type visibility should not affect the result
+ */
+ FeatureSettingsBean[] data = new FeatureSettingsBean[1];
+ data[0] = new FeatureSettingsBean("Cath", fc, null, false);
+ fr.setFeaturePriority(data);
+ assertEquals(fr.getColour(sf1), Color.red);
+ data[0] = new FeatureSettingsBean("Cath", fc, null, true);
+ fr.setFeaturePriority(data);
+ assertEquals(fr.getColour(sf1), Color.red);
+
+ /*
+ * hide feature group, then unhide
+ */
+ fr.setGroupVisibility("group1", false);
+ assertNull(fr.getColour(sf1));
+ fr.setGroupVisibility("group1", true);
+ assertEquals(fr.getColour(sf1), Color.red);
+
+ /*
+ * graduated colour by score, no threshold, no score
+ *
+ */
+ FeatureColourI gc = new FeatureColour(Color.red, Color.yellow,
+ Color.red, Color.green, 1f, 11f);
+ fr.getFeatureColours().put("Cath", gc);
+ assertEquals(fr.getColour(sf1), Color.green);
+
+ /*
+ * graduated colour by score, no threshold, with score value
+ */
+ SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f,
+ "group1");
+ // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0)
+ Color expected = new Color(255, 128, 0);
+ assertEquals(fr.getColour(sf2), expected);
+
+ /*
+ * above threshold, score is above threshold - no change
+ */
+ gc.setAboveThreshold(true);
+ gc.setThreshold(5f);
+ assertEquals(fr.getColour(sf2), expected);
+
+ /*
+ * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11
+ * or from yellow(255, 255, 0) to red(255, 0, 0)
+ */
+ gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green,
+ 5f, 11f);
+ fr.getFeatureColours().put("Cath", gc);
+ gc.setAutoScaled(false); // this does little other than save a checkbox setting!
+ assertEquals(fr.getColour(sf2), new Color(255, 213, 0));
+
+ /*
+ * feature score is below threshold - no colour
+ */
+ gc.setAboveThreshold(true);
+ gc.setThreshold(7f);
+ assertNull(fr.getColour(sf2));
+
+ /*
+ * feature score is above threshold - no colour
+ */
+ gc.setBelowThreshold(true);
+ gc.setThreshold(3f);
+ assertNull(fr.getColour(sf2));
+
+ /*
+ * colour by feature attribute value
+ * first with no value held
+ */
+ gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green,
+ 1f, 11f);
+ fr.getFeatureColours().put("Cath", gc);
+ gc.setAttributeName("AF");
+ assertEquals(fr.getColour(sf2), Color.green);
+
+ // with non-numeric attribute value
+ sf2.setValue("AF", "Five");
+ assertEquals(fr.getColour(sf2), Color.green);
+
+ // with numeric attribute value
+ sf2.setValue("AF", "6");
+ assertEquals(fr.getColour(sf2), expected);
+
+ // with numeric value outwith threshold
+ gc.setAboveThreshold(true);
+ gc.setThreshold(10f);
+ assertNull(fr.getColour(sf2));
+
+ // with filter on AF < 4
+ gc.setAboveThreshold(false);
+ assertEquals(fr.getColour(sf2), expected);
+ FeatureMatcherSetI filter = new FeatureMatcherSet();
+ filter.and(FeatureMatcher.byAttribute(Condition.LT, "4.0", "AF"));
+ fr.setFeatureFilter("Cath", filter);
+ assertNull(fr.getColour(sf2));
+
+ // with filter on 'Consequence contains missense'
+ filter = new FeatureMatcherSet();
+ filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense",
+ "Consequence"));
+ fr.setFeatureFilter("Cath", filter);
+ // if feature has no Consequence attribute, no colour
+ assertNull(fr.getColour(sf2));
+ // if attribute does not match filter, no colour
+ sf2.setValue("Consequence", "Synonymous");
+ assertNull(fr.getColour(sf2));
+ // attribute matches filter
+ sf2.setValue("Consequence", "Missense variant");
+ assertEquals(fr.getColour(sf2), expected);
+
+ // with filter on CSQ:Feature contains "ENST01234"
+ filter = new FeatureMatcherSet();
+ filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234",
+ "CSQ", "Feature"));
+ fr.setFeatureFilter("Cath", filter);
+ // if feature has no CSQ data, no colour
+ assertNull(fr.getColour(sf2));
+ // if CSQ data does not include Feature, no colour
+ Map<String, String> csqData = new HashMap<>();
+ csqData.put("BIOTYPE", "Transcript");
+ sf2.setValue("CSQ", csqData);
+ assertNull(fr.getColour(sf2));
+ // if attribute does not match filter, no colour
+ csqData.put("Feature", "ENST9876");
+ assertNull(fr.getColour(sf2));
+ // attribute matches filter
+ csqData.put("Feature", "ENST01234");
+ assertEquals(fr.getColour(sf2), expected);
+ }
+
+ @Test(groups = "Functional")
+ public void testIsVisible()
+ {
+ String seqData = ">s1\nMLQGIFPRS\n";
+ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
+ DataSourceType.PASTE);
+ AlignViewportI av = af.getViewport();
+ FeatureRenderer fr = new FeatureRenderer(av);
+ SequenceI seq = av.getAlignment().getSequenceAt(0);
+ SequenceFeature sf = new SequenceFeature("METAL", "Desc", 10, 10, 1f,
+ "Group");
+ sf.setValue("AC", "11");
+ sf.setValue("CLIN_SIG", "Likely Pathogenic");
+ seq.addSequenceFeature(sf);
+
+ assertFalse(fr.isVisible(null));
+
+ /*
+ * initial state FeatureRenderer hasn't 'found' feature
+ * and so its feature type has not yet been set visible
+ */
+ assertFalse(fr.getDisplayedFeatureCols().containsKey("METAL"));
+ assertFalse(fr.isVisible(sf));
+
+ fr.findAllFeatures(true);
+ assertTrue(fr.isVisible(sf));
+
+ /*
+ * feature group not visible
+ */
+ fr.setGroupVisibility("Group", false);
+ assertFalse(fr.isVisible(sf));
+ fr.setGroupVisibility("Group", true);
+ assertTrue(fr.isVisible(sf));
+
+ /*
+ * feature score outwith colour threshold (score > 2)
+ */
+ FeatureColourI fc = new FeatureColour(null, Color.white, Color.black,
+ Color.white, 0, 10);
+ fc.setAboveThreshold(true);
+ fc.setThreshold(2f);
+ fr.setColour("METAL", fc);
+ assertFalse(fr.isVisible(sf)); // score 1 is not above threshold 2
+ fc.setBelowThreshold(true);
+ assertTrue(fr.isVisible(sf)); // score 1 is below threshold 2
+
+ /*
+ * colour with threshold on attribute AC (value is 11)
+ */
+ fc.setAttributeName("AC");
+ assertFalse(fr.isVisible(sf)); // value 11 is not below threshold 2
+ fc.setAboveThreshold(true);
+ assertTrue(fr.isVisible(sf)); // value 11 is above threshold 2
+
+ fc.setAttributeName("AF"); // attribute AF is absent in sf
+ assertTrue(fr.isVisible(sf)); // feature is not excluded by threshold
+
+ FeatureMatcherSetI filter = new FeatureMatcherSet();
+ filter.and(FeatureMatcher.byAttribute(Condition.Contains, "pathogenic",
+ "CLIN_SIG"));
+ fr.setFeatureFilter("METAL", filter);
+ assertTrue(fr.isVisible(sf)); // feature matches filter
+ filter.and(FeatureMatcher.byScore(Condition.LE, "0.4"));
+ assertFalse(fr.isVisible(sf)); // feature doesn't match filter
+ }
+
+ @Test(groups = "Functional")
+ public void testFindComplementFeaturesAtResidue()
+ {
+ Jalview.main(
+ new String[]
+ { "-nonews", "-props", "test/jalview/testProps.jvprops" });
+
+ // codons for MCWHSE
+ String cdsSeq = ">cds\nATGtgtTGGcacTCAgaa";
+ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(cdsSeq,
+ DataSourceType.PASTE);
+ af.showTranslation_actionPerformed(
+ GeneticCodes.getInstance().getStandardCodeTable());
+ af.closeMenuItem_actionPerformed(true);
+
+ /*
+ * find the complement frames (ugly)
+ */
+ AlignFrame[] frames = Desktop.getAlignFrames();
+ assertEquals(frames.length, 2);
+ AlignViewport av1 = frames[0].getViewport();
+ AlignViewport av2 = frames[1].getViewport();
+ AlignViewport cds = av1.getAlignment().isNucleotide() ? av1 : av2;
+ AlignViewport peptide = cds == av1 ? av2 : av1;
+ assertNotNull(cds);
+ assertNotNull(peptide);
+
+ /*
+ * add features to CDS at first codon, positions 2-3
+ */
+ SequenceI seq1 = cds.getAlignment().getSequenceAt(0);
+ SequenceFeature sf1 = new SequenceFeature("sequence_variant", "G,GT", 2,
+ 2, "ensembl");
+ seq1.addSequenceFeature(sf1);
+ SequenceFeature sf2 = new SequenceFeature("sequence_variant", "C, CA",
+ 3, 3, "ensembl");
+ seq1.addSequenceFeature(sf2);
+
+ /*
+ * 'find' mapped features from the peptide position
+ * - first with CDS features _not_ shown on peptide alignment
+ */
+ SequenceI seq2 = peptide.getAlignment().getSequenceAt(0);
+ FeatureRenderer frC = new FeatureRenderer(cds);
+ frC.featuresAdded();
+ MappedFeatures mf = frC.findComplementFeaturesAtResidue(seq2, 1);
+ assertNotNull(mf);
+ assertEquals(mf.features.size(), 2);
+ assertSame(mf.features.get(0), sf1);
+ assertSame(mf.features.get(1), sf2);
+
+ /*
+ * add exon feature and verify it is only returned once for a
+ * peptide position, even though it is on all 3 codon positions
+ */
+ SequenceFeature sf3 = new SequenceFeature("exon", "exon1", 4, 12,
+ "ensembl");
+ seq1.addSequenceFeature(sf3);
+ frC.featuresAdded();
+ mf = frC.findComplementFeaturesAtResidue(seq2, 3);
+ assertNotNull(mf);
+ assertEquals(mf.features.size(), 1);
+ assertSame(mf.features.get(0), sf3);