-
Notifications
You must be signed in to change notification settings - Fork 185
brush interaction #1653
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
wants to merge
46
commits into
main
Choose a base branch
from
fil/brush
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
brush interaction #1653
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
c269a48
brush interaction
Fil addf3c5
remove metaKey test
Fil 7824515
apply suggestions from review
Fil a04ce1a
simpler! and no RAF
Fil ae143a9
fix tests
Fil 548c25e
cleaner derived "channels" Xl and Xm
Fil 5c13eca
put the brushes at the top (z-index) of the svg
Fil 4b98418
error if multiple brushes have been set on this chart
Fil 78bda67
This solves quite a few issues:
Fil 72a4bb4
extent (or logical) selectionMode
Fil 7e8d5ff
better extent
Fil eedbc23
initialize with data
Fil 0f120d2
datum
Fil d3a9c94
cleaner, avoid a crash if the mark does not return a node
Fil e153848
document
Fil cf36270
fix link
Fil 20a586c
fix links
Fil 33f0eac
use Promise.resolve to get to the top z-index
Fil e61d6a9
highlight experiment
mbostock 854a5b6
delegate render to derived mark
mbostock 277709b
long form sketch
mbostock ec463e2
Adopt creator()
Fil 5999ba6
k
Fil ac48639
initial value
Fil e4b6acf
do not mutate data!
Fil 183e084
aria-label brush
Fil 6c1108c
fix: markerEnd with a null channel
Fil bf3c728
show value on all brush tests
Fil 7acd0ce
show value.done
Fil 155b184
alphabetical order of case statements
Fil 347f085
combine onMounted
Fil 4da6662
more documentation
Fil 19a60ed
add version badge
Fil 428e33a
Merge branch 'main' into fil/brush
Fil 4c29052
don't dispatch an event if not connected — it can only be an initiali…
Fil 820d4d9
promote a value set up during initialization to the figure
Fil 9dd5644
test the initial value
Fil 02710bb
respect an existing render transform
Fil 4b035f5
Merge branch 'main' into fil/brush
Fil 115e2e5
Merge branch 'main' into fil/brush
Fil a41572d
update tests
Fil e3fe4fa
Merge branch 'main' into fil/brush
Fil b2f17c7
Merge branch 'main' into fil/brush
Fil db508ce
update tests
Fil ac82b51
Merge branch 'main' into fil/brush
Fil 399fcf5
Merge branch 'main' into fil/brush
Fil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<script setup> | ||
|
||
import * as Plot from "@observablehq/plot"; | ||
import * as d3 from "d3"; | ||
import {ref, shallowRef, onMounted} from "vue"; | ||
|
||
const penguins = shallowRef([]); | ||
|
||
onMounted(() => { | ||
d3.csv("../data/penguins.csv", d3.autoType).then((data) => (penguins.value = data)); | ||
}); | ||
|
||
</script> | ||
|
||
# Brush transform | ||
|
||
The **brush transform** allows the interactive selection of discrete elements, such as dots in a scatterplot, by direct manipulation of the chart. A brush listens to mouse and touch events on the chart, allowing the user to define a rectangular region. All the data points that fall within the region are included in the selection. | ||
|
||
:::plot defer | ||
```js | ||
Plot.dot( | ||
penguins, | ||
Plot.brush({ | ||
x: "culmen_length_mm", | ||
y: "culmen_depth_mm", | ||
stroke: "currentColor", | ||
fill: "#fff", | ||
unselected: {strokeOpacity: 0.5}, | ||
selected: {fill: "species"} | ||
}) | ||
).plot() | ||
``` | ||
::: | ||
|
||
When the chart has a dominant axis, an horizontal or vertical brush is recommended; for example, to select bars in a histogram: | ||
|
||
:::plot defer | ||
```js | ||
Plot.rectY( | ||
penguins, | ||
Plot.brushX( | ||
Plot.binX( | ||
{y: "count"}, | ||
{ | ||
x: "body_mass_g", | ||
thresholds: 40, | ||
unselected: {opacity: 0.1}, | ||
} | ||
) | ||
) | ||
).plot() | ||
``` | ||
::: | ||
|
||
The brush transform interactively partitions the mark’s index in two: the unselected subset — for points outside the region —, and the selected subset for points inside. As the selection changes, the mark is replaced by two derived marks: below, a mark for the unselected data, with the mark options combined with the **unselected** option; above, a mark for the selected data, with the mark options combined with the **selected** option. All the channel values are incorporated into default scale domains, allowing *e.g.* a color scale to include the fill channel of the selected mark. | ||
|
||
The brush transform supports both one- and two-dimensional brushing modes. The two-dimensional mode, [brush](#brush), is suitable for scatterplots and the general case: it allows the user to define a rectangular region by clicking on a corner (_e.g._ the top-left corner) and dragging the pointer to the bottom-right corner. The one-dimensional modes, [brushX](#brushX) and [brushY](#brushY), in contrast only consider one dimension; this is desirable when a chart has a “dominant” dimension, such as time in a time-series chart, the binned quantitative dimension in a histogram, or the categorical dimension of a bar chart. | ||
|
||
The brush transform emits an [*input* event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/input_event) whenever the selection changes, and sets the value of the plot element to the selected data. This allows you to use a plot as an [Observable view](https://observablehq.com/@observablehq/views) (viewof), or to register an *input* event listener to react to brushing. | ||
|
||
## Brush options | ||
|
||
The following options control the brush transform: | ||
|
||
- **x1** - the starting horizontal↔︎ target position; bound to the *x* scale | ||
- **y1** - the starting vertical↕︎ target position; bound to the *y* scale | ||
- **x2** - the ending horizontal↔︎ target position; bound to the *x* scale | ||
- **y2** - the ending vertical↕︎ target position; bound to the *y* scale | ||
- **x** - the fallback horizontal↔︎ target position; bound to the *x* scale | ||
- **y** - the fallback vertical↕︎ target position; bound to the *y* scale | ||
- **selected** - additional options for the derived mark representing the selection | ||
- **unselected** - additional options for the derived mark representing non-selected data | ||
|
||
The positional options define a sensitive surface for each data point, defined on the horizontal axis as the extent between *x1* and *x2* if specified, between *x* and *x + bandwidth* if *x* is a band scale, or the value *x* otherwise. The sensitive surface’s vertical extent likewise spans from *y1* to *y2* if specified, from *y* to *y + bandwidth* if *y* is a band scale, or is equal to the *y* value otherwise. | ||
|
||
When the user interacts with the plot by clicking and dragging the brush to define a rectangular region, all the elements whose sensitive surface intersect with the brushed region are selected, and the derived marks are re-rendered. | ||
|
||
The selected data exposed as the value of the plot is an array of the (possibly transformed) data rendered by the *selected* derived mark. For example, in the case of the histogram above, the selected data is an array of bins, each containing the penguins whose body mass is between the bin’s lower and upper bounds. | ||
|
||
The value is decorated with the brush’s coordinates (in data space) as its **x1** and **x2** properties for a quantitative scale *x*, and its **x** property if *x* is ordinal — and likewise for *y*. The value is also decorated with a **done** property set to false while brushing, true when the user releases the pointer, and undefined when the brush is canceled. Additionally, when faceting, it exposes the brushed facet’s *fx* and *fy* properties. | ||
|
||
For details on the user interface (including touch events, pointer events and modifier keys), see [d3-brush](https://github.com/d3/d3-brush). | ||
|
||
## brush(*options*) {#brush} | ||
|
||
```js | ||
Plot.dot(penguins, Plot.brush({x: "culmen_length_mm", y: "culmen_depth_mm"})) | ||
``` | ||
|
||
Applies the brush render transform to the specified *options* to filter the mark index such that the points whose sensitive surface intersect with the brushed region the point closest to the pointer is rendered. | ||
|
||
## brushX(*options*) {#brushX} | ||
|
||
```js | ||
Plot.tip(aapl, Plot.pointerX({x: "Date", y: "Close"})) | ||
``` | ||
|
||
Like [brush](#brush), except the determination of the intersection exclusively considers the *x* (horizontal↔︎) position; this should be used for plots where *x* is the dominant dimension, such as the binned quantitative dimension in a histogram, or the categorical dimension of a bar chart. | ||
|
||
## brushY(*options*) {#brushY} | ||
|
||
```js | ||
Plot.tip(alphabet, Plot.pointerY({x: "frequency", y: "letter"})) | ||
``` | ||
|
||
Like [brush](#brush), except the determination of the intersection exclusively considers the *y* (vertical↕) position; this should be used for plots where *y* is the dominant dimension. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import type {Rendered} from "../transforms/basic.js"; | ||
|
||
/** Options for the brush transform. */ | ||
type BrushOptions = { | ||
/** | ||
* How to display the selected mark when the user manipulates the brush. | ||
*/ | ||
selected?: null; // TODO | ||
/** | ||
* How to display the unselected mark when the user manipulates the brush. | ||
*/ | ||
unselected?: null; | ||
/** | ||
* The brush’s padding, defaults to 1. | ||
*/ | ||
padding?: number; | ||
}; | ||
|
||
/** | ||
* Applies a render transform to the specified *options* to filter the mark | ||
* index such that only the point closest to the pointer is rendered; the mark | ||
* will re-render interactively in response to pointer events. | ||
*/ | ||
export function brush<T>(options: T & BrushOptions): Rendered<T>; | ||
|
||
/** | ||
* Like the pointer transform, except the determination of the closest point | ||
* considers mostly the *x* (horizontal↔︎) position; this should be used for | ||
* plots where *x* is the dominant dimension, such as time in a time-series | ||
* chart, the binned quantitative dimension in a histogram, or the categorical | ||
* dimension of a bar chart. | ||
*/ | ||
export function brushX<T>(options: T & BrushOptions): Rendered<T>; | ||
|
||
/** | ||
* Like the pointer transform, except the determination of the closest point | ||
* considers mostly the *y* (vertical↕︎) position; this should be used for plots | ||
* where *y* is the dominant dimension, such as time in a time-series chart, the | ||
* binned quantitative dimension in a histogram, or the categorical dimension of | ||
* a bar chart. | ||
*/ | ||
export function brushY<T>(options: T & BrushOptions): Rendered<T>; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.