JAL-2754 Sequence.findFeatures(fromCol, toCol) features/JAL-2754findFeaturesByColumn
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 1 Jun 2017 13:35:10 +0000 (14:35 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 1 Jun 2017 13:35:10 +0000 (14:35 +0100)
17 files changed:
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/scoremodels/FeatureDistanceModel.java
src/jalview/api/FeatureRenderer.java
src/jalview/appletgui/SeqPanel.java
src/jalview/controller/AlignViewController.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/SeqPanel.java
src/jalview/renderer/seqfeatures/FeatureColourFinder.java
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/workers/ColumnCounterSetWorker.java
test/jalview/analysis/scoremodels/FeatureDistanceModelTest.java
test/jalview/datamodel/SequenceTest.java
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java
test/jalview/renderer/seqfeatures/FeatureRendererTest.java

index bddf6e3..9943a22 100755 (executable)
@@ -755,13 +755,10 @@ public class AlignmentSorter
        * get sequence residues overlapping column region
        * and features for residue positions and specified types
        */
-      // TODO new method findPositions(startCol, endCol)? JAL-2544
-      int startResidue = seqs[i].findPosition(startCol);
-      int endResidue = seqs[i].findPosition(endCol);
       String[] types = featureTypes == null ? null : featureTypes
               .toArray(new String[featureTypes.size()]);
-      List<SequenceFeature> sfs = seqs[i].findFeatures(startResidue,
-              endResidue, types);
+      List<SequenceFeature> sfs = seqs[i].findFeatures(startCol + 1,
+              endCol + 1, types);
 
       seqScores[i] = 0;
       scores[i] = 0.0;
@@ -772,18 +769,6 @@ public class AlignmentSorter
         SequenceFeature sf = it.next();
 
         /*
-         * double-check feature overlaps columns (JAL-2544)
-         * (could avoid this with a findPositions(fromCol, toCol) method)
-         * findIndex returns base 1 column values, startCol/endCol are base 0
-         */
-        if (seqs[i].findIndex(sf.getBegin()) > endCol + 1
-                || seqs[i].findIndex(sf.getEnd()) < startCol + 1)
-        {
-          it.remove();
-          continue;
-        }
-
-        /*
          * accept all features with null or empty group, otherwise
          * check group is one of the currently visible groups
          */
index 056ecdb..ddbaf73 100644 (file)
@@ -177,10 +177,12 @@ public class FeatureDistanceModel extends DistanceScoreModel
   /**
    * Builds and returns a map containing a (possibly empty) list (one per
    * SeqCigar) of visible feature types at the given column position. The map
-   * has no entry for sequences which are gapped at the column position.
+   * does not include entries for features which straddle a gapped column
+   * positions.
    * 
    * @param seqs
    * @param columnPosition
+   *          (0..)
    * @return
    */
   protected Map<SeqCigar, Set<String>> findFeatureTypesAtColumn(
@@ -192,9 +194,12 @@ public class FeatureDistanceModel extends DistanceScoreModel
       int spos = seq.findPosition(columnPosition);
       if (spos != -1)
       {
+        /*
+         * position is not a gap
+         */
         Set<String> types = new HashSet<String>();
-        List<SequenceFeature> sfs = fr.findFeaturesAtRes(seq.getRefSeq(),
-                spos);
+        List<SequenceFeature> sfs = fr.findFeaturesAtResidue(
+                seq.getRefSeq(), spos);
         for (SequenceFeature sf : sfs)
         {
           types.add(sf.getType());
index edd236b..9d2d7f4 100644 (file)
@@ -60,6 +60,7 @@ public interface FeatureRenderer
    * 
    * @param sequence
    * @param column
+   *          aligned column position (1..)
    * @param g
    * @return
    */
@@ -147,14 +148,27 @@ public interface FeatureRenderer
   void setGroupVisibility(String group, boolean visible);
 
   /**
-   * Returns features at the specified position on the given sequence.
+   * Returns features at the specified aligned column on the given sequence.
+   * Non-positional features are not included. If the column has a gap, then
+   * enclosing features are included (but not contact features).
+   * 
+   * @param sequence
+   * @param column
+   *          aligned column position (1..)
+   * @return
+   */
+  List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column);
+
+  /**
+   * Returns features at the specified residue position on the given sequence.
    * Non-positional features are not included.
    * 
    * @param sequence
-   * @param res
+   * @param resNo
+   *          residue position (start..)
    * @return
    */
-  List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res);
+  List<SequenceFeature> findFeaturesAtResidue(SequenceI sequence, int resNo);
 
   /**
    * get current displayed types, in ordering of rendering (on top last)
@@ -200,4 +214,5 @@ public interface FeatureRenderer
    * @return
    */
   float getTransparency();
+
 }
index fe7f115..9e31efe 100644 (file)
@@ -56,7 +56,6 @@ import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.util.Collections;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Vector;
 
 public class SeqPanel extends Panel implements MouseMotionListener,
@@ -530,7 +529,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
 
     int seq = findSeq(evt);
-    int res = findRes(evt);
+    int res = findColumn(evt);
 
     if (seq < 0 || res < 0)
     {
@@ -566,14 +565,9 @@ public class SeqPanel extends Panel implements MouseMotionListener,
         av.setSelectionGroup(null);
       }
 
-      int column = findRes(evt);
-      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
-      List<SequenceFeature> features = findFeaturesAtRes(sequence,
-              sequence.findPosition(column));
-      if (isGapped)
-      {
-        removeAdjacentFeatures(features, column + 1, sequence);
-      }
+      int column = findColumn(evt);
+      List<SequenceFeature> features = findFeaturesAtColumn(sequence,
+              column + 1);
 
       if (!features.isEmpty())
       {
@@ -609,7 +603,14 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
   int wrappedBlock = -1;
 
-  int findRes(MouseEvent evt)
+  /**
+   * Returns the aligned sequence position (base 0) at the mouse position, or
+   * the closest visible one
+   * 
+   * @param evt
+   * @return
+   */
+  int findColumn(MouseEvent evt)
   {
     int res = 0;
     int x = evt.getX();
@@ -712,7 +713,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   {
 
     int seq = findSeq(evt);
-    int res = findRes(evt);
+    int res = findColumn(evt);
 
     if (seq < av.getAlignment().getHeight()
             && res < av.getAlignment().getSequenceAt(seq).getLength())
@@ -784,7 +785,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
   @Override
   public void mouseMoved(MouseEvent evt)
   {
-    final int column = findRes(evt);
+    final int column = findColumn(evt);
     int seq = findSeq(evt);
 
     if (seq >= av.getAlignment().getHeight() || seq < 0 || column < 0)
@@ -867,12 +868,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
      */
     if (av.isShowSequenceFeatures())
     {
-      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
-              respos);
-      if (isGapped)
-      {
-        removeAdjacentFeatures(allFeatures, column + 1, sequence);
-      }
+      List<SequenceFeature> allFeatures = findFeaturesAtColumn(sequence,
+              column + 1);
       for (SequenceFeature sf : allFeatures)
       {
         tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
@@ -904,38 +901,19 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     }
   }
 
-  List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
-  {
-    return seqCanvas.getFeatureRenderer().findFeaturesAtRes(sequence, res);
-  }
-
   /**
-   * Removes from the list of features any that start after, or end before, the
-   * given column position. This allows us to retain only those features
-   * adjacent to a gapped position that straddle the position. Contact features
-   * that 'straddle' the position are also removed, since they are not 'at' the
-   * position.
+   * Returns features at the specified aligned column on the given sequence.
+   * Non-positional features are not included. If the column has a gap, then
+   * enclosing features are included (but not contact features).
    * 
-   * @param features
-   * @param column
-   *          alignment column (1..)
    * @param sequence
+   * @param column
+   *          (1..)
+   * @return
    */
-  protected void removeAdjacentFeatures(List<SequenceFeature> features,
-          int column, SequenceI sequence)
+  List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column)
   {
-    // TODO should this be an AlignViewController method (shared by gui)?
-    ListIterator<SequenceFeature> it = features.listIterator();
-    while (it.hasNext())
-    {
-      SequenceFeature sf = it.next();
-      if (sf.isContactFeature()
-              || sequence.findIndex(sf.getBegin()) > column
-              || sequence.findIndex(sf.getEnd()) < column)
-      {
-        it.remove();
-      }
-    }
+    return seqCanvas.getFeatureRenderer().findFeaturesAtColumn(sequence, column);
   }
 
   Tooltip tooltip;
@@ -1015,7 +993,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       return;
     }
 
-    int res = findRes(evt);
+    int res = findColumn(evt);
 
     if (res < 0)
     {
@@ -1428,7 +1406,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
       scrollThread = null;
     }
 
-    int res = findRes(evt);
+    int column = findColumn(evt);
     int seq = findSeq(evt);
     oldSeq = seq;
     startWrapBlock = wrappedBlock;
@@ -1440,16 +1418,16 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
 
-    if (sequence == null || res > sequence.getLength())
+    if (sequence == null || column > sequence.getLength())
     {
       return;
     }
 
     stretchGroup = av.getSelectionGroup();
 
-    if (stretchGroup == null || !stretchGroup.contains(sequence, res))
+    if (stretchGroup == null || !stretchGroup.contains(sequence, column))
     {
-      stretchGroup = av.getAlignment().findGroup(sequence, res);
+      stretchGroup = av.getAlignment().findGroup(sequence, column);
       if (stretchGroup != null)
       {
         // only update the current selection if the popup menu has a group to
@@ -1461,8 +1439,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     // DETECT RIGHT MOUSE BUTTON IN AWT
     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
     {
-      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
-              sequence.findPosition(res));
+      List<SequenceFeature> allFeatures = findFeaturesAtColumn(sequence,
+              sequence.findPosition(column + 1));
 
       Vector<String> links = null;
       for (SequenceFeature sf : allFeatures)
@@ -1484,7 +1462,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
     if (av.cursorMode)
     {
-      seqCanvas.cursorX = findRes(evt);
+      seqCanvas.cursorX = findColumn(evt);
       seqCanvas.cursorY = findSeq(evt);
       seqCanvas.repaint();
       return;
@@ -1496,8 +1474,8 @@ public class SeqPanel extends Panel implements MouseMotionListener,
     {
       // define a new group here
       SequenceGroup sg = new SequenceGroup();
-      sg.setStartRes(res);
-      sg.setEndRes(res);
+      sg.setStartRes(column);
+      sg.setEndRes(column);
       sg.addSequence(sequence, false);
       av.setSelectionGroup(sg);
       stretchGroup = sg;
@@ -1555,7 +1533,7 @@ public class SeqPanel extends Panel implements MouseMotionListener,
 
   public void doMouseDraggedDefineMode(MouseEvent evt)
   {
-    int res = findRes(evt);
+    int res = findColumn(evt);
     int y = findSeq(evt);
 
     if (wrappedBlock != startWrapBlock)
index 33683fd..5c1f403 100644 (file)
@@ -232,72 +232,66 @@ public class AlignViewController implements AlignViewControllerI
   static int findColumnsWithFeature(String featureType,
           SequenceCollectionI sqcol, BitSet bs)
   {
-    final int startPosition = sqcol.getStartRes() + 1; // converted to base 1
-    final int endPosition = sqcol.getEndRes() + 1;
+    final int startColumn = sqcol.getStartRes() + 1; // converted to base 1
+    final int endColumn = sqcol.getEndRes() + 1;
     List<SequenceI> seqs = sqcol.getSequences();
     int nseq = 0;
     for (SequenceI sq : seqs)
     {
       if (sq != null)
       {
-        int ist = sq.findPosition(sqcol.getStartRes());
-        int iend = sq.findPosition(sqcol.getEndRes()); // see JAL-2526
-        List<SequenceFeature> sfs = sq.findFeatures(ist, iend, featureType);
-        boolean overlap = false;
-        for (SequenceFeature sf : sfs)
+        // int ist = sq.findPosition(sqcol.getStartRes());
+        List<SequenceFeature> sfs = sq.findFeatures(startColumn,
+                endColumn, featureType);
+
+        if (!sfs.isEmpty())
         {
-          // future functionality - featureType == null means mark columns
-          // containing all displayed features
-          if (sf != null && (featureType.equals(sf.getType())))
-          {
-            int sfStartCol = sq.findIndex(sf.getBegin());
-            int sfEndCol = sq.findIndex(sf.getEnd()); // inefficient - JAL-2526
+          nseq++;
+        }
 
-            if (sf.isContactFeature())
-            {
-              /*
-               * 'contact' feature - check for 'start' or 'end'
-               * position within the selected region
-               */
-              if (sfStartCol >= startPosition && sfStartCol <= endPosition)
-              {
-                bs.set(sfStartCol - 1);
-                overlap = true;
-              }
-              if (sfEndCol >= startPosition && sfEndCol <= endPosition)
-              {
-                bs.set(sfEndCol - 1);
-                overlap = true;
-              }
-              continue;
-            }
+        for (SequenceFeature sf : sfs)
+        {
+          int sfStartCol = sq.findIndex(sf.getBegin());
+          int sfEndCol = sq.findIndex(sf.getEnd());
 
+          if (sf.isContactFeature())
+          {
             /*
-             * contiguous feature - select feature positions (if any) 
-             * within the selected region
+             * 'contact' feature - check for 'start' or 'end'
+             * position within the selected region
              */
-            if (sfStartCol < startPosition)
-            {
-              sfStartCol = startPosition;
-            }
-            if (sfStartCol < ist)
+            if (sfStartCol >= startColumn && sfStartCol <= endColumn)
             {
-              sfStartCol = ist;
+              bs.set(sfStartCol - 1);
             }
-            if (sfEndCol > endPosition)
+            if (sfEndCol >= startColumn && sfEndCol <= endColumn)
             {
-              sfEndCol = endPosition;
-            }
-            for (; sfStartCol <= sfEndCol; sfStartCol++)
-            {
-              bs.set(sfStartCol - 1); // convert to base 0
-              overlap = true;
+              bs.set(sfEndCol - 1);
             }
+            continue;
+          }
+
+          /*
+           * contiguous feature - select feature positions (if any) 
+           * within the selected region
+           */
+          if (sfStartCol < startColumn)
+          {
+            sfStartCol = startColumn;
+          }
+          // not sure what the point of this is
+          // if (sfStartCol < ist)
+          // {
+          // sfStartCol = ist;
+          // }
+          if (sfEndCol > endColumn)
+          {
+            sfEndCol = endColumn;
+          }
+          for (; sfStartCol <= sfEndCol; sfStartCol++)
+          {
+            bs.set(sfStartCol - 1); // convert to base 0
           }
-        }
-        if (overlap)
-        {
-          nseq++;
         }
       }
     }
index dd7c24a..0055d0e 100755 (executable)
@@ -34,6 +34,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Vector;
 
 import com.stevesoft.pat.Regex;
@@ -700,68 +701,6 @@ public class Sequence extends ASequence implements SequenceI
   }
 
   /**
-   * {@inheritDoc}
-   */
-  @Override
-  public Range findPositions(int fromCol, int toCol)
-  {
-    /*
-     * count residues before fromCol
-     */
-    int j = 0;
-    int count = 0;
-    int seqlen = sequence.length;
-    while (j < fromCol && j < seqlen)
-    {
-      if (!Comparison.isGap(sequence[j]))
-      {
-        count++;
-      }
-      j++;
-    }
-
-    /*
-     * find first and last residues between fromCol and toCol
-     */
-    int firstPos = 0;
-    int lastPos = 0;
-    int firstPosCol = 0;
-    boolean foundFirst = false;
-    
-    while (j <= toCol && j < seqlen)
-    {
-      if (!Comparison.isGap(sequence[j]))
-      {
-        count++;
-        if (!foundFirst)
-        {
-          firstPos = count;
-          firstPosCol = j;
-          foundFirst = true;
-        }
-        lastPos = count;
-      }
-      j++;
-    }
-
-    if (firstPos == 0)
-    {
-      /*
-       * no residues in this range
-       */
-      return null;
-    }
-
-    /*
-     * adjust for sequence start coordinate
-     */
-    firstPos += start - 1;
-    lastPos += start - 1;
-
-    return new Range(firstPos, lastPos);
-  }
-
-  /**
    * Returns an int array where indices correspond to each residue in the
    * sequence and the element value gives its position in the alignment
    * 
@@ -1505,13 +1444,55 @@ public class Sequence extends ASequence implements SequenceI
    * {@inheritDoc}
    */
   @Override
-  public List<SequenceFeature> findFeatures(int from, int to,
+  public List<SequenceFeature> findFeatures(int fromColumn, int toColumn,
           String... types)
   {
+    int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0
+    int endPos = findPosition(toColumn - 1);
+
+    List<SequenceFeature> result = new ArrayList<>();
     if (datasetSequence != null)
     {
-      return datasetSequence.findFeatures(from, to, types);
+      result = datasetSequence.getFeatures().findFeatures(startPos, endPos,
+              types);
     }
-    return sequenceFeatureStore.findFeatures(from, to, types);
+    else
+    {
+      result = sequenceFeatureStore.findFeatures(startPos, endPos, types);
+    }
+
+    /*
+     * if the start or end column is gapped, startPos or endPos may be to the 
+     * left or right, and we may have included adjacent or enclosing features;
+     * remove any that are not enclosing, non-contact features
+     */
+    if (endPos > this.end || Comparison.isGap(sequence[fromColumn - 1])
+            || Comparison.isGap(sequence[toColumn - 1]))
+    {
+      ListIterator<SequenceFeature> it = result.listIterator();
+      while (it.hasNext())
+      {
+        SequenceFeature sf = it.next();
+        int featureStartColumn = findIndex(sf.getBegin());
+        int featureEndColumn = findIndex(sf.getEnd());
+        boolean noOverlap = featureStartColumn > toColumn
+                        || featureEndColumn < fromColumn;
+
+        /*
+         * reject an 'enclosing' feature if it is actually a contact feature
+         */
+        if (sf.isContactFeature() && featureStartColumn < fromColumn
+                && featureEndColumn > toColumn)
+        {
+          noOverlap = true;
+        }
+        if (noOverlap)
+        {
+          it.remove();
+        }
+      }
+    }
+
+    return result;
   }
 }
index dbf3ed3..ec76cb9 100755 (executable)
@@ -202,30 +202,6 @@ public interface SequenceI extends ASequenceI
   public int findPosition(int i);
 
   /**
-   * Returns the range of sequence positions included in the given alignment
-   * position range. If no positions are included (the range is entirely gaps),
-   * then returns null.
-   * 
-   * <pre>
-   * Example: 
-   * >Seq/8-13
-   * ABC--DE-F
-   * findPositions(1, 4) returns Range(9, 9) // B only
-   * findPositions(3, 4) returns null // all gaps
-   * findPositions(2, 6) returns Range(10, 12) // CDE
-   * findPositions(3, 7) returns Range(11,12) // DE
-   * </pre>
-   * 
-   * @param fromCol
-   *          first aligned column position (base 0, inclusive)
-   * @param toCol
-   *          last aligned column position (base 0, inclusive)
-   * 
-   * @return
-   */
-  public Range findPositions(int fromCol, int toCol);
-
-  /**
    * Returns an int array where indices correspond to each residue in the
    * sequence and the element value gives its position in the alignment
    * 
@@ -510,14 +486,18 @@ public interface SequenceI extends ASequenceI
   public List<DBRefEntry> getPrimaryDBRefs();
 
   /**
-   * Returns a (possibly empty) list of sequence features that overlap the range
-   * from-to (inclusive), optionally restricted to one or more specified feature
-   * types
+   * Returns a (possibly empty) list of sequence features that overlap the given
+   * alignment column range, optionally restricted to one or more specified
+   * feature types. If the range is all gaps, then features which enclose it are
+   * included (but not contact features).
    * 
-   * @param from
-   * @param to
+   * @param fromCol
+   *          start column of range inclusive (1..)
+   * @param toCol
+   *          end column of range inclusive (1..)
    * @param types
+   *          optional feature types to restrict results to
    * @return
    */
-  List<SequenceFeature> findFeatures(int from, int to, String... types);
+  List<SequenceFeature> findFeatures(int fromCol, int toCol, String... types);
 }
index f32dd7b..721f0c1 100644 (file)
@@ -30,6 +30,7 @@ import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.io.HTMLOutput;
 import jalview.jbgui.GAlignmentPanel;
 import jalview.math.AlignmentDimension;
 import jalview.schemes.ResidueProperties;
@@ -1438,32 +1439,32 @@ public class AlignmentPanel extends GAlignmentPanel implements
     {
       try
       {
-        int s, sSize = av.getAlignment().getHeight(), res, alwidth = av
-                .getAlignment().getWidth(), g, gSize, f, fSize, sy;
+        int sSize = av.getAlignment().getHeight();
+        int alwidth = av.getAlignment().getWidth();
         PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
-        out.println(jalview.io.HTMLOutput.getImageMapHTML());
+        out.println(HTMLOutput.getImageMapHTML());
         out.println("<img src=\"" + imageName
                 + "\" border=\"0\" usemap=\"#Map\" >"
                 + "<map name=\"Map\">");
 
-        for (s = 0; s < sSize; s++)
+        for (int s = 0; s < sSize; s++)
         {
-          sy = s * av.getCharHeight() + scaleHeight;
+          int sy = s * av.getCharHeight() + scaleHeight;
 
           SequenceI seq = av.getAlignment().getSequenceAt(s);
           SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
-          for (res = 0; res < alwidth; res++)
+          for (int column = 0; column < alwidth; column++)
           {
             StringBuilder text = new StringBuilder(512);
             String triplet = null;
             if (av.getAlignment().isNucleotide())
             {
               triplet = ResidueProperties.nucleotideName.get(seq
-                      .getCharAt(res) + "");
+                      .getCharAt(column) + "");
             }
             else
             {
-              triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(res)
+              triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(column)
                       + "");
             }
 
@@ -1472,23 +1473,23 @@ public class AlignmentPanel extends GAlignmentPanel implements
               continue;
             }
 
-            int seqPos = seq.findPosition(res);
-            gSize = groups.length;
-            for (g = 0; g < gSize; g++)
+            int seqPos = seq.findPosition(column);
+            int gSize = groups.length;
+            for (int g = 0; g < gSize; g++)
             {
               if (text.length() < 1)
               {
                 text.append("<area shape=\"rect\" coords=\"")
-                        .append((idWidth + res * av.getCharWidth()))
+                        .append((idWidth + column * av.getCharWidth()))
                         .append(",").append(sy).append(",")
-                        .append((idWidth + (res + 1) * av.getCharWidth()))
+                        .append((idWidth + (column + 1) * av.getCharWidth()))
                         .append(",").append((av.getCharHeight() + sy))
                         .append("\"").append(" onMouseOver=\"toolTip('")
                         .append(seqPos).append(" ").append(triplet);
               }
 
-              if (groups[g].getStartRes() < res
-                      && groups[g].getEndRes() > res)
+              if (groups[g].getStartRes() < column
+                      && groups[g].getEndRes() > column)
               {
                 text.append("<br><em>").append(groups[g].getName())
                         .append("</em>");
@@ -1498,17 +1499,16 @@ public class AlignmentPanel extends GAlignmentPanel implements
             if (text.length() < 1)
             {
               text.append("<area shape=\"rect\" coords=\"")
-                      .append((idWidth + res * av.getCharWidth()))
+                      .append((idWidth + column * av.getCharWidth()))
                       .append(",").append(sy).append(",")
-                      .append((idWidth + (res + 1) * av.getCharWidth()))
+                      .append((idWidth + (column + 1) * av.getCharWidth()))
                       .append(",").append((av.getCharHeight() + sy))
                       .append("\"").append(" onMouseOver=\"toolTip('")
                       .append(seqPos).append(" ").append(triplet);
             }
-            if (!Comparison.isGap(seq.getCharAt(res)))
+            if (!Comparison.isGap(seq.getCharAt(column)))
             {
-              List<SequenceFeature> features = seq.findFeatures(seqPos,
-                      seqPos);
+              List<SequenceFeature> features = seq.findFeatures(column, column);
               for (SequenceFeature sf : features)
               {
                 if (sf.isContactFeature())
index 4b24dee..06568ca 100644 (file)
@@ -62,7 +62,6 @@ import java.awt.event.MouseWheelListener;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.ListIterator;
 
 import javax.swing.JPanel;
 import javax.swing.SwingUtilities;
@@ -733,6 +732,7 @@ public class SeqPanel extends JPanel implements MouseListener,
     int seq = findSeq(evt);
     if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
     {
+      lastMouseSeq = -1;
       return;
     }
     if (column == lastMouseColumn && seq == lastMouseSeq)
@@ -794,11 +794,7 @@ public class SeqPanel extends JPanel implements MouseListener,
     if (av.isShowSequenceFeatures())
     {
       List<SequenceFeature> features = ap.getFeatureRenderer()
-              .findFeaturesAtRes(sequence.getDatasetSequence(), pos);
-      if (isGapped)
-      {
-        removeAdjacentFeatures(features, column + 1, sequence);
-      }
+              .findFeaturesAtColumn(sequence, column + 1);
       seqARep.appendFeatures(tooltipText, pos, features,
               this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
     }
@@ -820,35 +816,6 @@ public class SeqPanel extends JPanel implements MouseListener,
     }
   }
 
-  /**
-   * Removes from the list of features any that start after, or end before, the
-   * given column position. This allows us to retain only those features
-   * adjacent to a gapped position that straddle the position. Contact features
-   * that 'straddle' the position are also removed, since they are not 'at' the
-   * position.
-   * 
-   * @param features
-   * @param column
-   *          alignment column (1..)
-   * @param sequence
-   */
-  protected void removeAdjacentFeatures(List<SequenceFeature> features,
-          final int column, SequenceI sequence)
-  {
-    // TODO should this be an AlignViewController method (and reused by applet)?
-    ListIterator<SequenceFeature> it = features.listIterator();
-    while (it.hasNext())
-    {
-      SequenceFeature sf = it.next();
-      if (sf.isContactFeature()
-              || sequence.findIndex(sf.getBegin()) > column
-              || sequence.findIndex(sf.getEnd()) < column)
-      {
-        it.remove();
-      }
-    }
-  }
-
   private Point lastp = null;
 
   /*
@@ -1587,19 +1554,13 @@ public class SeqPanel extends JPanel implements MouseListener,
       }
 
       int column = findColumn(evt);
-      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
 
       /*
        * find features at the position (if not gapped), or straddling
        * the position (if at a gap)
        */
       List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
-              .findFeaturesAtRes(sequence.getDatasetSequence(),
-                      sequence.findPosition(column));
-      if (isGapped)
-      {
-        removeAdjacentFeatures(features, column, sequence);
-      }
+              .findFeaturesAtColumn(sequence, column + 1);
 
       if (!features.isEmpty())
       {
@@ -1776,12 +1737,11 @@ public class SeqPanel extends JPanel implements MouseListener,
    */
   void showPopupMenu(MouseEvent evt)
   {
-    final int res = findColumn(evt);
+    final int column = findColumn(evt);
     final int seq = findSeq(evt);
     SequenceI sequence = av.getAlignment().getSequenceAt(seq);
     List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
-            .findFeaturesAtRes(sequence.getDatasetSequence(),
-                    sequence.findPosition(res));
+            .findFeaturesAtColumn(sequence, column + 1);
     List<String> links = new ArrayList<>();
     for (SequenceFeature sf : allFeatures)
     {
index 1db2004..ba63834 100644 (file)
@@ -55,7 +55,7 @@ public class FeatureColourFinder
    * @param defaultColour
    * @param seq
    * @param column
-   *          alignment column position (base zero)
+   *          alignment column position (0..)
    * @return
    */
   public Color findFeatureColour(Color defaultColour, SequenceI seq,
@@ -81,7 +81,7 @@ public class FeatureColourFinder
       }
     }
 
-    Color c = featureRenderer.findFeatureColour(seq, column, g);
+    Color c = featureRenderer.findFeatureColour(seq, column + 1, g);
     if (c == null)
     {
       return defaultColour;
index 950d0cd..5e071f9 100644 (file)
@@ -21,7 +21,6 @@
 package jalview.renderer.seqfeatures;
 
 import jalview.api.AlignViewportI;
-import jalview.datamodel.Range;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.util.Comparison;
@@ -227,7 +226,7 @@ public class FeatureRenderer extends FeatureRendererModel
       /*
        * simple case - just find the topmost rendered visible feature colour
        */
-      renderedColour = findFeatureColour(seq, seq.findPosition(column));
+      renderedColour = findFeatureColour(seq, column);
     }
     else
     {
@@ -278,15 +277,6 @@ public class FeatureRenderer extends FeatureRendererModel
               transparency));
     }
 
-    /*
-     * get range of sequence positions within column range
-     */
-    Range seqRange = seq.findPositions(start, end);
-    if (seqRange == null)
-    {
-      return null;
-    }
-
     Color drawnColour = null;
 
     /*
@@ -300,8 +290,8 @@ public class FeatureRenderer extends FeatureRendererModel
         continue;
       }
 
-      List<SequenceFeature> overlaps = seq.findFeatures(seqRange.start,
-              seqRange.end, type);
+      List<SequenceFeature> overlaps = seq.findFeatures(start + 1, end + 1,
+              type);
       for (SequenceFeature sf : overlaps)
       {
         /*
@@ -321,14 +311,12 @@ public class FeatureRenderer extends FeatureRendererModel
                 .findIndex(sf.end);
         if (isContactFeature)
         {
-          boolean drawn = renderFeature(g, seq,
-                  featureStartCol - 1,
-                  featureStartCol - 1, featureColour,
-                  start, end, y1, colourOnly);
-          drawn |= renderFeature(g, seq,
-                  featureEndCol - 1,
-                  featureEndCol - 1, featureColour,
-                  start, end, y1, colourOnly);
+          boolean drawn = renderFeature(g, seq, featureStartCol - 1,
+                  featureStartCol - 1, featureColour, start, end, y1,
+                  colourOnly);
+          drawn |= renderFeature(g, seq, featureEndCol - 1,
+                  featureEndCol - 1, featureColour, start, end, y1,
+                  colourOnly);
           if (drawn)
           {
             drawnColour = featureColour;
@@ -393,16 +381,21 @@ public class FeatureRenderer extends FeatureRendererModel
   }
 
   /**
-   * Returns the sequence feature colour rendered at the given sequence
-   * position, or null if none found. The feature of highest render order (i.e.
-   * on top) is found, subject to both feature type and feature group being
-   * visible, and its colour returned.
+   * Returns the sequence feature colour rendered at the given column position,
+   * or null if none found. The feature of highest render order (i.e. on top) is
+   * found, subject to both feature type and feature group being visible, and
+   * its colour returned.
+   * <p>
+   * Note this method does not check for a gap in the column so would return the
+   * colour for features enclosing a gapped column. Check for gap before calling
+   * if different behaviour is wanted.
    * 
    * @param seq
-   * @param pos
+   * @param column
+   *          (1..)
    * @return
    */
-  Color findFeatureColour(SequenceI seq, int pos)
+  Color findFeatureColour(SequenceI seq, int column)
   {
     /*
      * check for new feature added while processing
@@ -421,7 +414,8 @@ public class FeatureRenderer extends FeatureRendererModel
         continue;
       }
 
-      List<SequenceFeature> overlaps = seq.findFeatures(pos, pos, type);
+      List<SequenceFeature> overlaps = seq.findFeatures(column, column,
+              type);
       for (SequenceFeature sequenceFeature : overlaps)
       {
         if (!featureGroupNotShown(sequenceFeature))
index 1af491c..a568341 100644 (file)
@@ -262,10 +262,14 @@ public abstract class FeatureRendererModel implements
   }
 
   @Override
-  public List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
+  public List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column)
   {
+    /*
+     * include features at the position provided their feature type is 
+     * displayed, and feature group is null or marked for display
+     */
     List<SequenceFeature> result = new ArrayList<SequenceFeature>();
-    if (!av.areFeaturesDisplayed())
+    if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null)
     {
       return result;
     }
@@ -274,12 +278,7 @@ public abstract class FeatureRendererModel implements
             .getVisibleFeatures();
     String[] visibleTypes = visibleFeatures
             .toArray(new String[visibleFeatures.size()]);
-
-    /*
-     * include features at the position provided their feature type is 
-     * displayed, and feature group is null or marked for display
-     */
-    List<SequenceFeature> features = sequence.findFeatures(res, res,
+    List<SequenceFeature> features = sequence.findFeatures(column, column,
             visibleTypes);
 
     for (SequenceFeature sf : features)
@@ -966,4 +965,39 @@ public abstract class FeatureRendererModel implements
                     .booleanValue();
   }
 
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public List<SequenceFeature> findFeaturesAtResidue(SequenceI sequence,
+          int resNo)
+  {
+    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
+    if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null)
+    {
+      return result;
+    }
+
+    /*
+     * include features at the position provided their feature type is 
+     * displayed, and feature group is null or the empty string
+     * or marked for display
+     */
+    Set<String> visibleFeatures = getFeaturesDisplayed()
+            .getVisibleFeatures();
+    String[] visibleTypes = visibleFeatures
+            .toArray(new String[visibleFeatures.size()]);
+    List<SequenceFeature> features = sequence.getFeatures().findFeatures(
+            resNo, resNo, visibleTypes);
+  
+    for (SequenceFeature sf : features)
+    {
+      if (!featureGroupNotShown(sf))
+      {
+        result.add(sf);
+      }
+    }
+    return result;
+  }
+
 }
index 6c5707d..ba627a8 100644 (file)
@@ -228,6 +228,7 @@ class ColumnCounterSetWorker extends AlignCalcWorker
    * 
    * @param alignment
    * @param col
+   *          (0..)
    * @param row
    * @param fr
    */
@@ -248,14 +249,12 @@ class ColumnCounterSetWorker extends AlignCalcWorker
     {
       return null;
     }
-    int pos = seq.findPosition(col);
 
     /*
      * compute a count for any displayed features at residue
      */
-    // NB have to adjust pos if using AlignmentView.getVisibleAlignment
     // see JAL-2075
-    List<SequenceFeature> features = fr.findFeaturesAtRes(seq, pos);
+    List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, col + 1);
     int[] count = this.counter.count(String.valueOf(res), features);
     return count;
   }
index 263844f..16ca70d 100644 (file)
@@ -199,22 +199,22 @@ public class FeatureDistanceModelTest
     Assert.assertEquals(af.getFeatureRenderer().getDisplayedFeatureTypes()
             .size(), 1, "Should be just one feature type displayed");
     // step through and check for pointwise feature presence/absence
-    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 1)
+    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 1)
             .size(), 0);
     // step through and check for pointwise feature presence/absence
-    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 2)
+    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 2)
             .size(), 1);
     // step through and check for pointwise feature presence/absence
-    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 3)
+    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 3)
             .size(), 0);
     // step through and check for pointwise feature presence/absence
