JAL-2483 use SequenceFeatures to look up groups, types, feature lengths
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 19 Apr 2017 08:09:46 +0000 (09:09 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 19 Apr 2017 08:09:46 +0000 (09:09 +0100)
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/features/SequenceFeatures.java
src/jalview/datamodel/features/SequenceFeaturesI.java
src/jalview/gui/FeatureSettings.java
test/jalview/datamodel/features/SequenceFeaturesTest.java

index b93cae0..49caa4c 100755 (executable)
@@ -429,7 +429,8 @@ public class Sequence extends ASequence implements SequenceI
   @Override
   public SequenceFeaturesI getFeatures()
   {
-    return sequenceFeatureStore;
+    return datasetSequence != null ? datasetSequence.getFeatures()
+            : sequenceFeatureStore;
   }
 
   @Override
index 4cbf1d9..2f40193 100644 (file)
@@ -110,6 +110,26 @@ public class SequenceFeatures implements SequenceFeaturesI
    * {@inheritDoc}
    */
   @Override
+  public int getTotalFeatureLength(String... type)
+  {
+    int result = 0;
+
+    for (String featureType : varargToTypes(type))
+    {
+      FeatureStore featureSet = featureStore.get(featureType);
+      if (featureSet != null)
+      {
+        result += featureSet.getTotalFeatureLength();
+      }
+    }
+    return result;
+
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
   public List<SequenceFeature> getPositionalFeatures(String... type)
   {
     List<SequenceFeature> result = new ArrayList<SequenceFeature>();
index 5f904da..43e9448 100644 (file)
@@ -16,7 +16,7 @@ public interface SequenceFeaturesI
    * 
    * @param sf
    */
-  public abstract boolean add(SequenceFeature sf);
+  abstract boolean add(SequenceFeature sf);
 
   /**
    * Returns a (possibly empty) list of features, optionally restricted to
@@ -28,7 +28,7 @@ public interface SequenceFeaturesI
    * @param type
    * @return
    */
-  public abstract List<SequenceFeature> findFeatures(int from, int to,
+  abstract List<SequenceFeature> findFeatures(int from, int to,
           String... type);
 
   /**
@@ -38,9 +38,26 @@ public interface SequenceFeaturesI
    * @param type
    * @return
    */
-  public abstract List<SequenceFeature> getAllFeatures(String... type);
+  abstract List<SequenceFeature> getAllFeatures(String... type);
 
-  public abstract int getFeatureCount(boolean positional, String... type);
+  /**
+   * Answers the number of (positional or non-positional) features, optionally
+   * restricted to specified feature types. Contact features are counted as 1.
+   * 
+   * @param positional
+   * @param type
+   * @return
+   */
+  abstract int getFeatureCount(boolean positional, String... type);
+
+  /**
+   * Answers the total length of positional features, optionally restricted to
+   * specified feature types. Contact features are counted as length 1.
+   * 
+   * @param type
+   * @return
+   */
+  abstract int getTotalFeatureLength(String... type);
 
   /**
    * Answers a list of all positional features, optionally restricted to
@@ -49,7 +66,7 @@ public interface SequenceFeaturesI
    * @param type
    * @return
    */
-  public abstract List<SequenceFeature> getPositionalFeatures(
+  abstract List<SequenceFeature> getPositionalFeatures(
           String... type);
 
   /**
@@ -58,7 +75,7 @@ public interface SequenceFeaturesI
    * 
    * @return
    */
-  public abstract List<SequenceFeature> getContactFeatures(String... type);
+  abstract List<SequenceFeature> getContactFeatures(String... type);
 
   /**
    * Answers a list of all non-positional features, optionally restricted to
@@ -68,7 +85,7 @@ public interface SequenceFeaturesI
    *          if no type is specified, all are returned
    * @return
    */
-  public abstract List<SequenceFeature> getNonPositionalFeatures(
+  abstract List<SequenceFeature> getNonPositionalFeatures(
           String... type);
 
   /**
@@ -79,14 +96,14 @@ public interface SequenceFeaturesI
    * 
    * @param sf
    */
-  public abstract boolean delete(SequenceFeature sf);
+  abstract boolean delete(SequenceFeature sf);
 
   /**
    * Answers true if this store contains at least one feature, else false
    * 
    * @return
    */
-  public abstract boolean hasFeatures();
+  abstract boolean hasFeatures();
 
   /**
    * Returns a set of the distinct feature groups present in the collection. The
@@ -99,7 +116,7 @@ public interface SequenceFeaturesI
    * @param type
    * @return
    */
-  public abstract Set<String> getFeatureGroups(boolean positionalFeatures,
+  abstract Set<String> getFeatureGroups(boolean positionalFeatures,
           String... type);
 
   /**
@@ -112,7 +129,7 @@ public interface SequenceFeaturesI
    * @param groups
    * @return
    */
-  public abstract Set<String> getFeatureTypesForGroups(
+  abstract Set<String> getFeatureTypesForGroups(
           boolean positionalFeatures, String... groups);
 
   /**
@@ -120,6 +137,6 @@ public interface SequenceFeaturesI
    * 
    * @return
    */
-  public abstract Set<String> getFeatureTypes();
+  abstract Set<String> getFeatureTypes();
 
 }
\ No newline at end of file
index 067d8ae..a98e728 100644 (file)
@@ -24,7 +24,6 @@ import jalview.api.FeatureColourI;
 import jalview.api.FeatureSettingsControllerI;
 import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
-import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.gui.Help.HelpId;
 import jalview.io.JalviewFileChooser;
@@ -465,7 +464,7 @@ public class FeatureSettings extends JPanel implements
   private boolean handlingUpdate = false;
 
   /**
-   * contains a float[3] for each feature type string. created by setTableData
+   * holds {featureCount, totalExtent} for each feature type
    */
   Map<String, float[]> typeWidth = null;
 
@@ -558,69 +557,57 @@ public class FeatureSettings extends JPanel implements
     typeWidth = new Hashtable<String, float[]>();
     // TODO: change avWidth calculation to 'per-sequence' average and use long
     // rather than float
-    float[] avWidth = null;
-    SequenceFeature[] tmpfeatures;
-    String group = null, type;
-    Vector<String> visibleChecks = new Vector<String>();
-
-    // Find out which features should be visible depending on which groups
-    // are selected / deselected
-    // and recompute average width ordering
+
+    Set<String> displayableTypes = new HashSet<String>();
+
+    /*
+     * determine which feature types may be visible depending on 
+     * which groups are selected, and recompute average width data
+     */
     for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
     {
 
       SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
-      tmpfeatures = seq.getSequenceFeatures();
-      if (tmpfeatures == null)
-      {
-        continue;
-      }
-      Set<String> types = seq.getFeatures().getFeatureTypes();
-      int index = 0;
-      while (index < tmpfeatures.length)
-      {
-        group = tmpfeatures[index].featureGroup;
-
-        if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
-        {
-          index++;
-          continue;
-        }
 
+      /*
+       * get the sequence's groups for positional features
+       * and keep track of which groups are visible
+       */
+      Set<String> groups = seq.getFeatures().getFeatureGroups(true);
+      Set<String> visibleGroups = new HashSet<String>();
+      for (String group : groups)
+      {
         if (group == null || checkGroupState(group))
         {
-          type = tmpfeatures[index].getType();
-          if (!visibleChecks.contains(type))
-          {
-            visibleChecks.addElement(type);
-          }
-        }
-        if (!typeWidth.containsKey(tmpfeatures[index].getType()))
-        {
-          typeWidth.put(tmpfeatures[index].getType(),
-                  avWidth = new float[3]);
-        }
-        else
-        {
-          avWidth = typeWidth.get(tmpfeatures[index].getType());
-        }
-        avWidth[0]++;
-        if (tmpfeatures[index].getBegin() > tmpfeatures[index].getEnd())
-        {
-          avWidth[1] += 1 + tmpfeatures[index].getBegin()
-                  - tmpfeatures[index].getEnd();
+          visibleGroups.add(group);
         }
-        else
+      }
+
+      /*
+       * get distinct feature types for visible groups
+       * record distinct visible types, and their count and total length
+       */
+      Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
+              visibleGroups.toArray(new String[visibleGroups.size()]));
+      for (String type : types)
+      {
+        displayableTypes.add(type);
+        float[] avWidth = typeWidth.get(type);
+        if (avWidth == null)
         {
-          avWidth[1] += 1 + tmpfeatures[index].getEnd()
-                  - tmpfeatures[index].getBegin();
+          avWidth = new float[2];
+          typeWidth.put(type, avWidth);
         }
-        index++;
+        // todo this could include features with a non-visible group
+        // - do we greatly care?
+        // todo should we include non-displayable features here, and only
+        // update when features are added?
+        avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
+        avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
       }
     }
 
-    int fSize = visibleChecks.size();
-    Object[][] data = new Object[fSize][3];
+    Object[][] data = new Object[displayableTypes.size()][3];
     int dataIndex = 0;
 
     if (fr.hasRenderOrder())
@@ -636,9 +623,9 @@ public class FeatureSettings extends JPanel implements
       List<String> frl = fr.getRenderOrder();
       for (int ro = frl.size() - 1; ro > -1; ro--)
       {
-        type = frl.get(ro);
+        String type = frl.get(ro);
 
-        if (!visibleChecks.contains(type))
+        if (!displayableTypes.contains(type))
         {
           continue;
         }
@@ -648,16 +635,17 @@ public class FeatureSettings extends JPanel implements
         data[dataIndex][2] = new Boolean(af.getViewport()
                 .getFeaturesDisplayed().isVisible(type));
         dataIndex++;
-        visibleChecks.removeElement(type);
+        displayableTypes.remove(type);
       }
     }
 
-    fSize = visibleChecks.size();
-    for (int i = 0; i < fSize; i++)
+    /*
+     * process any extra features belonging only to 
+     * a group which was just selected
+     */
+    while (!displayableTypes.isEmpty())
     {
-      // These must be extra features belonging to the group
-      // which was just selected
-      type = visibleChecks.elementAt(i).toString();
+      String type = displayableTypes.iterator().next();
       data[dataIndex][0] = type;
 
       data[dataIndex][1] = fr.getFeatureStyle(type);
@@ -670,6 +658,7 @@ public class FeatureSettings extends JPanel implements
 
       data[dataIndex][2] = new Boolean(true);
       dataIndex++;
+      displayableTypes.remove(type);
     }
 
     if (originalData == null)
index 4cd37ee..31703cd 100644 (file)
@@ -713,4 +713,59 @@ public class SequenceFeaturesTest
     assertEquals(features.size(), 3);
     assertFalse(features.contains(sf3));
   }
+
+  @Test(groups = "Functional")
+  public void testGetTotalFeatureLength()
+  {
+    SequenceFeaturesI store = new SequenceFeatures();
+    assertEquals(store.getTotalFeatureLength(), 0);
+
+    SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+            Float.NaN, null);
+    assertTrue(store.add(sf1));
+    assertEquals(store.getTotalFeatureLength(), 11);
+
+    // re-add does nothing!
+    assertFalse(store.add(sf1));
+    assertEquals(store.getTotalFeatureLength(), 11);
+
+    /*
+     * add non-positional feature
+     */
+    SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 0, 0,
+            Float.NaN, null);
+    store.add(sf3);
+    assertEquals(store.getTotalFeatureLength(), 11);
+
+    /*
+     * add contact feature - counts 1 to feature length
+     */
+    SequenceFeature sf4 = new SequenceFeature("Disulphide Bond", "desc",
+            10, 20, Float.NaN, null);
+    store.add(sf4);
+    assertEquals(store.getTotalFeatureLength(), 12);
+
+    /*
+     * add another Pfam
+     */
+    SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 10, 20,
+            Float.NaN, null);
+    store.add(sf5);
+    assertEquals(store.getTotalFeatureLength(), 23);
+
+    /*
+     * delete features
+     */
+    assertTrue(store.delete(sf3)); // non-positional
+    assertEquals(store.getTotalFeatureLength(), 23); // no change
+
+    assertTrue(store.delete(sf5));
+    assertEquals(store.getTotalFeatureLength(), 12);
+
+    assertTrue(store.delete(sf4)); // contact
+    assertEquals(store.getTotalFeatureLength(), 11);
+
+    assertTrue(store.delete(sf1));
+    assertEquals(store.getTotalFeatureLength(), 0);
+  }
 }