Skip to content

Tooltips! #1304

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

Closed
wants to merge 35 commits into from
Closed

Tooltips! #1304

wants to merge 35 commits into from

Conversation

mbostock
Copy link
Member

@mbostock mbostock commented Mar 1, 2023

This is a first pass at the foundation for tooltips (hover-based assistance for reading values out of plots). I’m focusing to start on the semantics with the expectation that we will devise better shorthand syntax for declaring tooltips in the future (e.g., adding tooltip: true to an existing mark rather than having to repeat everything on a separate tooltip mark).

The goal in this prototype is to demonstrate that we can use a pointermove event listener to determine the closest data point to the pointer based on the scaled position channels (a subset of x, y, fx, and fy).

There are a few tricky things here. First, the tooltip mark needs to know the owner SVG element so that it can register an event listener at the SVG level, rather than within each facet, and without obscuring other pointer-based interaction such as text selection; this is done by adding a context.ownerSVGElement property. Second, the tooltip mark needs to “multiplex”: like other marks, it gets rendered separately for each facet, but we only want to show a single tooltip across facets. This is done using a mark-scoped WeakMap, but perhaps with additional support from Plot we could make this more elegant. In a sense the tooltip mark isn’t a mark because it has no (conventional) visual representation, but it does have channels that are accessed by the event listener.

To-do:

  • Get local coordinates of the pointer
  • Register one pointermove listener per plot
  • Find the closest point in screen (scaled) coordinates
  • Find the closest point across all facets
  • Limit the search radius
  • Hide the tooltip on pointerdown (pre-click)
  • Display unscaled values in a tooltip
  • Handle x or y not existing; respect frameAnchor
  • Handle fx or fy not existing (for the entire plot)
  • Handle faceting being disabled for this mark (facet: null)
  • Display all channels, not just position
  • Custom channels for showing arbitrary values
  • Remove the red dot for testing purposes
  • Render the tooltip as SVG or HTML, anchored to a point
  • Draw a little connection to the anchor point
  • Allow text in the tooltip to be selected
  • Draw the tooltip on top of the facet axis labels
  • Add a lil’ drop shadow
  • Fix the x label when the automatic label is dropped (e.g., “Date”)
  • Fix tooltips for dodge initializer
  • Automatically Click to switch between “sticky” and “free” mode
  • Decide whether the tooltip appears immediately or after a delay?
  • Implement four corner orientations
  • Option for specifying the preferred orientation (when it fits)?
  • Orient the tooltip relative to the anchor automatically based on position
  • Minimize changes in tooltip orientation
  • Configurable text styles
  • Limit the maximum width of the tooltip, clipping long text
  • Show title when tooltip value is truncated
  • Tooltips for line, searching first on x and then on y, and vice versa
  • Fix competing event listeners when multiple charts are on the page
  • Display x1-x2 extent for binX (and similar for binY)
  • Display y2-y1 length for stackY( and similar for stackX)
  • Rename corner option to anchor in case we want sides in future
  • Fix oscillation when tooltip doesn’t fit with any orientation
  • Ensure that the tooltip is always rendered last (Promise.resolve)?
  • Fix mobile Safari?
  • Reorder the channels (custom, then standard)
  • Ability to suppress known channels (e.g., x or y)
  • Control formatting of values in tooltip
  • Shorthand from existing marks tooltip: true #1525

Optional:

  • For link and arrow, treat {x1, y1} and {x2, y2} as distinct points?
  • Option for controlling where a tooltip appears on a rect/area/etc.?
  • Respect the r channel for hovering large circles?
  • Side anchors (not just corners: top, right, bottom, left)?
  • Rename to “tip”?
  • Configurable colors (maybe even channels?)
  • Handle multiple dots in the same position (e.g., click to cycle)?
  • Dark mode (media query or checking computed background color)?

Above, the idea of peruse mode is when you’re moving the pointer around and you want the tooltip to update instantly, whereas focus mode is when the pointer seems to be pointing at a particular point of interest, and you want to be able to move the pointer into the tooltip to select text. I’m thinking that you start in focus mode (where the tooltip is stable), but during the first 250ms (or whatever), you can either move the pointer into the tooltip, or you move it elsewhere, and in the latter case you switch to peruse mode and the tooltip will not receive pointer events.

Fixes #4 (eventually).

@mbostock
Copy link
Member Author

mbostock commented Mar 2, 2023

Another point I forgot to mention: the tooltip mark will need a way to access the unscaled channel values, too. Currently marks only receive the scaled values during render.

@mbostock mbostock mentioned this pull request Mar 4, 2023
@dennismphil
Copy link

