From ef76fb0b83fe9908a63380d9dc6e6df43bf1430e Mon Sep 17 00:00:00 2001 From: jrobinso <933148+jrobinso@users.noreply.github.com> Date: Thu, 28 Sep 2023 21:48:12 -0700 Subject: [PATCH] improve "hit testing" for clicks over features, etc. --- .../org/broad/igv/feature/FeatureUtils.java | 31 +++++++++---------- .../org/broad/igv/sam/AlignmentTrack.java | 3 +- .../java/org/broad/igv/sam/CoverageTrack.java | 2 +- .../java/org/broad/igv/track/DataTrack.java | 4 +-- .../igv/track/FeatureCollectionSource.java | 6 +--- .../org/broad/igv/track/FeatureTrack.java | 10 ++---- .../java/org/broad/igv/track/GisticTrack.java | 4 +-- .../broad/igv/track/TribbleFeatureSource.java | 6 +--- .../broad/igv/feature/FeatureUtilsTest.java | 6 ++-- 9 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/broad/igv/feature/FeatureUtils.java b/src/main/java/org/broad/igv/feature/FeatureUtils.java index 2964bf396b..73e37a4f50 100644 --- a/src/main/java/org/broad/igv/feature/FeatureUtils.java +++ b/src/main/java/org/broad/igv/feature/FeatureUtils.java @@ -57,15 +57,15 @@ public static void sortFeatureList(List features) { * Return a feature from the supplied list whose extent, expanded by "buffer", contains the given position. * * @param position 0-based genomic position to which to search for feature - * @param buffer search region. The first feature which contains the start position, (expanded by buffer, inclusive) - * will be accepted. + * @param bpPerPixel current resolution in base pairs per pixel. * @param features * @return */ - public static T getFeatureAt(double position, int buffer, List features) { + public static T getFeatureAt(double position, double bpPerPixel, List features) { int startIdx = 0; int endIdx = features.size(); + int buffer = (int) (2 * bpPerPixel); while (startIdx != endIdx) { int idx = (startIdx + endIdx) / 2; @@ -367,34 +367,33 @@ public static int getIndexBeforeOld(double position, List fea /** - * Return all features from the supplied list who's extent, expanded to "minWidth" if needed, contains the given position + * Return all features from the supplied list who's extent, expanded by "flanking", intersect the position * * @param position - * @param maxLength -- the distance back from position at which to start search (the maximum feature length) - * @param minWidth -- the minimum effective width of the feature + * @param flanking -- the minimum effective width of the feature * @param features * @return */ public static List getAllFeaturesAt(double position, - double maxLength, - double minWidth, + double flanking, List features) { List returnList = null; - double adjustedPosition = Math.max(0, position - maxLength); - int startIdx = Math.max(0, getIndexBefore(adjustedPosition, features)); + + int startIdx = Math.max(0, getIndexBefore(position - flanking, features)); + double start = position - (flanking / 2); + double end = position + (flanking / 2); for (int idx = startIdx; idx < features.size(); idx++) { Feature feature = features.get(idx); - double start = feature.getStart() - (minWidth / 2); - if (start > position) { - break; - } - double end = feature.getEnd() + (minWidth / 2); - if (position >= start && position <= end) { + if(feature.getEnd() >= start && feature.getStart() <= end) { if (returnList == null) returnList = new ArrayList(); returnList.add(feature); } + + if (feature.getStart() > end) { + break; + } } // Sort features by distance from position (features closest to position listed first) diff --git a/src/main/java/org/broad/igv/sam/AlignmentTrack.java b/src/main/java/org/broad/igv/sam/AlignmentTrack.java index 4469b705c5..305dd19799 100644 --- a/src/main/java/org/broad/igv/sam/AlignmentTrack.java +++ b/src/main/java/org/broad/igv/sam/AlignmentTrack.java @@ -875,8 +875,7 @@ Alignment getAlignmentAt(double position, int y, ReferenceFrame frame) { List features = row.alignments; // No buffer for alignments, you must zoom in far enough for them to be visible - int buffer = 0; - return FeatureUtils.getFeatureAt(position, buffer, features); + return FeatureUtils.getFeatureAt(position, 0, features); } } } diff --git a/src/main/java/org/broad/igv/sam/CoverageTrack.java b/src/main/java/org/broad/igv/sam/CoverageTrack.java index 05d4b62c59..b93d0680af 100644 --- a/src/main/java/org/broad/igv/sam/CoverageTrack.java +++ b/src/main/java/org/broad/igv/sam/CoverageTrack.java @@ -402,7 +402,7 @@ private String getPrecomputedValueString(String chr, double position, ReferenceF if (scores == null) { return ""; } else { - LocusScore score = (LocusScore) FeatureUtils.getFeatureAt(position, 0, scores); + LocusScore score = FeatureUtils.getFeatureAt(position, 0, scores); return score == null ? "" : "Mean count: " + score.getScore(); } } diff --git a/src/main/java/org/broad/igv/track/DataTrack.java b/src/main/java/org/broad/igv/track/DataTrack.java index 2340e91de1..2a2410f8d7 100644 --- a/src/main/java/org/broad/igv/track/DataTrack.java +++ b/src/main/java/org/broad/igv/track/DataTrack.java @@ -300,9 +300,7 @@ private LocusScore getLocusScoreAt(String chr, double position, ReferenceFrame f return null; } else { // give a 2 pixel window, otherwise very narrow features will be missed. - double bpPerPixel = frame.getScale(); - int buffer = (int) (2 * bpPerPixel); /* * */ - return (LocusScore) FeatureUtils.getFeatureAt(position, buffer, scores); + return FeatureUtils.getFeatureAt(position, frame.getScale(), scores); } } diff --git a/src/main/java/org/broad/igv/track/FeatureCollectionSource.java b/src/main/java/org/broad/igv/track/FeatureCollectionSource.java index be7674e720..387c806a47 100644 --- a/src/main/java/org/broad/igv/track/FeatureCollectionSource.java +++ b/src/main/java/org/broad/igv/track/FeatureCollectionSource.java @@ -301,14 +301,10 @@ public String getValueString(String chr, double position, ReferenceFrame frame) int zoom = Math.max(0, frame.getZoom()); List scores = getSummaryScoresForRange(chr, (int) position - 10, (int) position + 10, zoom); - // give a 2 pixel window, otherwise very narrow features will be missed. - double bpPerPixel = frame.getScale(); - int minWidth = (int) (2 * bpPerPixel); /* * */ - if (scores == null) { return ""; } else { - LocusScore score = (LocusScore) FeatureUtils.getFeatureAt(position, minWidth, scores); + LocusScore score = (LocusScore) FeatureUtils.getFeatureAt(position, frame.getScale(), scores); return score == null ? "" : "Mean count: " + score.getScore(); } } diff --git a/src/main/java/org/broad/igv/track/FeatureTrack.java b/src/main/java/org/broad/igv/track/FeatureTrack.java index 9e05998523..d1e8080cf8 100644 --- a/src/main/java/org/broad/igv/track/FeatureTrack.java +++ b/src/main/java/org/broad/igv/track/FeatureTrack.java @@ -424,10 +424,7 @@ public String getValueStringAt(String chr, double position, int mouseX, int mous if (scores == null) { return ""; } else { - // give a +/- 2 pixel buffer, otherwise very narrow features will be missed. - double bpPerPixel = frame.getScale(); - int minWidth = (int) (2 * bpPerPixel); /* * */ - LocusScore score = (LocusScore) FeatureUtils.getFeatureAt(position, minWidth, scores); + LocusScore score = FeatureUtils.getFeatureAt(position, frame.getScale(), scores); return score == null ? null : "Mean count: " + score.getScore(); } @@ -558,9 +555,8 @@ public List getFeaturesAtPositionInFeatureRow(double position, int feat if (possFeatures != null) { // give a minum 2 pixel or 1/2 bp window, otherwise very narrow features will be missed. double bpPerPixel = frame.getScale(); - double minWidth = Math.max(2, 3 * bpPerPixel); - int maxFeatureLength = packedFeatures.getMaxFeatureLength(); - featureList = FeatureUtils.getAllFeaturesAt(position, maxFeatureLength, minWidth, possFeatures); + double minWidth = (int) (2* bpPerPixel); + featureList = FeatureUtils.getAllFeaturesAt(position, minWidth, possFeatures); } return featureList; } diff --git a/src/main/java/org/broad/igv/track/GisticTrack.java b/src/main/java/org/broad/igv/track/GisticTrack.java index fc391d8d05..ddb4c5d2a0 100644 --- a/src/main/java/org/broad/igv/track/GisticTrack.java +++ b/src/main/java/org/broad/igv/track/GisticTrack.java @@ -360,13 +360,13 @@ public String getValueStringAt(String chr, double position, int mouseX, int igno LocusScore amp = null; List ampScores = ampScoreMap.get(chr); if (ampScores != null) { - amp = (LocusScore) FeatureUtils.getFeatureAt(position, 0, ampScores); + amp = FeatureUtils.getFeatureAt(position, 0, ampScores); } LocusScore del = null; List delScores = delScoreMap.get(chr); if (delScores != null) { - del = (LocusScore) FeatureUtils.getFeatureAt(position, 0, delScores); + del = FeatureUtils.getFeatureAt(position, 0, delScores); } if ((amp == null) && (del == null)) { diff --git a/src/main/java/org/broad/igv/track/TribbleFeatureSource.java b/src/main/java/org/broad/igv/track/TribbleFeatureSource.java index f56891f9b4..fbb4ac02af 100644 --- a/src/main/java/org/broad/igv/track/TribbleFeatureSource.java +++ b/src/main/java/org/broad/igv/track/TribbleFeatureSource.java @@ -578,14 +578,10 @@ public String getValueString(String chr, double position, ReferenceFrame frame) int zoom = Math.max(0, frame.getZoom()); List scores = getSummaryScoresForRange(chr, (int) position - 10, (int) position + 10, zoom); - // give a 2 pixel window, otherwise very narrow features will be missed. - double bpPerPixel = frame.getScale(); - int minWidth = (int) (2 * bpPerPixel); /* * */ - if (scores == null) { return ""; } else { - LocusScore score = (LocusScore) FeatureUtils.getFeatureAt(position, minWidth, scores); + LocusScore score = FeatureUtils.getFeatureAt(position, frame.getScale(), scores); return score == null ? "" : "Mean count: " + score.getScore(); } } diff --git a/src/test/java/org/broad/igv/feature/FeatureUtilsTest.java b/src/test/java/org/broad/igv/feature/FeatureUtilsTest.java index f0b419f065..7d36cada9b 100644 --- a/src/test/java/org/broad/igv/feature/FeatureUtilsTest.java +++ b/src/test/java/org/broad/igv/feature/FeatureUtilsTest.java @@ -129,10 +129,8 @@ public void testGetFeatureClosestWithIndels(){ public void testGetAllFeaturesAt() { int position = 56078756; - int maxLength = 100000; - - - List result = FeatureUtils.getAllFeaturesAt(position, maxLength, 0, featureList); + double flanking = 0; + List result = FeatureUtils.getAllFeaturesAt(position, flanking, featureList); assertEquals(21, result.size()); for (Feature f : result) { assertTrue(position >= f.getStart() && position <= f.getEnd());