JAL-2483 JAL-2481 findAllFeatures without scanning all features
[jalview.git] / src / jalview / viewmodel / seqfeatures / FeatureRendererModel.java
index 68febb9..4ab050d 100644 (file)
@@ -35,7 +35,9 @@ import java.beans.PropertyChangeListener;
 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;
@@ -306,7 +308,7 @@ public abstract class FeatureRendererModel implements
    * 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
    */
@@ -328,8 +330,7 @@ public abstract class FeatureRendererModel implements
     }
     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++)
@@ -340,94 +341,109 @@ public abstract class FeatureRendererModel implements
         }
       }
     }
-    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;
 
   /**
@@ -664,7 +680,8 @@ public abstract class FeatureRendererModel implements
   }
 
   /**
-   * 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) }