leeoniya / uPlot

📈 A small, fast chart for time series, lines, areas, ohlc & bars
MIT License
8.48k stars 370 forks source link

Point hover bounding box option for individual series #912

Closed AndrewPhilbin closed 3 months ago

AndrewPhilbin commented 3 months ago

I'm trying to figure out a way to increase the hover "hit" (or bounding) box for specific series in order to focus points on that series preferentially and show tooltips, especially in crowded chart situations (which are unfortunately a necessity).

It looks as though there is a bbox option for points in the type declarations that might be what I'm looking for but it doesn't seem to affect anything when set in opts, nor does it seem to be referenced in the dist code.

Size and width points options both seem to increase the visual size of the hovered point but don't affect the hover distance.

Is there a way to achieve this with a setSeries hook, custom points draw plugin, etc. or am I missing something much more obvious?

leeoniya commented 3 months ago

if you're talking about timeseries / line data, you'll probably want cursor.dataIdx, which allows you to supply your own callback that determines which datapoints are hovered for each series based on cursor position and the nearest index in the dataset:

https://github.com/leeoniya/uPlot/blob/ff021df38cc3c150cb1bf5195aea4b2fbfc65506/dist/uPlot.d.ts#L555-L556

demo: https://leeoniya.github.io/uPlot/demos/nearest-non-null.html

if you're talking about cursor.focus which determines which series to highlight (when cursor.focus.prox > 0), there's a similar setting cursor.focus.dist:

https://github.com/leeoniya/uPlot/blob/ff021df38cc3c150cb1bf5195aea4b2fbfc65506/dist/uPlot.d.ts#L525-L526

AndrewPhilbin commented 3 months ago

Thank you for the reply. I'm wondering if my particular use case is actually a little strange or a bit of an edge case:

example

In the screenshot, what I'm aiming for is for the red series to be easier to focus/select. When other points are close by, it's finicky to get these particular series to focus and requires zooming in a lot.

After looking into the properties you mentioned it seems like I what I need is to use cursor.dataIdx to hover the nearest non-null point on the red series, but only within a certain distance, and at that point trigger a series focus. I'm just having a little trouble conceptualizing how to achieve this with hooks/cursor settings/etc.

leeoniya commented 3 months ago

i think you'll need to make a runnable jsfiddle that reproduces the issue, with actual data. e.g.: https://jsfiddle.net/q73vxrpe/

the cursor in your screenshot is close enough that it should be focusing without anything extra. my guess is that your data for the red series is not in ascending-x order (as it should be for all series).

AndrewPhilbin commented 3 months ago

I didn't consider the potential data issue and will certainly start there! If that seems alright I will post a fiddle that recreates this behavior. Thank you for your help!

AndrewPhilbin commented 3 months ago

I'm still not sure if I'm just making a simple mistake, but in this fiddle https://jsfiddle.net/Yumcaxion/hnw9mdcq/58/, in the chart using time: true for the x-scale, the points on the red two-point line are impossible to hover until you've zoomed in quite a bit and even then they are difficult to hover. I've confirmed that the timestamps are all unique and in ascending order.

In the time: false x-scale chart, the distance for the point hover/series focus seems like it works as expected. These lines are a little weird because they will always only be two points surrounded by null values, but it seems that as long as the datasets are the same length as the primary y set, this shouldn't be an issue, correct?

Apologies for the hastily assembled fiddle.

leeoniya commented 3 months ago

well, the shape of the data ends up being different. the actual timestamp data has two nearly-coincident points surrounding the red timestamp.

the hover works by converting your mouse cursor position, which only has pixel-level resolution to an index. and if you have a ton of indices in a single pixel, the binary search will always land on the same index for the same pixel. the reason it works in the indexed case is cause the spacing is wider/more uniform.

i think the way to go here is the cursor.dataIdx route.

in uPlot v2, there will be no alignment requirement, so this case should "just work", but v2 is a ways off still :)

image

AndrewPhilbin commented 3 months ago

That makes perfect sense! I'm not sure why I didn't make that connection from the different series timestamps being so close together. This gives me some ideas on how to solve this. I appreciate the help!