JAL-2490 find features for export as GFF, JAL-2548 respect group
[jalview.git] / src / jalview / viewmodel / seqfeatures / FeatureRendererModel.java
index 4ac4804..a8e8989 100644 (file)
@@ -28,16 +28,17 @@ import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.seqfeatures.FeatureRenderer;
 import jalview.schemes.FeatureColour;
-import jalview.schemes.UserColourScheme;
+import jalview.util.ColorUtils;
 
 import java.awt.Color;
 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;
 import java.util.Map;
 import java.util.Set;
@@ -115,11 +116,10 @@ public abstract class FeatureRendererModel implements
           synchronized (fd)
           {
             fd.clear();
-            java.util.Iterator<String> fdisp = _fr.getFeaturesDisplayed()
-                    .getVisibleFeatures();
-            while (fdisp.hasNext())
+            for (String type : _fr.getFeaturesDisplayed()
+                    .getVisibleFeatures())
             {
-              fd.setVisible(fdisp.next());
+              fd.setVisible(type);
             }
           }
         }
@@ -265,44 +265,39 @@ public abstract class FeatureRendererModel implements
   @Override
   public List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
   {
-    ArrayList<SequenceFeature> tmp = new ArrayList<SequenceFeature>();
-    SequenceFeature[] features = sequence.getSequenceFeatures();
-
-    if (features != null)
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    if (!av.areFeaturesDisplayed())
     {
-      for (int i = 0; i < features.length; i++)
-      {
-        if (!av.areFeaturesDisplayed()
-                || !av.getFeaturesDisplayed().isVisible(
-                        features[i].getType()))
-        {
-          continue;
-        }
+      return result;
+    }
 
-        if (features[i].featureGroup != null
-                && featureGroups != null
-                && featureGroups.containsKey(features[i].featureGroup)
-                && !featureGroups.get(features[i].featureGroup)
-                        .booleanValue())
-        {
-          continue;
-        }
+    Set<String> visibleFeatures = getFeaturesDisplayed()
+            .getVisibleFeatures();
+    String[] visibleTypes = visibleFeatures
+            .toArray(new String[visibleFeatures.size()]);
 
-        if ((features[i].getBegin() <= res)
-                && (features[i].getEnd() >= res))
-        {
-          tmp.add(features[i]);
-        }
+    /*
+     * include features at the position provided their feature type is 
+     * displayed, and feature group is null or marked for display
+     */
+    List<SequenceFeature> features = sequence.getFeatures().findFeatures(
+            res, res, visibleTypes);
+
+    for (SequenceFeature sf : features)
+    {
+      if (!featureGroupNotShown(sf))
+      {
+        result.add(sf);
       }
     }
-    return tmp;
+    return result;
   }
 
   /**
    * 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
    */
@@ -324,8 +319,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++)
@@ -336,94 +330,150 @@ public abstract class FeatureRendererModel implements
         }
       }
     }
-    if (minmax == null)
-    {
-      minmax = new Hashtable<String, float[][]>();
-    }
+    // <<<<<<< HEAD
+    //
+    // =======
+    // if (minmax == null)
+    // {
+    // minmax = new Hashtable<String, float[][]>();
+    // }
+    //
+    // Set<String> oldGroups = new HashSet<String>(featureGroups.keySet());
+    // >>>>>>> refs/heads/develop
     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)
-      {
-        continue;
-      }
-
-      int index = 0;
-      while (index < features.length)
+      for (String group : asq.getFeatures().getFeatureGroups(true))
       {
-        if (!featuresDisplayed.isRegistered(features[index].getType()))
+        // <<<<<<< HEAD
+        /*
+         * features in null group are always displayed; other groups
+         * keep their current visibility; new groups as 'newMadeVisible'
+         */
+        boolean groupDisplayed = true;
+        if (group != null)
+        // =======
+        // continue;
+        // }
+        //
+        // int index = 0;
+        // while (index < features.length)
+        // {
+        // String fgrp = features[index].getFeatureGroup();
+        // oldGroups.remove(fgrp);
+        // if (!featuresDisplayed.isRegistered(features[index].getType()))
+        // >>>>>>> refs/heads/develop
         {
-          String fgrp = features[index].getFeatureGroup();
-          if (fgrp != null)
+          // <<<<<<< HEAD
+          if (featureGroups.containsKey(group))
+          // =======
+          // if (fgrp != null)
+          // >>>>>>> refs/heads/develop
           {
-            Boolean groupDisplayed = featureGroups.get(fgrp);
-            if (groupDisplayed == null)
-            {
-              groupDisplayed = Boolean.valueOf(newMadeVisible);
-              featureGroups.put(fgrp, groupDisplayed);
-            }
-            if (!groupDisplayed.booleanValue())
-            {
-              index++;
-              continue;
-            }
+            groupDisplayed = featureGroups.get(group);
           }
-          if (!(features[index].begin == 0 && features[index].end == 0))
+          else
           {
-            // 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);
-            }
+            groupDisplayed = newMadeVisible;
+            featureGroups.put(group, groupDisplayed);
           }
         }
