JAL-3383 JAL-3253-applet additional efficiencies; FeatureStore
authorhansonr <hansonr@STO24954W.ad.stolaf.edu>
Sat, 3 Aug 2019 04:26:18 +0000 (23:26 -0500)
committerhansonr <hansonr@STO24954W.ad.stolaf.edu>
Sat, 3 Aug 2019 04:26:18 +0000 (23:26 -0500)
alternative to IntervalStore

- implemented for JavaScript only (see SequenceFeatures):

    boolean useIntervalStore = /**
                                * @j2sNative false &&
                                */
            true;

but can be tested in Java and JavaScript by changing either of those to
true/false.

- only requires storage for one sorted array in FeatureSorter:

  private SequenceFeature[] orderedFeatureStarts;

and one additional linked-list field pointer in SequenceFeature:

SequenceFeature containedBy

- when running, the position is looked up in the begin-sorted feature
array, and then the containedBy links are simply traversed using:

    SequenceFeature sf = findClosestFeature(orderedFeatureStarts, pos);
    while (sf != null) {
      if (sf.end >= pos)
      {
        result.add(sf);
      }
      sf = sf.containedBy;
    }

- my preliminary timing tests suggest this is 2x faster than
IntervalStore in JavaScript.

src/jalview/api/FeatureColourI.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/datamodel/SequenceI.java
src/jalview/datamodel/features/FeatureStore.java
src/jalview/datamodel/features/SequenceFeatures.java
src/jalview/datamodel/features/SequenceFeaturesI.java
src/jalview/renderer/OverviewRenderer.java
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java

index 7bfd8a8..c5f6727 100644 (file)
@@ -64,10 +64,9 @@ public interface FeatureColourI
   Color getNoColour();
 
   /**
-   * Answers true if the feature has a single colour, i.e. if isColourByLabel()
-   * and isGraduatedColour() both answer false
+   * Answers true if the feature has a single colour
    * 
-   * @return
+   * @return true iff not (isColourByLabel() || isGraduatedColour())
    */
   boolean isSimpleColour();
 
index 7624d2c..4219c8e 100755 (executable)
@@ -2156,15 +2156,26 @@ public class Sequence extends ASequence implements SequenceI
   /**
    * @author Bob Hanson 2019.07.30
    * 
-   * allows passing the result ArrayList as a parameter to avoid unnecessary construction
+   *         allows passing the result ArrayList as a parameter to avoid
+   *         unnecessary construction
+   * @return result (JavaScript) or new ArrayList (Java -- see FeatureRender)
    * 
    */
   @Override
-  public void findFeatures(int column, String type,
+  public List<SequenceFeature> findFeatures(int column, String type,
           List<SequenceFeature> result)
   {
-    getFeatures().findFeatures(findPosition(column - 1), type, result);
+    return getFeatures().findFeatures(findPosition(column - 1), type,
+            result);
   }
 
+  /**
+   * allows early intervention for renderer if this returns false
+   */
+  @Override
+  public boolean hasFeatures(String type)
+  {
+    return getFeatures().hasFeatures(type);
+  }
 
 }
index 7052f34..bf2408c 100755 (executable)
@@ -99,6 +99,11 @@ public class SequenceFeature implements FeatureLocationI
    */
   private String source;
 
+  // for Overview sort:
+  public int index;
+
+  public SequenceFeature containedBy;
+
   /**
    * Constructs a duplicate feature. Note: Uses makes a shallow copy of the
    * otherDetails map, so the new and original SequenceFeature may reference the
index 013897d..0b26564 100755 (executable)
@@ -609,12 +609,23 @@ public interface SequenceI extends ASequenceI
   public void resetColors();
 
   /**
+   * allows passing the result ArrayList as a parameter to avoid unnecessary
+   * construction
+   * 
    * @author Bob Hanson 2019.07.30
    * 
-   * allows passing the result ArrayList as a parameter to avoid unnecessary construction
    * 
    */
-  void findFeatures(int column, String type, List<SequenceFeature> result);
+  List<SequenceFeature> findFeatures(int column, String type,
+          List<SequenceFeature> result);
+
+  /**
+   * allows early intervention for renderer if false
+   * 
+   * @author Bob Hanson 2019.07.30
+   * 
+   */
+  public boolean hasFeatures(String type);
 
 }
 
index 686ac26..bd6bd1e 100644 (file)
@@ -23,7 +23,10 @@ package jalview.datamodel.features;
 import jalview.datamodel.SequenceFeature;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -32,6 +35,7 @@ import intervalstore.api.IntervalStoreI;
 import intervalstore.impl.BinarySearcher;
 import intervalstore.impl.IntervalStore;
 