-    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 4)
+    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 4)
             .size(), 0);
     // step through and check for pointwise feature presence/absence
-    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 5)
+    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 5)
             .size(), 1);
     // step through and check for pointwise feature presence/absence
-    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 6)
+    Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 6)
             .size(), 0);
   }
 
index 4da11eb..7d3fdd2 100644 (file)
@@ -1162,23 +1162,57 @@ public class SequenceTest
     seq.setDatasetSequence(seq2);
   }
 
-  @Test
-  public void testFindPositions()
+  @Test(groups = { "Functional" })
+  public void testFindFeatures()
   {
-    SequenceI sq = new Sequence("Seq", "ABC--DE-F", 8, 13);
-
-    Range range = sq.findPositions(1, 4); // BC
-    assertEquals(new Range(9, 10), range);
-
-    range = sq.findPositions(2, 4); // C
-    assertEquals(new Range(10, 10), range);
-
-    assertNull(sq.findPositions(3, 4)); // all gaps
+    SequenceI sq = new Sequence("test/8-16", "-ABC--DEF--GHI--");
+    sq.createDatasetSequence();
 
-    range = sq.findPositions(2, 6); // CDE
-    assertEquals(new Range(10, 12), range);
+    assertTrue(sq.findFeatures(1, 99).isEmpty());
 
-    range = sq.findPositions(3, 7); // DE
-    assertEquals(new Range(11, 12), range);
+    // add non-positional feature
+    SequenceFeature sf0 = new SequenceFeature("Cath", "desc", 0, 0, 2f,
+            null);
+    sq.addSequenceFeature(sf0);
+    // add feature on BCD
+    SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 9, 11, 2f,
+            null);
+    sq.addSequenceFeature(sf1);
+    // add feature on DE
+    SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 11, 12, 2f,
+            null);
+    sq.addSequenceFeature(sf2);
+    // add contact feature at [B, H]
+    SequenceFeature sf3 = new SequenceFeature("Disulphide bond", "desc", 9,
+            15, 2f,
+            null);
+    sq.addSequenceFeature(sf3);
+    // add contact feature at [F, G]
+    SequenceFeature sf4 = new SequenceFeature("Disulfide Bond", "desc", 13,
+            14, 2f,
+            null);
+    sq.addSequenceFeature(sf4);
+
+    // no features in columns 1-2 (-A)
+    List<SequenceFeature> found = sq.findFeatures(1, 2);
+    assertTrue(found.isEmpty());
+
+    // columns 1-6 (-ABC--) includes BCD and B/H feature but not DE
+    found = sq.findFeatures(1, 6);
+    assertEquals(2, found.size());
+    assertTrue(found.contains(sf1));
+    assertTrue(found.contains(sf3));
+
+    // columns 5-6 (--) includes (enclosing) BCD but not (contact) B/H feature
+    found = sq.findFeatures(5, 6);
+    assertEquals(1, found.size());
+    assertTrue(found.contains(sf1));
+
+    // columns 7-10 (DEF-) includes BCD, DE, F/G but not B/H feature
+    found = sq.findFeatures(7, 10);
+    assertEquals(3, found.size());
+    assertTrue(found.contains(sf1));
+    assertTrue(found.contains(sf2));
+    assertTrue(found.contains(sf4));
   }
 }
