leeoniya / uPlot

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

mouse zooming at the edges #244

Closed hz2018tv closed 3 years ago

hz2018tv commented 3 years ago

in the candelstick demo, if you use mouse to zoom, sometimes the edge ticks would be excluded or missed. or if you hold the mouse left button and swing the mouse out of canvas, the selection range could be random. ideally, when mouse cursor is out of range, every ticks along that direction should be included?

leeoniya commented 3 years ago

ideally, when mouse cursor is out of range, every ticks along that direction should be included?

that does not seem like intuitive behavior to me. is there an existing chart that works this way?

hz2018tv commented 3 years ago

thinkorswim app is like that. searched a little bit online, found some, like this one https://timetotrade.com/chart/GBPUSD, click on zoom, then hold mouse left button and swing to right, when cursor is out of range, all points included. so if we can detect the out of range behavior, then in the zooming, we just set the tick to max (right) or min (left). just thinking :D

leeoniya commented 3 years ago

are you sure you're not talking about panning (similar to #243).

the demo you linked pans on drag rather than zooming; it zooms on mousewheel.

hz2018tv commented 3 years ago

probably not. panning means the plot is shifted left and right, zooming means selecting a segment and magnifying the selection. in the link https://timetotrade.com/chart/GBPUSD above, you have to click on the zoom button to enable zooming, otherwise it is panning, I guess.

leeoniya commented 3 years ago

i actually have absolutely no idea how their zoom is supposed to work.

when i try to zoom-drag from 2020 back to the left edge of the chart it seems to be trying to do something like helpfully scrolling for you when you hit the edge of the chart, but actually completely messes up the range and initial selection starting point, which is fully disorienting to me. then it fails to actually zoom if i mouseup outside the chart, which is gonna be the majority of the time if i'm trying to max out at the edge. instead it continues thinking i'm selecting until i click at the very edge inside the chart anyways.

is there a link to another lib or demo that doesnt feel completely broken? (or a GIF that shows the behavior you're wnting).

hz2018tv commented 3 years ago

try this one then https://www.highcharts.com/stock/demo/stock-tools-gui , click on the "zoom change" first, then click in the middle of the chart, hold the button , then swing the mouse.

leeoniya commented 3 years ago

ok, i think i understand what you're saying. you just mean it's difficult to hit the very edge of the chart, not that you want to see the ticks are out of view.

i think this is mainly obvious when dragging quickly, but if you drag slowly it works properly, yes?

hz2018tv commented 3 years ago

yes sometimes, but it happens when dragging slowly too quite often. always the very edge one. maybe we draw a little more like 1 or 2 more ticks to see ? I mean, just to make canvas a little bit wider. but since uplot is already doing the shaded area for selection, if we can detect the cursor being out of range, then we set the selection area to max. just a thought.

leeoniya commented 3 years ago

if we can detect the cursor being out of range, then we set the selection area to max. just a thought.

yeah, this is gonna have to be the strategy here.

the problem is that when you move the mouse quickly, the browser just doesnt fire all the mousemove events. this is especially true when you're doing something like mutating the shaded zoom area. the highcharts demo you linked has the same issue for me.

hz2018tv commented 3 years ago

yes, the highcharts one does it sometimes too if mouse moves too quickly, but quite less often, almost always can cover the edge ones, with moderate speed. the previous "broken" link actually seldom fails on the swing to the right. a different world if you go "far left" though :D

leeoniya commented 3 years ago

i think we'll need to hook into the mouseleave on .over and based on the last known mouse position fire off another fake mousemove to hook into the normal selection/scaling logic.

https://github.com/leeoniya/uPlot/blob/6a8be6cb89c90fda8ab042da8ec8de67c0a23f00/src/uPlot.js#L1870-L1877

cc @EmiPhil :)

leeoniya commented 3 years ago

i got a basic version of this working in the edge-snap-selection branch:

https://github.com/leeoniya/uPlot/blob/51004d21cefe45c55eb242d6cb1ce443d4a32af6/src/uPlot.js#L1870-L1901

this probably still needs more work with some checks for the types of dragging and the edge exits allowed and testing with sync between multiple charts.

it may potentially be a more tolerant version of this code, which we may be able to remove:

