Skip to content
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

brush interaction #1653

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open

brush interaction #1653

wants to merge 44 commits into from

Conversation

Fil
Copy link
Contributor

@Fil Fil commented May 29, 2023

  • tests that show the input events
  • put the brush on top of the svg?
  • can we make it work if multiple marks are brushed? otherwise, error?
  • add an option to make viewof the geometric selection (vs. data selection)?
  • remove the intersection option, unless we have another intersection to offer?
  • could a brush span multiple facets? (but then, no geometric selection…)
  • fix aria-label
  • Using a RAF to append the brushable g elements to the top of the svg seems unpredictable
  • documentation
  • how to display unselected vs selected
  • document unselected/selected
  • types for unselected/selected
  • fix the bug with markerEnd
  • document that brush doesn't yet work with line, area, contour marks etc. Is there a way we could auto-detect where it will work?
  • set initial value to data, even for figures
  • respect a render transform option
  • make it possible to have a brush with no data
  • understand why a brush with data: [{}] is slow
  • make the grey rect optional, or even opt-in with a rect mark?
  • a way to do something on gesture start and end?

nice to have:

  • cross-filter example (Tracking issue for 1.0 #1711)
  • What does brush: true do? I'm tempted to say that it adds {unselected:{opacity: 0.1}}
  • possible optimization 1: do not generate s and u if unselected and selected are undefined
  • possible optimization 2: faster filter by sorting the index along Xl, Xm, Yl, Ym
  • double-click to cancel, or click outside to cancel

demo: https://observablehq.com/@observablehq/plot-brush-interaction-1653

closes #5

supersedes #71 and (probably?) #721

Note that this produces a slightly different value during brushing and when brush ends; we should unify this pattern with pointer (#1832).

@Fil Fil requested a review from mbostock May 29, 2023 17:03
src/plot.js Outdated Show resolved Hide resolved
src/interactions/brush.js Outdated Show resolved Hide resolved
src/interactions/brush.js Outdated Show resolved Hide resolved
src/interactions/brush.js Outdated Show resolved Hide resolved
src/interactions/brush.js Outdated Show resolved Hide resolved
src/interactions/brush.js Outdated Show resolved Hide resolved
@Fil Fil force-pushed the fil/brush branch 2 times, most recently from d3d3f30 to b3db506 Compare August 21, 2023 12:05
@Fil Fil marked this pull request as ready for review August 22, 2023 16:21
Copy link
Member

@mbostock mbostock left a comment

Choose a reason for hiding this comment

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

This approach — brushing as a filtering render transform similar to the pointer interaction — is interesting! I hadn’t yet considered it, and I believe we never discussed it; our prior approaches to brushing were as a mark (#71 #721). But maybe I just forgot. 😅

I like that this approach gives some ability to control how the selected data are displayed. For the pointer interaction, the primary motivation was to display a tip selectively for the point closest to the mouse. But we found a number of other useful applications. Here I believe the primary motivation is to alter the display so as to highlight what data are selected vs. not selected. For example in a scatterplot, the dots might start out as black, and then while brushing we might draw the selected points red and the unselected points light gray.

This approach doesn’t yet give us quite what we need, though: it lets us conditionally draw selected data, but it doesn’t let us conditionally draw unselected data, and so the selected data are typically drawn atop a “non-interactive” mark which is always visible.

This approach also requires us to specify how to display selected data, even if we don’t want anything above the intrinsic display of the brush. We would have to declare an invisible mark, say by setting the stroke to none:

Plot.plot({
  marks: [
    Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm"}),
    Plot.dot(penguins, Plot.brush({x: "culmen_length_mm", y: "culmen_depth_mm", stroke: "none"}))
  ]
})

Whereas a brush mark would encourage this minimal display by default:

Plot.plot({
  marks: [
    Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm"}),
    Plot.brush(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm"})
  ]
})

We don’t have to solve this problem with the brush transform, though; ideally, we have a shorthand brush option like we did for the tip mark and pointer interaction. In that case, we could say:

Plot.plot({
  marks: [
    Plot.dot(penguins, {x: "culmen_length_mm", y: "culmen_depth_mm", brush: true})
  ]
})

So the real question is: what is the default display when applying the brush interaction to an existing mark? And will we support altering the display of what is not selected while brushing?

My ideal would be to start with an existing (“non-interactive”) mark definition, and then define “interactive overrides” (additional options) for selected and unselected data while brushing. Maybe like:

Plot.plot({
  marks: [
    Plot.dot(penguins, {
      x: "culmen_length_mm",
      y: "culmen_depth_mm",
      brush: {
        selected: {stroke: "red"},
        unselected: {stroke: "gray"}
      }
    })
  ]
})

I like the idea of doing this as a render transform — I just think we need three possible states (non-interactive, interactive and selected, interactive and unselected), and they should probably all be defined on the same mark rather than on separate marks.

If this proves too difficult, we could only offer the intrinsic visual representation of the brush, and have a brush mark instead of a brush transform. But I think we should continue exploring this direction! It seems promising!

docs/data/api.data.ts Show resolved Hide resolved
docs/features/interactions.md Outdated Show resolved Hide resolved
docs/features/interactions.md Outdated Show resolved Hide resolved
src/interactions/brush.js Outdated Show resolved Hide resolved
src/interactions/brush.js Outdated Show resolved Hide resolved
src/interactions/brush.d.ts Outdated Show resolved Hide resolved
@mbostock
Copy link
Member

mbostock commented Aug 30, 2023

I am exploring the use of render transforms to override mark appearance during interaction in the mbostock/pointer-highlight branch.

@Fil
Copy link
Contributor Author

Fil commented Sep 5, 2023

This branch now adopts the creator() API. I still have to figure how to type the selected and unselected options, though.

@seans84
Copy link

seans84 commented May 26, 2024

Is this PR dead? I'd really like to be able to use this.

@mishatsvelik
Copy link

@Fil Would be really nice to be able to have this working alongside tip/pointer in such a way that does not prevent them from emitting data. I.e. if a data point is clicked on via pointer and the plot chart is declared as a viewof, it should emit the data for that point rather than all the data (or at least configure brush to be able to be overridden in this way).

@Fil
Copy link
Contributor Author

Fil commented Sep 23, 2024

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.

Brushing
4 participants