leeoniya / uPlot

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

A way to have setSelect hook and also bind a mouse click #867

Closed simPod closed 7 months ago

simPod commented 8 months ago

I'm using uplot in grafana. There's this hook registered in ZoomPlugin

  useLayoutEffect(() => {
    config.addHook('setSelect', (u) => {
      const min = u.posToVal(u.select.left, 'x');
      const max = u.posToVal(u.select.left + u.select.width, 'x');

      if (u.select.width >= MIN_ZOOM_DIST) {
        pluginLog('ZoomPlugin', false, 'selected', {
          min,
          max,
          bbox: {
            left: u.bbox.left / window.devicePixelRatio + u.select.left,
            top: u.bbox.top / window.devicePixelRatio,
            height: u.bbox.height / window.devicePixelRatio,
            width: u.select.width,
          },
        });
        onZoom({ from: min, to: max });
      }

      // manually hide selected region (since cursor.drag.setScale = false)
      /* @ts-ignore */
      u.setSelect({ left: 0, width: 0 }, false);
    });

I also wanted to register another hook that reacts on mouse click (while keeping the original hook working).

I registered this before registering setSelect hook:

  useLayoutEffect(() => {
    config.setCursor({
      bind: {
        mousedown: (u) => {
          return e => {
            console.log('works');

            return null;
          }
        },
      },
    });
  }, [config]);

which works but also overrides original setSelect hook. I also tried bind: { click: ... but that does not do anything at all.

How can I add another hook to react on mouse click?

leeoniya commented 7 months ago

hey @simPod, sorry i missed this. was on parental leave and PTO.

I also wanted to register another hook that reacts on mouse click (while keeping the original hook working).

if you simply want to handle clicks, you should just use a plain u.over.addEventListener('click', ...). you can see i do this in ContextMenuPlugin to handle clicking on closest/focused series: https://github.com/grafana/grafana/blob/0238db42af61e2fb4db26cd877e46015a9a56818/public/app/plugins/panel/timeseries/plugins/ContextMenuPlugin.tsx#L48-L71

I also tried bind: { click: ... but that does not do anything at all.

this is for intercepting clicks on legend items (in uPlot's legend, not in Grafana's legend). the whole bind API is designed to allow interception and filtering of events that uPlot itself listens to. uPlot does not actually listen to click events on the plotting area, but to mousedown, mouseup, and dblclick.

ZoomPlugin has changed a bit recently and i've had to work around the fact that the bind API is a singleton. you cannot bind multiple event filters/interceptors to mousedown from different plugins that are loaded at the same time. so here i have to use regular (capturing) listeners to change the drag behavior at the time of mousedown based on modifier key.

https://github.com/grafana/grafana/blob/0238db42af61e2fb4db26cd877e46015a9a56818/packages/grafana-ui/src/components/uPlot/plugins/ZoomPlugin.tsx#L24-L51

i cannot use the bind API because we already use it in AnnotationEditorPlugin:

https://github.com/grafana/grafana/blob/0238db42af61e2fb4db26cd877e46015a9a56818/public/app/plugins/panel/timeseries/plugins/AnnotationEditorPlugin.tsx#L90-L106

we're in the process of re-working all the cursor-related plugins to be a single CursorAction plugin, because there is a bunch of complex state that has to be shared between them and having them isolated from each other (as they are now) is a total clusterfuck because each one needs to avoid stepping on each other's toes with respect to modifier keys, event types handled, etc. when all the plugins are combined, we can then use the bind api to handle all the logic for mousedown and mouseup in a single place that knows the proper context of everything that is possible to do with a cursor.