index 29fd092..734f7eb 100644 (file)
@@ -39,6 +39,7 @@ import jalview.gui.JvOptionPane;
 import jalview.gui.Preferences;
 import jalview.gui.StructureViewer;
 import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.DataSourceType;
 import jalview.io.FileLoader;
 import jalview.structure.StructureMapping;
 import jalview.structure.StructureSelectionManager;
@@ -50,7 +51,6 @@ import java.io.File;
 import java.io.IOException;
 import java.util.List;
 import java.util.Vector;
-import jalview.io.DataSourceType;
 
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
@@ -440,15 +440,18 @@ public class JalviewChimeraView
     binding.copyStructureAttributesToFeatures("phi", af.getViewport()
             .getAlignPanel());
     fr.setVisible("phi");
-    List<SequenceFeature> fs = fr.findFeaturesAtRes(fer2Arath, 54);
+    List<SequenceFeature> fs = fer2Arath.getFeatures().findFeatures(54, 54);
     assertEquals(fs.size(), 3);
-    assertEquals(fs.get(0).getType(), "RESNUM");
-    assertEquals(fs.get(1).getType(), "phi");
-    assertEquals(fs.get(2).getType(), "phi");
-    assertEquals(fs.get(1).getDescription(), "A"); // chain
-    assertEquals(fs.get(2).getDescription(), "B");
-    assertEquals(fs.get(1).getScore(), -131.0713f, 0.001f);
-    assertEquals(fs.get(2).getScore(), -127.39512, 0.001f);
+    /*
+     * order of returned features is not guaranteed
+     */
+    assertTrue("RESNUM".equals(fs.get(0).getType())
+            || "RESNUM".equals(fs.get(1).getType())
+            || "RESNUM".equals(fs.get(2).getType()));
+    assertTrue(fs.contains(new SequenceFeature("phi", "A", 54, 54,
+            -131.0713f, "Chimera")));
+    assertTrue(fs.contains(new SequenceFeature("phi", "B", 54, 54,
+            -127.39512f, "Chimera")));
 
     /*
      * tear down - also in AfterMethod
@@ -470,7 +473,8 @@ public class JalviewChimeraView
           int res, String featureType)
   {
     String where = "at position " + res;
-    List<SequenceFeature> fs = fr.findFeaturesAtRes(seq, res);
+    List<SequenceFeature> fs = seq.getFeatures().findFeatures(res, res);
+
     assertEquals(fs.size(), 2, where);
     assertEquals(fs.get(0).getType(), "RESNUM", where);
     SequenceFeature sf = fs.get(1);
index ab5c137..31348c6 100644 (file)
@@ -138,9 +138,9 @@ public class FeatureRendererTest
   }
 
   @Test(groups = "Functional")
-  public void testFindFeaturesAtRes()
+  public void testFindFeaturesAtColumn()
   {
-    String seqData = ">s1\nabcdefghijklmnopqrstuvwxyz\n";
+    String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
             DataSourceType.PASTE);
     AlignViewportI av = af.getViewport();
@@ -150,7 +150,7 @@ public class FeatureRendererTest
     /*
      * with no features
      */
