ScottPlot icon indicating copy to clipboard operation
ScottPlot copied to clipboard

Effects of LastRender on GetNearest When Using Multiple Y-Axes

Open nivGevaAerotor opened this issue 8 months ago • 5 comments

I'm plotting multiple Scatter plots on one FormsPlot, such that every Scatter is plotted on a different Y-axis (both left and right). I'm also using Scatter's GetNearest method to find the plotted point closest to the cursor's location on the FormsPlot.

I found that when first plotting a Scatter with a small Y-value range (40 and lower) on the default-left Y-axis, GetNearest won't return a real datapoint - even when the cursor hovers right above one of the Scatters plotted. I figured that the reason is that LastRender is relative to the default axes, i.e. it gets the layout and size information from the main, default axes. Thus, when GetNearest uses LastRender to find a datapoint, it can only use the size of the default Y-axis - and if it's too small compared to other Y-axes with Scatters plotted on them, then it'll mess with GetNearest ability to find a proper point on the Scatters plotted on the large-Y-value-range Y-axes.

Is there a way to feed GetNearest a render object that takes in different axes than the default axes? Or is there another way to go around this problem, so I could use GetNearest properly even when the default Y-axis has a small value-range? I would also love a deeper explanation on why and how does LastRender effects GetNearest - what information does LastRender holds, and how GetNearest uses it to find the closest point on a Scatter?

A huge thanks in advance for any help or response!

nivGevaAerotor avatar May 07 '25 08:05 nivGevaAerotor

I had a similar issue before trying to display ToolTips at mouse positions which display data of nearest points on the plottables around it which were all on different Y-Axes and solved it by using a different approach.

  • First I got the mousepixel on the WinUiPlot
  • Then I got the relative coordinates on the axis I want using SignalXY.Axes.GetCoordinates
  • Then I call SignalXY.Data.GetNearest

To put this into a bit of perspective:

Point mousePositionRelative = e.GetCurrentPoint(Chart).Position; // e here is the event args of PointerMoved
Pixel mousePixel = new(mousePositionRelative.X, mousePositionRelative.Y);

Coordinates axisRelativeCoords = sigXY.Axes.GetCoordinates(mousePixel);
DataPoint nearestPoint = sigXY.Data.GetNearest(axisRelativeCoords, Chart.Plot.LastRender, 10);

Then I added the tooltips but how I did that isn't relevant here so I'll omit it.

danielgruethling avatar May 16 '25 13:05 danielgruethling

Firstly - thanks for the response!

It seems I've done pretty much what you did - but still got bad results from GetNearest. Here's how my code goes about it:

double width = fp.Width;
double height = fp.Height;
Coordinates cursorScatCoords = fp.Plot.GetCoordinates(pix, scat.Axes.XAxis, scat.Axes.YAxis);      // pix is the cursor's location in the pixel space
double xThreshold = (scat.Axes.XAxis.Max - scat.Axes.XAxis.Min) * (pixelRadius / width);              // pixelRadius is a global constant, set to 15
double yThreshold = (scat.Axes.YAxis.Max - scat.Axes.YAxis.Min) * (pixelRadius / height);
float scatDistThreshold = Math.Max((float)Math.Sqrt(Math.Pow(xThreshold, 2) + Math.Pow(yThreshold, 2)), 15);
ScottPlot.DataPoint scatClosestPoint = scat.Data.GetNearest(cursorScatCoords, fp.Plot.LastRender, scatDistThreshold);

The only major difference I see between our approaches is the way you get the cursor's pixel values ( but I'm pretty sure I get mine correctly because it works when the default-left Y-axis has a large Y-value-range) and the threshold I set for GetNearest (which is a minimum of 15, so it shouldn't cause any issues).

I wonder why it works for you but not for me, weird.

nivGevaAerotor avatar May 21 '25 08:05 nivGevaAerotor

You can try using DataSourceUtilities.GetNearestFast or DataSourceUtilities.GetNearestSmart, which should solve your problem. The sample code is as follows:

Coordinates axisRelativeCoords = signal.Axes.GetCoordinates(mousePixel);
var xAxis = signal.Axes.XAxis;
var yAxis = signal.Axes.YAxis;
DataPoint candidate = DataSourceUtilities.GetNearestFast((IDataSource)signal.Data, axisRelativeCoords, ScottPlotControl.Plot.LastRender, 15, xAxis, yAxis);

nicewh avatar Jul 27 '25 03:07 nicewh

@nicewh & @nivGevaAerotor The 'DataSourceUtilities' class was introduced in my PR (#4270) because I was having similar issues and reworked the whole system to resolve those issues.

DataSourceUtilities.GetNearestSmart is a very simple check to detect if it is a sorted collection or not (sorted on X). quite literally :

If (sorted()) 
    return DataSourceUtilities.GetNearestFast(); // ONLY WORKS IF COLLECTION IS SORTED ON X
else
    return DataSourceUtilities.GetNearest();

Is there a way to feed GetNearest a render object that takes in different axes than the default axes? Or is there another way to go around this problem, so I could use GetNearest properly even when the default Y-axis has a small value-range?

If you look here : https://github.com/ScottPlot/ScottPlot/blob/2d95fad740d4552315672f3d2aaee9cfaf12a71c/src/ScottPlot5/ScottPlot5/Plottables/Scatter.cs#L236C40-L236C50

The GetNearest method of the Scatter plottable calls GetNearest and uses the X and Y axis it is assigned. Now, you could pass in the axes yourself, but that should (theoretically) only fix it if the code linked above was bugged. But it works (afaik) for all of the other plottables. Please verify you are calling it from the Scatter plottable, and not the datasource for the plottable (the datasource is not aware of X or Y axis).


I would also love a deeper explanation on why and how does LastRender effects GetNearest - what information does LastRender holds, and how GetNearest uses it to find the closest point on a Scatter?

What you are likely running into is a scaling issue, because it sounds very close to what I was attempting to resolve with I had to adjust my mouse position according to : https://scottplot.net/faq/dpi-scaling/

While I am fuzzy on what information the last render holds exactly, I can tell you what I learned while writing my PR and building my own charts. GetNearest is searching for the nearest X/Y coordinate by squaring each of them and checking if it was the closest distance to the mouse. To do this, the mouse coordinate passed in must be properly scaled. After properly accounting for scaling, my scatter plots and signalXY plots starting behaving as expected.

For example, if my display scaling of the plot is 2x, and I am at Coordinate (100,50), my mouse coordinate must be transformed to (200, 100). This is important because the display scaling is independent of the plot (what the plot thinks is 20 px-per-unit-X might be 40 due to display scaling)

RFBomb avatar Sep 22 '25 01:09 RFBomb

@RFBomb I did encounter a similar scaling issue with multiple Y-axes. For a long time, I wasn’t aware of the  DataSourceUtilities  class, which made this problem bother me for quite a while. Later, I was lucky enough to find this method when going through the source code. Thank you so much for submitting that PR—it really solved a big problem for me!

nicewh avatar Sep 26 '25 14:09 nicewh