+
 /**
  * A data store for a set of sequence features that supports efficient lookup of
  * features overlapping a given range. Intended for (but not limited to) storage
@@ -90,9 +94,7 @@ public class FeatureStore
 
   float nonPositionalMaxScore;
 
-  private SequenceFeature[] temp = new SequenceFeature[3];
-
-  private boolean isTainted;
+  private ArrayList<SequenceFeature> featuresList;
 
   /**
    * Constructor
@@ -100,6 +102,7 @@ public class FeatureStore
   public FeatureStore()
   {
     features = new IntervalStore<>();
+    featuresList = new ArrayList<>();
     positionalFeatureGroups = new HashSet<>();
     nonPositionalFeatureGroups = new HashSet<>();
     positionalMinScore = Float.NaN;
@@ -118,6 +121,7 @@ public class FeatureStore
    * 
    * @param feature
    */
+
   public boolean addFeature(SequenceFeature feature)
   {
     if (contains(feature))
@@ -186,18 +190,17 @@ public class FeatureStore
   {
     if (feature.isNonPositional())
     {
-      return nonPositionalFeatures == null ? false : nonPositionalFeatures
-              .contains(feature);
+      return nonPositionalFeatures == null ? false
+              : nonPositionalFeatures.contains(feature);
     }
 
     if (feature.isContactFeature())
     {
-      return contactFeatureStarts == null ? false : listContains(
-              contactFeatureStarts, feature);
+      return contactFeatureStarts == null ? false
+              : listContains(contactFeatureStarts, feature);
     }
 
-    return features == null ? false : features
-            .contains(feature);
+    return features == null ? false : features.contains(feature);
   }
 
   /**
@@ -254,7 +257,7 @@ public class FeatureStore
       features = new IntervalStore<>();
     }
     features.add(feature);
-    isTainted = true;
+    featuresList.add(feature);
   }
 
   /**
@@ -285,7 +288,6 @@ public class FeatureStore
             f -> f.getBegin() >= feature.getBegin());
     contactFeatureStarts.add(insertPosition, feature);
 
-
     /*
      * insert into list sorted by end (second contact position):
      * binary search the sorted list to find the insertion point
@@ -349,6 +351,7 @@ public class FeatureStore
    *          end position of overlap range (inclusive)
    * @return
    */
+
   public List<SequenceFeature> findOverlappingFeatures(long start, long end)
   {
     List<SequenceFeature> result = new ArrayList<>();
@@ -408,8 +411,8 @@ public class FeatureStore
       SequenceFeature sf = contactFeatureEnds.get(index);
       if (!sf.isContactFeature())
       {
-        System.err.println("Error! non-contact feature type "
-                + sf.getType() + " in contact features list");
+        System.err.println("Error! non-contact feature type " + sf.getType()
+                + " in contact features list");
         index++;
         continue;
       }
@@ -488,6 +491,7 @@ public class FeatureStore
    * 
    * @return
    */
+
   public List<SequenceFeature> getPositionalFeatures()
   {
     List<SequenceFeature> result = new ArrayList<>();
@@ -517,6 +521,7 @@ public class FeatureStore
    * 
    * @return
    */
+
   public List<SequenceFeature> getContactFeatures()
   {
     if (contactFeatureStarts == null)
@@ -532,6 +537,7 @@ public class FeatureStore
    * 
    * @return
    */
+
   public List<SequenceFeature> getNonPositionalFeatures()
   {
     if (nonPositionalFeatures == null)
@@ -549,6 +555,7 @@ public class FeatureStore
    * 
    * @param sf
    */
+
   public synchronized boolean delete(SequenceFeature sf)
   {
     boolean removed = false;
@@ -583,6 +590,7 @@ public class FeatureStore
     if (!removed && features != null)
     {
       removed = features.remove(sf);
+      featuresList.remove(sf);
     }
 
     if (removed)
@@ -607,7 +615,6 @@ public class FeatureStore
     positionalMaxScore = Float.NaN;
     nonPositionalMinScore = Float.NaN;
     nonPositionalMaxScore = Float.NaN;
-    isTainted = true;
     /*
      * scan non-positional features for groups and scores
      */
@@ -677,13 +684,13 @@ public class FeatureStore
    * 
    * @return
    */
+
   public boolean isEmpty()
   {
     boolean hasFeatures = (contactFeatureStarts != null
-            && !contactFeatureStarts
-                    .isEmpty())
-            || (nonPositionalFeatures != null && !nonPositionalFeatures
-                    .isEmpty())
+            && !contactFeatureStarts.isEmpty())
+            || (nonPositionalFeatures != null
+                    && !nonPositionalFeatures.isEmpty())
             || (features != null && features.size() > 0);
 
     return !hasFeatures;
@@ -697,6 +704,7 @@ public class FeatureStore
    * @param positionalFeatures
    * @return
    */
+
   public Set<String> getFeatureGroups(boolean positionalFeatures)
   {
     if (positionalFeatures)
@@ -705,9 +713,9 @@ public class FeatureStore
     }
     else
     {
-      return nonPositionalFeatureGroups == null ? Collections
-              .<String> emptySet() : Collections
-              .unmodifiableSet(nonPositionalFeatureGroups);
+      return nonPositionalFeatureGroups == null
+              ? Collections.<String> emptySet()
+              : Collections.unmodifiableSet(nonPositionalFeatureGroups);
     }
   }
 
@@ -718,12 +726,13 @@ public class FeatureStore
    * @param positional
    * @return
    */
+
   public int getFeatureCount(boolean positional)
   {
     if (!positional)
     {
-      return nonPositionalFeatures == null ? 0 : nonPositionalFeatures
-              .size();
+      return nonPositionalFeatures == null ? 0
+              : nonPositionalFeatures.size();
     }
 
     int size = 0;
@@ -748,6 +757,7 @@ public class FeatureStore
    * 
    * @return
    */
+
   public int getTotalFeatureLength()
   {
     return totalExtent;
@@ -761,6 +771,7 @@ public class FeatureStore
    * @param positional
    * @return
    */
+
   public float getMinimumScore(boolean positional)
   {
     return positional ? positionalMinScore : nonPositionalMinScore;
@@ -774,6 +785,7 @@ public class FeatureStore
    * @param positional
    * @return
    */
+
   public float getMaximumScore(boolean positional)
   {
     return positional ? positionalMaxScore : nonPositionalMaxScore;
@@ -787,6 +799,7 @@ public class FeatureStore
    * @param group
    * @return
    */
+
   public List<SequenceFeature> getFeaturesForGroup(boolean positional,
           String group)
   {
@@ -807,8 +820,8 @@ public class FeatureStore
     for (SequenceFeature sf : sfs)
     {
       String featureGroup = sf.getFeatureGroup();
-      if (group == null && featureGroup == null || group != null
-              && group.equals(featureGroup))
+      if (group == null && featureGroup == null
+              || group != null && group.equals(featureGroup))
       {
         result.add(sf);
       }
@@ -825,6 +838,7 @@ public class FeatureStore
    * @param shiftBy
    * @return
    */
+
   public synchronized boolean shiftFeatures(int fromPosition, int shiftBy)
   {
     /*
@@ -859,34 +873,30 @@ public class FeatureStore
 
   /**
    * Find all features containing this position.
-   * Uses isTainted field to know when to reconstruct its temporary array.
    * 
    * @param pos
    * @return list of SequenceFeatures
    * @author Bob Hanson 2019.07.30
    */
-  public void findOverlappingFeatures(int pos, List<SequenceFeature> result)
+
+  public List<SequenceFeature> findOverlappingFeatures(int pos,
+          List<SequenceFeature> result)
   {
+    if (result == null)
+    {
+      result = new ArrayList<>();
+    }
 
     if (contactFeatureStarts != null)
     {
       findContacts(contactFeatureStarts, pos, result, true);
       findContacts(contactFeatureEnds, pos, result, false);
     }
-    if (features != null)
+    if (featuresList != null)
     {
-      int n = features.size();
-      if (isTainted)
-      {
-        isTainted = false;
-        if (temp.length < n)
-        {
-          temp = new SequenceFeature[n << 1];
-        }
-        features.toArray(temp);
-      }
-      findOverlaps(temp, n, pos, result);
+      findOverlaps(featuresList, pos, result);
     }
+    return result;
   }
 
   /**
@@ -935,28 +945,173 @@ public class FeatureStore
     }
   }
 
+  BitSet bs = new BitSet();
+
   /**
-   * Brute force point-interval overlap test
+   * Double binary sort with bitset correlation
+   * 
    * 
    * @param features
-   * @param n
    * @param pos
    * @param result
    */
-  private static void findOverlaps(SequenceFeature[] features, int n,
-          int pos,
+  private void findOverlaps(List<SequenceFeature> features, int pos,
           List<SequenceFeature> result)
   {
-    // BH I know, brute force. We need a single-position overlap
-    // method for IntervalStore, I think.
-    for (int i = n; --i >= 0;)
+    int n = featuresList.size();
+    if (n == 1)
+    {
+      checkOne(featuresList.get(0), pos, result);
+      return;
+    }
+    if (orderedFeatureStarts == null)
     {
-      SequenceFeature f = features[i];
-      if (f.begin <= pos && f.end >= pos)
+      rebuildArrays(n);
+    }
+    SequenceFeature sf = findClosestFeature(orderedFeatureStarts, pos);
+    while (sf != null) {
+      if (sf.end >= pos)
       {
-        result.add(f);
+        result.add(sf);
       }
+      sf = sf.containedBy;
     }
   }
 
+  private void linkFeatures(SequenceFeature[] intervals)
+  {
+    if (intervals.length < 2)
+    {
+      return;
+    }
+    int maxEnd = intervals[0].end;
+    for (int i = 1, n = intervals.length; i < n; i++)
+    {
+      SequenceFeature ithis = intervals[i];
+      if (ithis.begin <= maxEnd)
+      {
+        ithis.containedBy = getContainedBy(intervals[i - 1], ithis);
+      }
+      if (ithis.end > maxEnd)
+      {
+        maxEnd = ithis.end;
+      }
+    }
+  }
+
+  private SequenceFeature getContainedBy(SequenceFeature sf,
+          SequenceFeature sf0)
+  {
+    int begin = sf0.begin;
+    while (sf != null)
+    {
+      if (begin <= sf.end)
+      {
+        System.out.println("\nFS found " + sf0.index + ":" + sf0
+                + "\nFS in    " + sf.index + ":" + sf);
+        return sf;
+      }
+      sf = sf.containedBy;
+    }
+    return null;
+  }
+
+  private SequenceFeature findClosestFeature(SequenceFeature[] l, int pos)
+  {
+    int low = 0;
+    int high = l.length - 1;
+    while (low <= high)
+    {
+      int mid = (low + high) >>> 1;
+      SequenceFeature f = l[mid];
+      switch (Long.signum(f.begin - pos))
+      {
+      case -1:
+        low = mid + 1;
+        continue;
+      case 1:
+        high = mid - 1;
+        continue;
+      case 0:
+
+        while (++mid <= high && l[mid].begin == pos)
+          {
+            ;
+          }
+          mid--;
+        return l[mid];
+      }
+    }
+    // -1 here?
+    return (high < 0 || low >= l.length ? null : l[high]);
+  }
+
+  private void checkOne(SequenceFeature sf, int pos,
+          List<SequenceFeature> result)
+  {
+    if (sf.begin <= pos && sf.end >= pos)
+    {
+      result.add(sf);
+    }
+    return;
+  }
+
+  /*
+   * contact features ordered by first contact position
+   */
+  private SequenceFeature[] orderedFeatureStarts;
+
+  private void rebuildArrays(int n)
+  {
+    if (startComp == null)
+    {
+      startComp = new StartComparator();
+    }
+    orderedFeatureStarts = new SequenceFeature[n];
+
+    for (int i = n; --i >= 0;)
+    {
+      SequenceFeature sf = featuresList.get(i);
+      sf.index = i;
+      orderedFeatureStarts[i] = sf;
+    }
+    Arrays.sort(orderedFeatureStarts, startComp);
+    linkFeatures(orderedFeatureStarts);
+  }
+
+  class StartComparator implements Comparator<SequenceFeature>
+  {
+
+    int pos;
+
+    @Override
+    public int compare(SequenceFeature o1, SequenceFeature o2)
+    {
+      int p1 = o1.begin;
+      int p2 = o2.begin;
+      return (p1 < p2 ? -1 : p1 > p2 ? 1 : 0);
+    }
+
+  }
+
+  static StartComparator startComp;
+
+  // class EndComparator implements Comparator<SequenceFeature>
+  // {
+  //
+  // int pos;
+  //
+  // @Override
+  // public int compare(SequenceFeature o1, SequenceFeature o2)
+  // {
+  // int p1 = o1.end;
+  // int p2 = o2.end;
+  // int val = (p1 < p2 ? 1 : p1 > p2 ? -1 : 0);
+  // return val;
+  // }
+  //
+  // }
+  //
+  // static EndComparator endComp;
+
 }
index 93ee71b..0d29184 100644 (file)
@@ -468,17 +468,66 @@ public class SequenceFeatures implements SequenceFeaturesI
   /**
    * Simplified find for features associated with a given position.
    * 
+   * JavaScript set to not use IntervalI, but easily testable by setting false
+   * to true in javadoc
+   * 
+   * FeatureRenderer has checked already that featureStore does contain type.
+   * 
    * @author Bob Hanson 2019.07.30
    */
   @Override
-  public void findFeatures(int pos, String type, List<SequenceFeature> list)
+  public List<SequenceFeature> findFeatures(int pos, String type,
+          List<SequenceFeature> list)
   {
     FeatureStore fs = featureStore.get(type);
-    if (fs != null)
-    {
-      fs.findOverlappingFeatures(pos, list);
-    }
+    boolean useIntervalStore = /**
+                                * @j2sNative false &&
+                                */
+            true;
+    return (useIntervalStore ? fs.findOverlappingFeatures(pos, pos)
+            : fs.findOverlappingFeatures(pos, list));
   }
 
+  // Chrome; developer console closed
+
+  // BH 2019.08.01 useIntervalStore true, redraw false:
+  // Platform: timer mark 13.848 0.367 overviewrender 16000 pixels row:14
+  // Platform: timer mark 15.391 0.39 overviewrender 16000 pixels row:14
+  // Platform: timer mark 16.498 0.39 overviewrender 16000 pixels row:14
+  // Platform: timer mark 17.596 0.401 overviewrender 16000 pixels row:14
+  // Platform: timer mark 18.738 0.363 overviewrender 16000 pixels row:14
+  // Platform: timer mark 19.659 0.358 overviewrender 16000 pixels row:14
+  // Platform: timer mark 20.737 0.359 overviewrender 16000 pixels row:14
+  // Platform: timer mark 21.797 0.391 overviewrender 16000 pixels row:14
+  // Platform: timer mark 22.851 0.361 overviewrender 16000 pixels row:14
+  // Platform: timer mark 24.019 0.395 overviewrender 16000 pixels row:14
+
+  // BH 2019.08.01 useIntervalStore false, redraw false:
+  // Platform: timer mark 19.011 0.181 overviewrender 16000 pixels row:14
+  // Platform: timer mark 20.311 0.183 overviewrender 16000 pixels row:14
+  // Platform: timer mark 21.368 0.175 overviewrender 16000 pixels row:14
+  // Platform: timer mark 22.347 0.178 overviewrender 16000 pixels row:14
+  // Platform: timer mark 23.605 0.216 overviewrender 16000 pixels row:14
+  // Platform: timer mark 24.836 0.191 overviewrender 16000 pixels row:14
+  // Platform: timer mark 26.016 0.181 overviewrender 16000 pixels row:14
+  // Platform: timer mark 27.278 0.178 overviewrender 16000 pixels row:14
+  // Platform: timer mark 28.158 0.181 overviewrender 16000 pixels row:14
+  // Platform: timer mark 29.227 0.196 overviewrender 16000 pixels row:14
+  // Platform: timer mark 30.1 0.171 overviewrender 16000 pixels row:14
+  // Platform: timer mark 31.684 0.196 overviewrender 16000 pixels row:14
+  // Platform: timer mark 32.779 0.18 overviewrender 16000 pixels row:14
+  // Platform: timer mark 52.355 0.185 overviewrender 16000 pixels row:14
+  // Platform: timer mark 53.829 0.186 overviewrender 16000 pixels row:14
+
+
+
+  /**
+   * @author Bob Hanson 2019.08.01
+   */
+  @Override
+  public boolean hasFeatures(String type)
+  {
+    return featureStore.containsKey(type);
+  }
 
 }
index ec4b38c..deed751 100644 (file)
@@ -229,5 +229,22 @@ public interface SequenceFeaturesI
    */
   void deleteAll();
 
-  void findFeatures(int pos, String type, List<SequenceFeature> result);
+  /**
+   * Point-specific parameter return for JavaScript
+   * 
+   * @param pos
+   * @param type
+   * @param result
+   * @return result (JavaScript) or new ArrayList (Java -- see FeatureRender)
+   * @author Bob Hanson 2019.07.30
+   */
+  List<SequenceFeature> findFeatures(int pos, String type, List<SequenceFeature> result);
+
+  /**
+   * @author Bob Hanson 2019.08.01
+   * 
+   * @param type
+   * @return true if this type is in featureStore
+   */
+  boolean hasFeatures(String type);
 }
index 22c75c3..36b5847 100644 (file)
@@ -30,7 +30,6 @@ import jalview.datamodel.Annotation;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
 import jalview.renderer.seqfeatures.FeatureColourFinder;
-import jalview.renderer.seqfeatures.FeatureRenderer;
 import jalview.util.Platform;
 import jalview.viewmodel.OverviewDimensions;
 
@@ -97,21 +96,32 @@ public class OverviewRenderer
 
   private AlignmentViewPanel panel;
 
-  private int sequencesHeight;
+  // private int sequencesHeight;
 
-  public OverviewRenderer(AlignmentViewPanel panel, FeatureRenderer fr,
-          OverviewDimensions od,
-          AlignmentI alignment,
-          ResidueShaderI resshader, OverviewResColourFinder colFinder)
+  public OverviewRenderer(AlignmentViewPanel panel,
+          jalview.api.FeatureRenderer fr, OverviewDimensions od,
+          AlignmentI alignment, ResidueShaderI resshader,
+          OverviewResColourFinder colFinder)
   {
     this(panel, fr, od, alignment, resshader, colFinder, true);
   }
 
+  /**
+   * @param panel
+   * @param fr
+   * @param od
+   * @param alignment
+   * @param resshader
+   * @param colFinder
+   * @param shwoProgress
+   *          possibly not, in JavaScript and for testng
+   */
   public OverviewRenderer(AlignmentViewPanel panel,
           jalview.api.FeatureRenderer fr, OverviewDimensions od,
           AlignmentI alignment, ResidueShaderI resshader,
           OverviewResColourFinder colFinder, boolean showProgress)
   {
+    {
     this.panel = panel;
     finder = new FeatureColourFinder(fr);
     al = alignment;
@@ -129,7 +139,7 @@ public class OverviewRenderer
     pixelsPerSeq = od.getPixelsPerSeq();
     pixelsPerCol = od.getPixelsPerCol();
     colsPerPixel = Math.max(1, 1f / pixelsPerCol);
-
+    }
   }
 
   final static int STATE_INIT = 0;
@@ -224,9 +234,9 @@ public class OverviewRenderer
     miniMe = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
     WritableRaster raster = miniMe.getRaster();
     DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
-    Platform.timeCheck(null, Platform.TIME_MARK);
     pixels = db.getBankData()[0];
     bscol = cols.getOverviewBitSet();
+    Platform.timeCheck(null, Platform.TIME_MARK);
   }
 
   private void nextRow()
@@ -327,6 +337,16 @@ public class OverviewRenderer
             w);
   }
 
+  private ActionListener listener = new ActionListener()
+  {
+    @Override
+    public void actionPerformed(ActionEvent e)
+    {
+      mainLoop();
+    }
+
+  };
+
   private boolean loop()
   {
     if (delay <= 0)
@@ -335,15 +355,7 @@ public class OverviewRenderer
     }
     if (timer == null)
     {
-      timer = new Timer(delay, new ActionListener()
-      {
-        @Override
-        public void actionPerformed(ActionEvent e)
-        {
-          mainLoop();
-        }
-
-      });
+      timer = new Timer(delay, listener);
       timer.setRepeats(false);
       timer.start();
     }
@@ -356,10 +368,13 @@ public class OverviewRenderer
 
   private void done()
   {
-    Platform.timeCheck(
-            "overviewrender " + ndone + " pixels row:" + row + " redraw:"
-                    + redraw,
-            Platform.TIME_MARK);
+    if (!redraw)
+    {
+      Platform.timeCheck(
+              "overviewrender " + ndone + " pixels row:" + row + " redraw:"
+                      + redraw,
+              Platform.TIME_MARK);
+    }
 
     overlayHiddenRegions();
     if (showProgress)
index bed72c5..ee5ffe6 100644 (file)
@@ -91,11 +91,13 @@ public class FeatureRenderer extends FeatureRendererModel
     int pady = (y1 + charHeight) - charHeight / 5;
 
     FontMetrics fm = g.getFontMetrics();
+    char s = '\0';
     for (int i = featureStart; i <= featureEnd; i++)
     {
-      char s = seq.getCharAt(i);
 
-      if (Comparison.isGap(s))
+      // colourOnly is just for Overview -- no need to check this again
+
+      if (!colourOnly && Comparison.isGap(s = seq.getCharAt(i)))
       {
         continue;
       }
@@ -104,7 +106,12 @@ public class FeatureRenderer extends FeatureRendererModel
 
       g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
 
-      if (colourOnly || !validCharWidth)
+      if (colourOnly)
+      {
+        return true;
+      }
+
+      if (!validCharWidth)
       {
         continue;
       }
@@ -118,6 +125,9 @@ public class FeatureRenderer extends FeatureRendererModel
   }
 
   /**
+   * 
+   * BH - this method is never called?
+   * 
    * Renders the sequence using the given SCORE feature colour between the given
    * start and end columns. Returns true if at least one column is drawn, else
    * false (the feature range does not overlap the start and end positions).
@@ -212,10 +222,12 @@ public class FeatureRenderer extends FeatureRendererModel
   @Override
   public Color findFeatureColour(SequenceI seq, int column, Graphics g)
   {
-    if (!av.isShowSequenceFeatures())
-    {
-      return null;
-    }
+    // BH 2019.08.01
+    // this is already checked in FeatureColorFinder
+    // if (!av.isShowSequenceFeatures())
+    // {
+    // return null;
+    // }
 
     // column is 'base 1' but getCharAt is an array index (ie from 0)
     if (Comparison.isGap(seq.getCharAt(column - 1)))
@@ -253,7 +265,8 @@ public class FeatureRenderer extends FeatureRendererModel
    * applies), or null if no feature is drawn in the range given.
    * 
    * @param g
-   *          the graphics context to draw on (may be null if colourOnly==true)
+   *          the graphics context to draw on (may be null only if t == 1 from
+   *          colourOnly==true)
    * @param seq
    * @param start
    *          start column
@@ -270,21 +283,27 @@ public class FeatureRenderer extends FeatureRendererModel
           final SequenceI seq, int start, int end, int y1,
           boolean colourOnly)
   {
+    // from SeqCanvas and OverviewRender
     /*
      * if columns are all gapped, or sequence has no features, nothing to do
      */
-    ContiguousI visiblePositions = seq.findPositions(start + 1, end + 1);
-    if (visiblePositions == null || !seq.getFeatures().hasFeatures())
+    ContiguousI visiblePositions;
+    if (!seq.getFeatures().hasFeatures() || (visiblePositions = seq
+            .findPositions(start + 1, end + 1)) == null)
     {
       return null;
     }
 
+    int vp0 = visiblePositions.getBegin();
+    int vp1 = visiblePositions.getEnd();
+
     updateFeatures();
 
-    if (transparency != 1f && g != null)
+    if (transparency != 1f) // g cannot be null here if trans == 1f - BH // && g
+                            // != null)
     {
-      Graphics2D g2 = (Graphics2D) g;
-      g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
+      ((Graphics2D) g).setComposite(
+              AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
               transparency));
     }
 
@@ -293,25 +312,28 @@ public class FeatureRenderer extends FeatureRendererModel
     /*
      * iterate over features in ordering of their rendering (last is on top)
      */
-    for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++)
+    for (int renderIndex = 0, n = renderOrder.length; renderIndex < n; renderIndex++)
     {
       String type = renderOrder[renderIndex];
-      if (!showFeatureOfType(type))
+      if (!seq.hasFeatures(type) || !showFeatureOfType(type))
       {
         continue;
       }
 
       FeatureColourI fc = getFeatureStyle(type);
-      List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(
-              visiblePositions.getBegin(), visiblePositions.getEnd(), type);
+      List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(vp0,
+              vp1, type);
 
-      if (fc.isSimpleColour())
+      // colourOnly (i.e. Overview) can only be here if translucent, so
+      // there is no need to check for filtering
+      if (!colourOnly && fc.isSimpleColour())
       {
         filterFeaturesForDisplay(overlaps);
       }
 
-      for (SequenceFeature sf : overlaps)
+      for (int i = overlaps.size(); --i >= 0;)
       {
+        SequenceFeature sf = overlaps.get(i);
         Color featureColour = getColor(sf, fc);
         if (featureColour == null)
         {
@@ -326,22 +348,22 @@ public class FeatureRenderer extends FeatureRendererModel
          * restrict to visible positions (or if a contact feature,
          * to a single position)
          */
-        int visibleStart = sf.getBegin();
-        if (visibleStart < visiblePositions.getBegin())
+        int sf0 = sf.getBegin();
+        int sf1 = sf.getEnd();
+        int visibleStart = sf0;
+        if (visibleStart < vp0)
         {
-          visibleStart = sf.isContactFeature() ? sf.getEnd()
-                  : visiblePositions.getBegin();
+          visibleStart = sf.isContactFeature() ? sf1 : vp0;
         }
-        int visibleEnd = sf.getEnd();
-        if (visibleEnd > visiblePositions.getEnd())
+        int visibleEnd = sf1;
+        if (visibleEnd > vp1)
         {
-          visibleEnd = sf.isContactFeature() ? sf.getBegin()
-                  : visiblePositions.getEnd();
+          visibleEnd = sf.isContactFeature() ? sf0 : vp1;
         }
 
         int featureStartCol = seq.findIndex(visibleStart);
-        int featureEndCol = sf.begin == sf.end ? featureStartCol : seq
-                .findIndex(visibleEnd);
+        int featureEndCol = (sf.begin == sf.end ? featureStartCol
+                : seq.findIndex(visibleEnd));
 
         // Color featureColour = getColour(sequenceFeature);
 
@@ -382,26 +404,24 @@ public class FeatureRenderer extends FeatureRendererModel
           else
           {
           */
-            boolean drawn = renderFeature(g, seq,
-                    featureStartCol - 1,
-                    featureEndCol - 1, featureColour,
-                    start, end, y1, colourOnly);
-            if (drawn)
-            {
-              drawnColour = featureColour;
-            }
+          boolean drawn = renderFeature(g, seq, featureStartCol - 1,
+                  featureEndCol - 1, featureColour, start, end, y1,
+                  colourOnly);
+          if (drawn)
+          {
+            drawnColour = featureColour;
+          }
           /*}*/
         }
       }
     }
 
-    if (transparency != 1.0f && g != null)
+    if (transparency != 1.0f)
     {
       /*
        * reset transparency
        */
-      Graphics2D g2 = (Graphics2D) g;
-      g2.setComposite(NO_TRANSPARENCY);
+      ((Graphics2D) g).setComposite(NO_TRANSPARENCY);
     }
 
     return drawnColour;
@@ -418,7 +438,9 @@ public class FeatureRenderer extends FeatureRendererModel
     findAllFeatures();
   }
 
-  private List<SequenceFeature> overlaps = new ArrayList<>();
+  @SuppressWarnings("unused")
+  private List<SequenceFeature> overlaps = (/** @j2sNative true || */
+  false ? null : new ArrayList<>());
 
   /**
    * Returns the sequence feature colour rendered at the given column position,
@@ -431,9 +453,10 @@ public class FeatureRenderer extends FeatureRendererModel
    * colour for features enclosing a gapped column. Check for gap before calling
    * if different behaviour is wanted.
    * 
-   * BH 2019.07.30 
+   * BH 2019.07.30
    * 
-   * Adds a result ArrayList to parameters in order to avoid an unnecessary construction of that for every pixel checked.
+   * Adds a result ArrayList to parameters in order to avoid an unnecessary
+   * construction of that for every pixel checked.
    * 
    * 
    * @param seq
@@ -452,28 +475,32 @@ public class FeatureRenderer extends FeatureRendererModel
      * inspect features in reverse renderOrder (the last in the array is 
      * displayed on top) until we find one that is rendered at the position
      */
-    for (int renderIndex = renderOrder.length
-            - 1; renderIndex >= 0; renderIndex--)
+    for (int renderIndex = renderOrder.length; --renderIndex >= 0;)
     {
       String type = renderOrder[renderIndex];
-      if (!showFeatureOfType(type))
+      if (!seq.hasFeatures(type) || !showFeatureOfType(type))
       {
         continue;
       }
 
-      overlaps.clear();
-      seq.findFeatures(column, type, overlaps);
-      if (overlaps.size() > 0)
+      if (overlaps != null)
+      {
+        overlaps.clear();
+      }
+      List<SequenceFeature> list = seq.findFeatures(column, type, overlaps);
+      if (list.size() > 0)
       {
-        for (SequenceFeature sequenceFeature : overlaps)
+        for (int i = 0, n = list.size(); i < n; i++)
         {
-          if (!featureGroupNotShown(sequenceFeature))
+          SequenceFeature sf = list.get(i);
+          if (featureGroupNotShown(sf))
           {
-            Color col = getColour(sequenceFeature);
-            if (col != null)
-            {
-              return col;
-            }
+            continue;
+          }
+          Color col = getColour(sf);
+          if (col != null)
+          {
+            return col;
           }
         }
       }
index 6bf1f45..8576482 100644 (file)
@@ -988,10 +988,8 @@ public abstract class FeatureRendererModel
   {
     return featureGroups != null
             && sequenceFeature.featureGroup != null
-            && sequenceFeature.featureGroup.length() != 0
-            && featureGroups.containsKey(sequenceFeature.featureGroup)
-            && !featureGroups.get(sequenceFeature.featureGroup)
-                    .booleanValue();
+            && sequenceFeature.featureGroup.length() > 0 && featureGroups
+                    .get(sequenceFeature.featureGroup) == Boolean.FALSE;
   }
 
   /**