-    List<SequenceFeature> features = fr.findFeaturesAtRes(seq, 3);
+    List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
     assertTrue(features.isEmpty());
 
     /*
@@ -159,34 +159,43 @@ public class FeatureRendererTest
     SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
             "Group"); // non-positional
     seq.addSequenceFeature(sf1);
-    SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 5, 15, 1f,
+    SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
             "Group1");
     seq.addSequenceFeature(sf2);
-    SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 5, 15, 1f,
+    SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
             "Group2");
     seq.addSequenceFeature(sf3);
-    SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 5, 15, 1f,
+    SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
             null); // null group is always treated as visible
     seq.addSequenceFeature(sf4);
 
     /*
      * add contact features
      */
-    SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 4,
-            12, 1f, "Group1");
+    SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
+            15, 1f, "Group1");
     seq.addSequenceFeature(sf5);
-    SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 4,
-            12, 1f, "Group2");
+    SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
+            15, 1f, "Group2");
     seq.addSequenceFeature(sf6);
-    SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 4,
-            12, 1f, null);
+    SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
+            15, 1f, null);
     seq.addSequenceFeature(sf7);
 
+    // feature spanning B--C
+    SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
+            "Group");
+    seq.addSequenceFeature(sf8);
+    // contact feature B/C
+    SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
+            6, 1f, "Group");
+    seq.addSequenceFeature(sf9);
+
     /*
      * let feature renderer discover features (and make visible)
      */
     fr.findAllFeatures(true);