dennismphil commented Mar 9, 2023

Thank you @mkfreeman @Fil @mbostock for the work on this in the posted notebooks. Today I ran into an issue where it was difficult to build a on:hover interaction and was wondering if the Plot API was a bit friendlier when they will be developed/built. 

The design feedback that I wanted to pass on:

With this Pull request, in a histogram on hover, is it possible to reduce the opacity of all the bars except the one on which is mouse pointer is over? Wanted to seek advise on how would one go about doing this.

@mbostock mbostock force-pushed the mbostock/tooltip branch 3 times, most recently from 55f7ca2 to 0bcd9f1 Compare April 6, 2023 03:01
@mbostock
Copy link
Member Author

mbostock commented Apr 6, 2023

This has been rebased against the latest main, and now requires even fewer core changes—just the new ownerSVGElement property on the context. 😌 Remaining work centers around the contents and appearance of the tooltip, the various anchor modes, and shorthand syntax for deriving tooltips from an existing mark.

@mbostock mbostock force-pushed the mbostock/tooltip branch 2 times, most recently from f9087ff to 371dcfa Compare April 6, 2023 03:43
@mbostock mbostock force-pushed the mbostock/tooltip branch 7 times, most recently from a8b6a7c to b1e03e7 Compare May 2, 2023 22:02
@@ -61,7 +61,7 @@ export type ChannelName =
* An object literal of channel definitions. This is also used to represent
* materialized channel states after mark initialization.
*/
export type Channels = {[key in ChannelName]?: Channel};
export type Channels = Record<string, Channel>;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve changed this here to allow specifying custom channels for tooltips, but we might want to make this change in more places… and we might even want to change the ChannelName type to include string so that it’s always allowed, but use the never hack to still let VS Code autocomplete known channel names. Although, that makes it less likely we will detect typos.

@mbostock
Copy link
Member Author

mbostock commented May 4, 2023

@Fil Can you do an initial review here? I want to know if we have sufficiently captured the requirements for the first version or if there are unanticipated requirements that could delay this landing.

@mbostock
Copy link
Member Author

mbostock commented May 4, 2023

We’ll need to figure out some hint so that the tooltip can be smarter with x and y when x1 and x2 or y1 and y2 are available. Sometimes we want to express an extent (for example with binX, it’d be nice to see the upper and lower bound of the bin in x rather than the midpoint) and other times we want a length (for example with stackY, we want to see the difference between y2 and y1, not the midpoint y). The latter—fixing the tooltip with the stack transform—is especially important because it is misleading to show the midpoint, especially in the streamgraph case. I wonder whether the stack transform can populate a hint that y1 and y2 represent lengths (say using the channel.source hint, and suppressing y2)?

@mbostock mbostock force-pushed the mbostock/tooltip branch from be8db5a to 6bccc91 Compare May 5, 2023 18:49
@mbostock
Copy link
Member Author

mbostock commented May 6, 2023

Screen.Recording.2023-05-05.at.5.12.34.PM.mov

@mbostock mbostock marked this pull request as ready for review May 6, 2023 00:16
@mbostock mbostock mentioned this pull request May 6, 2023
* mobile tooltip on tap
test: https://observablehq.com/@fil/tt-tests

* pointermove on pointerenter

* ignore pointerleave for non-mouse

---------

Co-authored-by: Mike Bostock <[email protected]>
@mbostock
Copy link
Member Author

mbostock commented May 6, 2023

Given the numerous requests we have received related to this feature, and knowing that there is a lot of demand for customization here, I do wonder whether the initial implementation of this feature could be decoupled a bit (a bit like how axes were initially built-in, but then later we redesigned them to be explicit marks affording greater flexibility).

In particular I’d like to try implementing the (non-interactive) display of the tooltip as a standard mark. I imagine using it to annotate a specific point in the plot (statically and manually), or perhaps multiple points at the same time (say the extrema).

If we had such a mark, perhaps we could have a separate “point” interaction that picks a specific point of interest using pointer events. And then lastly some way to tie that pointing interaction with the tooltip display mark (having Plot re-render the mark automatically in response to the interaction saying that the focused point has changed).

A lot of details to figure out, but since we’re going to need to do some of this for interaction anyway, it might be worth experimenting even if it means delaying the initial release of tooltips?

@mbostock mbostock mentioned this pull request May 6, 2023
57 tasks
@mbostock
Copy link
Member Author

mbostock commented May 7, 2023

Closing in favor of #1527, which does almost everything this PR does, but is capable of much more, too.

@mbostock mbostock closed this May 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tooltips (hover to read values).
3 participants