diff --git a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/data/PlotDataSearch.java b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/data/PlotDataSearch.java index e5382360ac..29cec7d9b7 100644 --- a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/data/PlotDataSearch.java +++ b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/data/PlotDataSearch.java @@ -7,6 +7,9 @@ ******************************************************************************/ package org.csstudio.javafx.rtplot.data; +import java.util.Date; +import java.time.Instant; + /** Search for samples in a haystack. * @author Kay Kasemir */ @@ -125,4 +128,67 @@ final public int findSampleGreaterThan(final PlotDataProvider data, final } return -1; } + + /** Find the sample closest to the given value. + * @param data Data, must already be locked + * @param x The value to look for. + * @return Index of sample closest to x, or -1 if data is empty or type unsupported. + */ + public int findClosestSample(final PlotDataProvider data, final XTYPE x) { + int low = 0; + int high = data.size() - 1; + + if (high < 0) return -1; + + int bestIndex = -1; + double bestDistance = Double.MAX_VALUE; + + double target; + try { + target = toDouble(x); + } catch (IllegalArgumentException e) { + return -1; + } + + double distance; + + while (low <= high) { + int mid = (low + high) / 2; + PlotDataItem sample = data.get(mid); + XTYPE sampleX = sample.getPosition(); + int cmp = sampleX.compareTo(x); + + try{ + distance = Math.abs(toDouble(sampleX) - target); + } catch (IllegalArgumentException e) { + return -1; + } + + if (distance < bestDistance) { + bestDistance = distance; + bestIndex = mid; + } + + if (cmp == 0) { + return mid; + } else if (cmp < 0) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return bestIndex; + } + + /** Convert supported XTYPE to double for distance comparison */ + private double toDouble(XTYPE x) { + if (x instanceof Number) + return ((Number) x).doubleValue(); + if (x instanceof Instant) + return ((Instant) x).toEpochMilli(); + if (x instanceof Date) + return ((Date) x).getTime(); + throw new IllegalArgumentException("Unsupported XTYPE: " + x.getClass()); + } } diff --git a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/AnnotationImpl.java b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/AnnotationImpl.java index c2935a81b7..ae519e0d04 100644 --- a/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/AnnotationImpl.java +++ b/app/rtplot/src/main/java/org/csstudio/javafx/rtplot/internal/AnnotationImpl.java @@ -43,6 +43,8 @@ public class AnnotationImpl> extends Annotation< private static final Stroke DASH = new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[] { 3f, 3f }, 1.0f); + private boolean searchLessOrEqual; + /** What part of this annotation has been selected by the mouse? */ public static enum Selection { /** Nothing */ @@ -65,6 +67,7 @@ public static enum Selection public AnnotationImpl(final boolean internal, final Trace trace, final XTYPE position, final double value, final Point2D offset, final String text) { super(internal, trace, position, value, offset, text); + searchLessOrEqual = true; } /** Set to new position @@ -179,7 +182,15 @@ boolean updateValue(final XTYPE location) throws Exception throw new TimeoutException("Cannot update annotation, no lock on " + data); try { - final int index = search.findSampleLessOrEqual(data, location); + final int index; + if (searchLessOrEqual){ + index = search.findSampleLessOrEqual(data, location); + } + else{ + index = search.findSampleGreaterOrEqual(data, location); + } + searchLessOrEqual = !searchLessOrEqual; + if (index < 0) return false; final PlotDataItem sample = data.get(index);