-    features = fr.findFeaturesAtRes(seq, 12); // all positional
+    features = fr.findFeaturesAtColumn(seq, 15); // all positional
     assertEquals(features.size(), 6);
     assertTrue(features.contains(sf2));
     assertTrue(features.contains(sf3));
@@ -198,7 +207,7 @@ public class FeatureRendererTest
     /*
      * at a non-contact position
      */
-    features = fr.findFeaturesAtRes(seq, 11);
+    features = fr.findFeaturesAtColumn(seq, 14);
     assertEquals(features.size(), 3);
     assertTrue(features.contains(sf2));
     assertTrue(features.contains(sf3));
@@ -214,7 +223,8 @@ public class FeatureRendererTest
     data[2] = new Object[] { "Type3", colour, true };
     data[3] = new Object[] { "Disulphide Bond", colour, true };
     fr.setFeaturePriority(data);
-    features = fr.findFeaturesAtRes(seq, 12);
+
+    features = fr.findFeaturesAtColumn(seq, 15);
     assertEquals(features.size(), 5); // no sf2
     assertTrue(features.contains(sf3));
     assertTrue(features.contains(sf4));
@@ -226,10 +236,20 @@ public class FeatureRendererTest
      * make "Group2" not displayed
      */
     fr.setGroupVisibility("Group2", false);
-    features = fr.findFeaturesAtRes(seq, 12);
+
+    features = fr.findFeaturesAtColumn(seq, 15);
     assertEquals(features.size(), 3); // no sf2, sf3, sf6
     assertTrue(features.contains(sf4));
     assertTrue(features.contains(sf5));
     assertTrue(features.contains(sf7));
+
+    // features 'at' a gap between b and c
+    // - returns enclosing feature BC but not contact feature B/C
+    features = fr.findFeaturesAtColumn(seq, 4);
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf8));
+    features = fr.findFeaturesAtColumn(seq, 5);
+    assertEquals(features.size(), 1);
+    assertTrue(features.contains(sf8));
   }
 }