Skip to content

A pointer interaction that handles click differently? #1832

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Fil opened this issue Aug 24, 2023 · 12 comments
Open

A pointer interaction that handles click differently? #1832

Fil opened this issue Aug 24, 2023 · 12 comments
Labels
enhancement New feature or request question Further information is needed

Comments

@Fil
Copy link
Contributor

Fil commented Aug 24, 2023

When building an interactive app, we might want:

  1. a pointer that only sets the value when we click
  2. a pointer that doesn't stick when we click
@Fil Fil added enhancement New feature or request question Further information is needed labels Aug 24, 2023
@mbostock
Copy link
Member

Maybe we can call it the “clicker” interaction in homage to TLOU. 😂

@yurivish
Copy link
Contributor

yurivish commented Aug 24, 2023

I've been using Plot to build early product prototypes recently and something along these lines would have been extremely useful (though this use case is admittedly stretching the boundaries of "exploratory data visualization" a bit!)

A bit of context in case it's useful: one of the prototypes explored ways to browse the results of a set of benchmarks/performance experiments. The prototype looks like this:

image

The top "chart" shows a grid of cells, where each cell represents a specific set of experimental parameters. The charts below correspond to detailed information about the selected cell in the top chart.

The interaction I wanted was that the user would click on a cell in the top chart to explore the associated experiments, with the secondary charts appearing only once a cell had been clicked.

I couldn't figure out a way to distinguish hover from click, though, so I ended up doing some lower-level event handling to maintain a variable that held the "last-pointed-at" element as a way to model a sticky hover selection – otherwise, moving the cursor away from the cells would result in a very visible reflow as the associated charts disappeared.

I would have liked to be able to show a tip in the top chart on hover, but not show the associated charts until the user clicks on a cell, ie. to distinguish a hover selection from a sticky selection. The tip represents a first level of detail, and the associated charts represent a second, much greater level of detail (akin to a page navigation).

A related question that came up was how to persist the pointer state across re-renders of the chart. The prototype had a few range sliders that all of the visualizations depended on. Since these included the top chart (which held the selection state), the selection would be reset whenever the user interacted with any of the sliders. Because I was storing the "last-pointed-at" element separately, I was able to keep the associated charts on screen as the user slid the sliders, but the selection state in the top chart (ie. the tooltip indicating the selected experiment) would disappear and I couldn't figure out how to bring it back without introducing dependency cycles.

(One thing I tried was setting the value of the top plot and triggering an input event, but that did not seem to cause the tips to show up -- maybe because I was setting the value of the figure element and not the svg itself, though?)

@Fil
Copy link
Contributor Author

Fil commented Sep 1, 2023

Another example https://observablehq.com/d/7e5f1eec1354f1a2

@ezralee
Copy link

ezralee commented Sep 22, 2023

We are using Plot as our visualization library to embed within an in house "production" app and being able to disable "click-to-stick" would be great for things like: Initial click on figure expands to full screen view -> optionally click to stick to highlight data point -> download SVG.

@Fil Fil mentioned this issue Sep 22, 2023
25 tasks
@vorbei
Copy link

vorbei commented Oct 16, 2023

+1 For click only pointer. We are using Plot for interactive viz app, and selected value will trigger the "list detail data" function, we do hope to set "click" as a stable interaction. We need to request data base on the selected value, and it should not be be triggered too often. The hover to point is nice for realtime data exhibition, but not very ideal for this scenario.

@tannerlinsley
Copy link

If it's helpful to anyone, current @nozzleio, we are listening to pointer events and grabbing the plot.value, then using that value in any registered click/hover callbacks on the entire plot. Blah blah blah, here's some code that might inspire?

useStrictEffect(() => {
    if (plotEl) {
      plotEl.innerHTML = ''
      plotEl.append(plot)
    }

    let registeredOnLeave = false

    const onTipHoverCb = () => {
      if (!registeredOnLeave) {
        registeredOnLeave = true
        const onLeave = e => {
          if (!plotEl.contains(e.target)) {
            getOnTipHover()?.(undefined!)
            registeredOnLeave = false
            document.removeEventListener('mousemove', onLeave)
          }
        }
        document.addEventListener('mousemove', onLeave)
      }
      getOnTipHover()?.(plot.value as T)
    }

    const onTipClickCb = (e: any) => {
      if (plot.value) getOnTipClick()?.(plot.value as T, e)
    }

    plot.addEventListener('mousemove', onTipHoverCb)
    plot.addEventListener('click', onTipClickCb)

    return () => {
      plot.removeEventListener('mousemove', onTipHoverCb)
      plot.removeEventListener('click', onTipClickCb)
    }
  }, [getOnTipClick, getOnTipHover, width, height, plot, plotEl])

@aboveyunhai
Copy link

When building an interactive app, we might want:

  1. a pointer that only sets the value when we click
  2. a pointer that doesn't stick when we click

The first option or any form of value exposing really leaves us the opportunity to do whatever we want.

The current default of click to stick (which adds point-event: none to some elements) actually blocking any custom events after click. If possible, just pass something to us or expose the click event to user to handle/overwrite.

@jordan-calderwood-reify
Copy link

Hello! I would love to be able to disable the default click to stick behavior. We are trying to replace our app's current visualization library with Plot but the sticky behavior doesn't play nicely with our desired onClick behavior.

I see a PR to this effect https://github.com/observablehq/plot/pull/1979/files. Is this still a viable solution? Anything I can do to help?

@mbostock
Copy link
Member

mbostock commented May 4, 2024

A quick workaround for this is that you can use event.stopPropagation on pointerdown events to prevent the pointer interaction from becoming sticky.

Another approach is to emit a pointerleave event that causes the pointer event to hide and clear the sticky bit.

const pointerdowned = (event) => {
  const pointerleave = new PointerEvent("pointerleave", {bubbles: true, pointerType: "mouse"});
  event.target.dispatchEvent(pointerleave);
};

@jessiesanford
Copy link

jessiesanford commented Aug 13, 2024

@mbostock I have tried using event.stopPropagation on my chart pointerdown events but it seems to only trigger when I click in the whitespace of the chart, when I click on a chart element (such as a dot, bar, or line) the pointerdown event is not fired. Do you have any other insight into this issue or workarounds?

@mkfreeman
Copy link
Contributor

@jessiesanford I know this is a little stale, but if you're trying to make this tooltip leave when you click on the chart, I was able to get this to work (took a bit of fiddling):

const plot = Plot.plot(...)
plot.addEventListener(
  "pointerdown",
  (event) => {
    event.stopPropagation();
    // Do whatever you want with event and plot.value.....
    // Force a pointerleave to hide the tooltip
    const pointerleave = new PointerEvent("pointerleave", {
      bubbles: true,
      pointerType: "mouse",
    });
    event.target?.dispatchEvent(pointerleave);
  },
  { capture: true } // this needs to be on to capture the pointerdown
);

@Fil
Copy link
Contributor Author

Fil commented Mar 5, 2025

Same issue in this discussion #2292

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is needed
Projects
None yet
Development

No branches or pull requests

10 participants