-
Notifications
You must be signed in to change notification settings - Fork 178
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
base: main
Are you sure you want to change the base?
brush interaction #1653
Conversation
d3d3f30
to
b3db506
Compare
There was a problem hiding this 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!
I am exploring the use of render transforms to override mark appearance during interaction in the mbostock/pointer-highlight branch. |
This branch now adopts the creator() API. I still have to figure how to type the selected and unselected options, though. |
test viewof
- throws an error if multiple brushes appear on the same chart - puts the brush receiver at the top of the svg (z-index) - repairs aria-label
Is this PR dead? I'd really like to be able to use this. |
@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 |
@mishatsvelik ref. your notebook https://observablehq.com/d/6f3b6e6206c3451b. |
put the brush on top of the svg?add an option to make viewof the geometric selection (vs. data selection)?could a brush span multiple facets? (but then, no geometric selection…)Using a RAF to append the brushable g elements to the top of the svg seems unpredictablenice to have:
{unselected:{opacity: 0.1}}
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).