leeoniya / uPlot

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

implement API primitives to support dual chart zoom ranging #87

Closed leeoniya closed 4 years ago

leeoniya commented 4 years ago

copied from https://github.com/leeoniya/uPlot/issues/83#issuecomment-570573947

i was planning on making a demo similar to this:

http://dygraphs.com/gallery/#g/range-selector

there's nothing built in but i think there's enough api to make this without too much effort using 2 charts with the same data but different scale ranges and some ui sprinkled on.

i might need to add the ability to disable zoom without disabling selection and make a new "select" hook. i havent really thought about it much yet.

leeoniya commented 4 years ago

most of the necessary work is done in 10bf5b2b5739c41b1b3aeec4055ba21510f0a83b & 130fc217a2d29b1f6903951f78b1b036a6a01c49. i have a basic bi-directional demo working locally, but still not completely happy with the hook firing order and when setSelect should fire:

still need to add the ability for initial (but not permanent) scale ranges to be be defined.

the bi-directional nature leads to a chicken-egg problem if we rely only on hooks to set initial state. does the zoomed chart's setScale trigger the full chart's setSelect or vice versa? if neither and we rely on setting the values directly via opts, does that mean we don't fire the hooks at all? bad too.

mbj4668 commented 4 years ago

Can you share the demo you have?

leeoniya commented 4 years ago

yes, i'll push it later tonight.

leeoniya commented 4 years ago

the demo is here: https://github.com/leeoniya/uPlot/blob/master/demos/zoom-ranger.html

not super elegant, but not terrible either. couple thoughts that came out of this exercise.

mbj4668 commented 4 years ago

Thanks for putting this together!

I agree that passing scale values rather than coords seems more useful. (but why is y and height used at all for setSelect?)

For point 2, wouldn't it be possible to pass the setSelect "opts" object to the hook? Then the caller can simply set some field in that object, if necessary.

mbj4668 commented 4 years ago

After experimenting with this solution, I realized that for my use case, I can do something simpler; I now use a slider (Ion.RangeSlider) with two handles to set the x min/max values, and for this I just need setScale() (and the setScale hook to update the slider). This works well (I can even do "panning" with direct update of the uplot when I drag the slider). But I want to align the slider with the canvas that uplot draws. Would it be possible to pass an "id" of the canvas element that uplot creates in the options, so that I can find the element? Currently I do: $('.plot')[0].childNodes[0].getBoundingClientRect() but that is brittle and not even correct... Perhaps I should open a separate issue for this?

leeoniya commented 4 years ago

(but why is y and height used at all for setSelect?)

cause as of 130fc217a2d29b1f6903951f78b1b036a6a01c49 it's now possible to do selection and zooming/rescaling via a rect. it's still disabled by default. but can be enabled in cursor.drag.

after thinking some more, i'll probably leave the current API. there are bigger snowballing questions that start to happen with regards to how sync would work if the APIs took scale values rather than coordinates (coords exist everywhere, but scales may not).

ultimately, cursor setting and selection drawing do not affect the chart drawing. they're visual add-ons. i was trying to make it possible to declaratively set the initial cursor position and selection range in opts, but in reality, i don't think there's a good case for this since both can be set after initialization in some final ready hook, or immediately after instantiation. there's some hook firing refinement still to do but i think the current design makes the right compromises.

Would it be possible to pass an "id" of the canvas element that uplot creates in the options

uplot can already take an id in the opts to add to the top-level wrapper, but the fastest way to grab the canvas element is u.ctx.canvas.

mbj4668 commented 4 years ago

Yes I know I can pass an id for the top-level wrapper, but I need the canvas. u.ctx.canvas works fine. Do you view this (u.ctx) as part of the public api?

leeoniya commented 4 years ago

Do you view this (u.ctx) as part of the public api?

yes. it's explicitly exposed: https://github.com/leeoniya/uPlot/blob/e7acfc2642b284699a297b5ce5303ede738e8bcc/src/Line.js#L411

leeoniya commented 4 years ago

ok, i think this is now good: https://github.com/leeoniya/uPlot/blob/master/demos/zoom-ranger.html

the only meh are the viaRanger & viaZoom flags to sync/disambiguation, but i'll live with it. pretty happy with the rest.