https://github.com/leeoniya/uPlot/blob/51004d21cefe45c55eb242d6cb1ce443d4a32af6/src/uPlot.js#L1779-L1785

EmiPhil commented 3 years ago

Haven't had a chance to look at any of this on my PC but there could be another possibility for achieving what I think the goal is. We could have a snapToEdge option in the drag settings where if the mouse is close enough to the edge it just snaps.

Though I see that if the cursor goes outside then snap to edge still makes sense. But yeah it seems to me that if you are like 5 pixels off the edge of the graph it could be reasonable to say that you are really trying to snap to the edge (or at least reasonable enough to provide that kind of option). And just thinking that if there was a snap option it would get triggered before the cursor goes out so maybe that would be a help.

I'll need to process more in the morning though, hopefully this wasn't a bonehead comment 😂

leeoniya commented 3 years ago

i frequently get distances of like 20px - 50px from the edges, so i dont think simply switching from 1px to 5px is gonna cut it. i think we'll need to rely on mouseleave here, which is fine and actually more robust if it's as simple as my test.

EDIT: ah yeah, that code breaks dragging back in. probably after that second call to updateCursor(1) we'll want to restore dragging = true.

EDIT 2: yep, that worked. pushed another commit.

EmiPhil commented 3 years ago

I'll look at that soon. While we are talking drag ergonomics I made issues #245 and #246 which are some drag pain points for me. Maybe we can wrap all three up into a release if you agree with them?

Also the more complicated issue... if setScale = false, add "handles" to the drag box for adjusting it (or at least add the html elements to allow someone to build a plugin the implements the drag behavior)

EmiPhil commented 3 years ago

Yeah this feature is great. It definitely acts like how you would expect.

Can confirm that omni zoom to a corner doesn't work because it favors whichever side of the plot you hit first on exit

EmiPhil commented 3 years ago

Though I'm not sure how we would support a corner zoom in just mouseleave. We would need to track the mouse movement outside of the graphs if dragging. Maybe that would be more work than it is worth

leeoniya commented 3 years ago

how would corner zoom be different? you'd just need to check proximity to two edges instead of one and max both out instead of just one. isnt it just a matter of checking what dragX and dragY mode you're currently in?

EmiPhil commented 3 years ago

Ah, yeah. That would work. On mouse leave if you are within x distance from the other edge and in omni zoom then snap?

EmiPhil commented 3 years ago
                let dLft = mouseLeft1;
                let dRgt = plotWidCss - mouseLeft1;
                let dTop = mouseTop1;
                let dBtm = plotHgtCss - mouseTop1;

                if (dragX) {
                    let xMin = min(dLft, dRgt)

                    if (xMin == dLft)
                        mouseLeft1 = 0;
                    if (xMin == dRgt)
                        mouseLeft1 = plotWidCss;
                }

                if (dragY) {
                    let yMin = min(dTop, dBtm);

                    if (yMin == dTop)
                        mouseTop1 = 0;
                    if (yMin == dBtm)
                        mouseTop1 = plotHgtCss;
                }
EmiPhil commented 3 years ago

This forces corner zoom a little aggressively on omni hahaha

EmiPhil commented 3 years ago

https://github.com/EmiPhil/uPlot/tree/edge

I guess that we would need some sort of a way to limit the effect

leeoniya commented 3 years ago

I guess that we would need some sort of a way to limit the effect

getting it perfect is likely to be pretty complex. i'm okay setting a proximity threshold that defaults to 0 and is something like 10px if dragX && dragY.

also, i don't think we can get rid of the 1px snapping since you still want to be able to place the cursor at the edge to hover some point without triggering mouseleave and hiding the cursor.

EmiPhil commented 3 years ago

https://github.com/EmiPhil/uPlot/commit/43da37c059b22cc285e6f4f7e1e71019b6e26438

This uses a proximity check for corner snapping. Haven't tested sync yet though

EmiPhil commented 3 years ago

Haha on the sync cursor demo if you drag right on the bottom left chart into the bottom right chart it drags from the right chart to the left. Logical but kinda funny behavior

EmiPhil commented 3 years ago

I'd say that overall sync does work from what I can tell. proxThresh should probably be a setting?

leeoniya commented 3 years ago

alright, i think this one is squashed!

@hz2018tv try the latest build.

hz2018tv commented 3 years ago

looking good. good job!