}
/**
- * Answers the minimum score held for positional or non-positional features
- * for the specified type. This may be Float.NaN if there are no features, or
- * none has a non-NaN score.
- *
- * @param type
- * @param positional
- * @return
+ * {@inheritDoc}
*/
+ @Override
public float getMinimumScore(String type, boolean positional)
{
return featureStore.containsKey(type) ? featureStore.get(type)
}
/**
- * Answers the maximum score held for positional or non-positional features
- * for the specified type. This may be Float.NaN if there are no features, or
- * none has a non-NaN score.
- *
- * @param type
- * @param positional
- * @return
+ * {@inheritDoc}
*/
+ @Override
public float getMaximumScore(String type, boolean positional)
{
return featureStore.containsKey(type) ? featureStore.get(type)
*
* @param sf
*/
- abstract boolean add(SequenceFeature sf);
+ boolean add(SequenceFeature sf);
/**
* Returns a (possibly empty) list of features, optionally restricted to
* @param type
* @return
*/
- abstract List<SequenceFeature> findFeatures(int from, int to,
+ List<SequenceFeature> findFeatures(int from, int to,
String... type);
/**
* @param type
* @return
*/
- abstract List<SequenceFeature> getAllFeatures(String... type);
+ List<SequenceFeature> getAllFeatures(String... type);
/**
* Answers the number of (positional or non-positional) features, optionally
* @param type
* @return
*/
- abstract int getFeatureCount(boolean positional, String... type);
+ int getFeatureCount(boolean positional, String... type);
/**
* Answers the total length of positional features, optionally restricted to
* @param type
* @return
*/
- abstract int getTotalFeatureLength(String... type);
+ int getTotalFeatureLength(String... type);
/**
* Answers a list of all positional features, optionally restricted to
* @param type
* @return
*/
- abstract List<SequenceFeature> getPositionalFeatures(
+ List<SequenceFeature> getPositionalFeatures(
String... type);
/**
*
* @return
*/
- abstract List<SequenceFeature> getContactFeatures(String... type);
+ List<SequenceFeature> getContactFeatures(String... type);
/**
* Answers a list of all non-positional features, optionally restricted to
* if no type is specified, all are returned
* @return
*/
- abstract List<SequenceFeature> getNonPositionalFeatures(
+ List<SequenceFeature> getNonPositionalFeatures(
String... type);
/**
*
* @param sf
*/
- abstract boolean delete(SequenceFeature sf);
+ boolean delete(SequenceFeature sf);
/**
* Answers true if this store contains at least one feature, else false
*
* @return
*/
- abstract boolean hasFeatures();
+ boolean hasFeatures();
/**
* Returns a set of the distinct feature groups present in the collection. The
* @param type
* @return
*/
- abstract Set<String> getFeatureGroups(boolean positionalFeatures,
+ Set<String> getFeatureGroups(boolean positionalFeatures,
String... type);
/**
* @param groups
* @return
*/
- abstract Set<String> getFeatureTypesForGroups(
+ Set<String> getFeatureTypesForGroups(
boolean positionalFeatures, String... groups);
/**
*
* @return
*/
- abstract Set<String> getFeatureTypes();
+ Set<String> getFeatureTypes();
+ /**
+ * Answers the minimum score held for positional or non-positional features
+ * for the specified type. This may be Float.NaN if there are no features, or
+ * none has a non-NaN score.
+ *
+ * @param type
+ * @param positional
+ * @return
+ */
+ float getMinimumScore(String type, boolean positional);
+
+ /**
+ * Answers the maximum score held for positional or non-positional features
+ * for the specified type. This may be Float.NaN if there are no features, or
+ * none has a non-NaN score.
+ *
+ * @param type
+ * @param positional
+ * @return
+ */
+ float getMaximumScore(String type, boolean positional);
}
\ No newline at end of file
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
* Searches alignment for all features and updates colours
*
* @param newMadeVisible
- * if true newly added feature types will be rendered immediatly
+ * if true newly added feature types will be rendered immediately
* TODO: check to see if this method should actually be proxied so
* repaint events can be propagated by the renderer code
*/
}
FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed();
- ArrayList<String> allfeatures = new ArrayList<String>();
- ArrayList<String> oldfeatures = new ArrayList<String>();
+ Set<String> oldfeatures = new HashSet<String>();
if (renderOrder != null)
{
for (int i = 0; i < renderOrder.length; i++)
}
}
}
- if (minmax == null)
- {
- minmax = new Hashtable<String, float[][]>();
- }
+
AlignmentI alignment = av.getAlignment();
+ List<String> allfeatures = new ArrayList<String>(); // or HashSet?
+
for (int i = 0; i < alignment.getHeight(); i++)
{
SequenceI asq = alignment.getSequenceAt(i);
- SequenceFeature[] features = asq.getSequenceFeatures();
-
- if (features == null)
+ for (String group : asq.getFeatures().getFeatureGroups(true))
{
- continue;
- }
-
- int index = 0;
- while (index < features.length)
- {
- if (!featuresDisplayed.isRegistered(features[index].getType()))
+ if (group == null)
{
- String fgrp = features[index].getFeatureGroup();
- if (fgrp != null)
- {
- Boolean groupDisplayed = featureGroups.get(fgrp);
- if (groupDisplayed == null)
- {
- groupDisplayed = Boolean.valueOf(newMadeVisible);
- featureGroups.put(fgrp, groupDisplayed);
- }
- if (!groupDisplayed.booleanValue())
- {
- index++;
- continue;
- }
- }
- if (!(features[index].begin == 0 && features[index].end == 0))
- {
- // If beginning and end are 0, the feature is for the whole sequence
- // and we don't want to render the feature in the normal way
-
- if (newMadeVisible
- && !oldfeatures.contains(features[index].getType()))
- {
- // this is a new feature type on the alignment. Mark it for
- // display.
- featuresDisplayed.setVisible(features[index].getType());
- setOrder(features[index].getType(), 0);
- }
- }
+ continue;
}
- if (!allfeatures.contains(features[index].getType()))
+ Boolean groupDisplayed = featureGroups.get(group);
+ if (groupDisplayed == null)
{
- allfeatures.add(features[index].getType());
+ groupDisplayed = Boolean.valueOf(newMadeVisible);
+ featureGroups.put(group, groupDisplayed);
}
- if (!Float.isNaN(features[index].score))
+ if (groupDisplayed)
{
- int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
- float[][] mm = minmax.get(features[index].getType());
- if (mm == null)
- {
- mm = new float[][] { null, null };
- minmax.put(features[index].getType(), mm);
- }
- if (mm[nonpos] == null)
- {
- mm[nonpos] = new float[] { features[index].score,
- features[index].score };
-
- }
- else
+ Set<String> types = asq.getFeatures().getFeatureTypesForGroups(
+ true, group);
+ for (String type : types)
{
- if (mm[nonpos][0] > features[index].score)
- {
- mm[nonpos][0] = features[index].score;
- }
- if (mm[nonpos][1] < features[index].score)
+ if (!allfeatures.contains(type)) // or use HashSet and no test?
{
- mm[nonpos][1] = features[index].score;
+ allfeatures.add(type);
}
+ updateMinMax(asq, type, true); // todo: for all features?
}
}
- index++;
}
}
+
+ /*
+ * mark any new feature types as visible
+ */
+ Collections.sort(allfeatures, String.CASE_INSENSITIVE_ORDER);
+ if (newMadeVisible)
+ {
+ for (String type : allfeatures)
+ {
+ if (!oldfeatures.contains(type))
+ {
+ featuresDisplayed.setVisible(type);
+ setOrder(type, 0);
+ }
+ }
+ }
+
updateRenderOrder(allfeatures);
findingFeatures = false;
}
+ /**
+ * Updates the global (alignment) min and max values for a feature type from
+ * the score for a sequence, if the score is not NaN. Values are stored
+ * separately for positional and non-positional features.
+ *
+ * @param seq
+ * @param featureType
+ * @param positional
+ */
+ protected void updateMinMax(SequenceI seq, String featureType,
+ boolean positional)
+ {
+ float min = seq.getFeatures().getMinimumScore(featureType, positional);
+ if (Float.isNaN(min))
+ {
+ return;
+ }
+
+ float max = seq.getFeatures().getMaximumScore(featureType, positional);
+
+ /*
+ * stored values are
+ * { {positionalMin, positionalMax}, {nonPositionalMin, nonPositionalMax} }
+ */
+ if (minmax == null)
+ {
+ minmax = new Hashtable<String, float[][]>();
+ }
+ synchronized (minmax)
+ {
+ float[][] mm = minmax.get(featureType);
+ int index = positional ? 0 : 1;
+ if (mm == null)
+ {
+ mm = new float[][] { null, null };
+ minmax.put(featureType, mm);
+ }
+ if (mm[index] == null)
+ {
+ mm[index] = new float[] { min, max };
+ }
+ else
+ {
+ mm[index][0] = Math.min(mm[index][0], min);
+ mm[index][1] = Math.max(mm[index][1], max);
+ }
+ }
+ }
protected Boolean firing = Boolean.FALSE;
/**
}
/**
- * Sets the priority order for features
+ * Sets the priority order for features, with the highest priority (displayed
+ * on top) at the start of the data array
*
* @param data
* { String(Type), Colour(Type), Boolean(Displayed) }
assertEquals(store.getTotalFeatureLength(), 0);
}
- @Test
+ @Test(groups = "Functional")
public void testGetMinimumScore_getMaximumScore()
{
SequenceFeatures sf = new SequenceFeatures();
--- /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.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"));
+ seqs.get(3).addSequenceFeature(
+ new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
+ // null value for type produces NullPointerException
+ fr.findAllFeatures(true);
+ types = fr.getRenderOrder();
+ groups = fr.getFeatureGroups();
+ assertEquals(types.size(), 2);
+ assertFalse(types.contains("Type"));
+ assertTrue(types.contains("Pfam"));
+ assertTrue(types.contains("Rfam"));
+ 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("Rfam", "Pfam"));
+
+ /*
+ * change render order (todo: an easier way)
+ * nb here last comes first in the data array
+ */
+ Object[][] data = new Object[2][];
+ FeatureColourI colour = new FeatureColour(Color.RED);
+ data[0] = new Object[] { "Rfam", colour, true };
+ data[1] = new Object[] { "Pfam", colour, false };
+ fr.setFeaturePriority(data);
+ assertEquals(fr.getRenderOrder(), Arrays.asList("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("Pfam", "Rfam", "Metal"));
+ assertEquals(fr.getDisplayedFeatureTypes(),
+ Arrays.asList("Rfam", "Metal"));
+ }
+}