-        if (!allfeatures.contains(features[index].getType()))
+        if (groupDisplayed)
         {
-          allfeatures.add(features[index].getType());
-        }
-        if (!Float.isNaN(features[index].score))
-        {
-          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++;
       }
     }
+
+    /*
+    //<<<<<<< HEAD
+     * 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);
+        }
+      }
+      // =======
+      // * oldGroups now consists of groups that no longer
+      // * have any feature in them - remove these
+      // */
+      // for (String grp : oldGroups)
+      // {
+      // featureGroups.remove(grp);
+      // >>>>>>> refs/heads/develop
+    }
+
     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;
 
   /**
@@ -538,7 +588,7 @@ public abstract class FeatureRendererModel implements
     FeatureColourI fc = featureColours.get(featureType);
     if (fc == null)
     {
-      Color col = UserColourScheme.createColourFromName(featureType);
+      Color col = ColorUtils.createColourFromName(featureType);
       fc = new FeatureColour(col);
       featureColours.put(featureType, fc);
     }
@@ -546,11 +596,13 @@ public abstract class FeatureRendererModel implements
   }
 
   /**
-   * calculate the render colour for a specific feature using current feature
-   * settings.
+   * Returns the configured colour for a particular feature instance. This
+   * includes calculation of 'colour by label', or of a graduated score colour,
+   * if applicable. It does not take into account feature visibility or colour
+   * transparency.
    * 
    * @param feature
-   * @return render colour for the given feature
+   * @return
    */
   public Color getColour(SequenceFeature feature)
   {
@@ -558,15 +610,29 @@ public abstract class FeatureRendererModel implements
     return fc.getColor(feature);
   }
 
+  /**
+   * Answers true unless the feature has a graduated colour scheme and the
+   * feature value lies outside the current threshold for display
+   * 
+   * @param sequenceFeature
+   * @return
+   */
   protected boolean showFeature(SequenceFeature sequenceFeature)
   {
     FeatureColourI fc = getFeatureStyle(sequenceFeature.type);
     return fc.isColored(sequenceFeature);
   }
 
+  /**
+   * Answers true if the feature type is currently selected to be displayed,
+   * else false
+   * 
+   * @param type
+   * @return
+   */
   protected boolean showFeatureOfType(String type)
   {
-    return av.getFeaturesDisplayed().isVisible(type);
+    return type == null ? false : av.getFeaturesDisplayed().isVisible(type);
   }
 
   @Override
@@ -575,11 +641,13 @@ public abstract class FeatureRendererModel implements
     featureColours.put(featureType, col);
   }
 
+  @Override
   public void setTransparency(float value)
   {
     transparency = value;
   }
 
+  @Override
   public float getTransparency()
   {
     return transparency;
@@ -642,7 +710,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) }
@@ -811,7 +880,7 @@ public abstract class FeatureRendererModel implements
    * @return list of groups
    */
   @Override
-  public List getGroups(boolean visible)
+  public List<String> getGroups(boolean visible)
   {
     if (featureGroups != null)
     {
@@ -866,11 +935,10 @@ public abstract class FeatureRendererModel implements
     {
       return fcols;
     }
-    Iterator<String> features = getViewport().getFeaturesDisplayed()
+    Set<String> features = getViewport().getFeaturesDisplayed()
             .getVisibleFeatures();
-    while (features.hasNext())
+    for (String feature : features)
     {
-      String feature = features.next();
       fcols.put(feature, getFeatureStyle(feature));
     }
     return fcols;
@@ -912,25 +980,31 @@ public abstract class FeatureRendererModel implements
   public List<String> getDisplayedFeatureGroups()
   {
     List<String> _gps = new ArrayList<String>();
-    boolean valid = false;
     for (String gp : getFeatureGroups())
     {
       if (checkGroupVisibility(gp, false))
       {
-        valid = true;
         _gps.add(gp);
       }
-      if (!valid)
-      {
-        return null;
-      }
-      else
-      {
-        // gps = new String[_gps.size()];
-        // _gps.toArray(gps);
-      }
     }
     return _gps;
   }
 
+  /**
+   * Answers true if the feature belongs to a feature group which is not
+   * currently displayed, else false
+   * 
+   * @param sequenceFeature
+   * @return
+   */
+  protected boolean featureGroupNotShown(final SequenceFeature sequenceFeature)
+  {
+    return featureGroups != null
+            && sequenceFeature.featureGroup != null
+            && sequenceFeature.featureGroup.length() != 0
+            && featureGroups.containsKey(sequenceFeature.featureGroup)
+            && !featureGroups.get(sequenceFeature.featureGroup)
+                    .booleanValue();
+  }
+
 }