Events
Overview
The Plot.events
mark supports mouse interactions via the following callbacks: onDrawStart
, onDraw
, onDrawEnd
, onClick
, and onMouseMove
.
Each callback receives an event object containing type
(the event name), x
, y
, and startTime
(for draw events only, to distinguish one draw event from another).
(
Plot.initialState({"points": []})
# setting $state.points in the `onDraw` callback,
# which is passed an event containing a `point`, an `[x, y]` array.
+ Plot.events(
onDrawStart=js("(event) => $state.points = [[event.x, event.y]]"),
onDraw=js("(event) => $state.points = [...$state.points, [event.x, event.y]]"),
)
# Draw a line through all points
+ Plot.line(js("$state.points"), stroke="blue", strokeWidth=4)
+ Plot.ellipse([[1, 1]], r=1, opacity=0.5, fill="red")
+ Plot.domain([0, 2])
)
Drawing Example
interactivity_warning
Say we wanted to pass a drawn path back to Python. We can initialize a ref, with an initial value of an empty list, to hold drawn points. Then, we pass in a python onDraw
callback to update the points using the widget's state.update
method. This time, let's add some additional dot marks to make our line more interesting.
(
Plot.initialState(
{"all_points": [], "drawn_points": [], "clicked_points": []}, sync=True
)
# Create drawing area and update points on draw
| Plot.events(
onDraw=js(
"(e) => $state.update(['drawn_points', 'append', [e.x, e.y, e.startTime]])"
),
onMouseMove=js("(e) => $state.update(['all_points', 'append', [e.x, e.y]])"),
onClick=js("(e) => $state.update(['clicked_points', 'append', [e.x, e.y]])"),
)
# Draw a continuous line through drawn points
+ Plot.line(js("$state.drawn_points"), z="2")
# Add small dots for drawn points
+ Plot.dot(js("$state.drawn_points"))
# Highlight every 6th drawn point in red
+ Plot.dot(
js("$state.drawn_points"),
Plot.select(
js("(indexes) => indexes.filter(i => i % 6 === 0)"),
{"fill": "red", "r": 10},
),
)
# Add symbol for clicked points
+ Plot.dot(js("$state.clicked_points"), r=10, symbol="star")
# Add light gray line for all points
+ Plot.line(js("$state.all_points"), stroke="rgba(0, 0, 0, 0.2)")
+ Plot.domain([0, 2])
| [
"div.bg-blue-500.text-white.p-3.rounded-sm",
{"onClick": lambda widget, e: print(widget.state.clicked_points)},
"Print clicked points",
]
| [
"div.bg-blue-500.text-white.p-3.rounded-sm",
{
"onClick": lambda widget, e: widget.state.update(
{"all_points": [], "drawn_points": []}
)
},
"Clear Line",
]
) | Plot.onChange({"clicked_points": print})
The onDraw
callback function updates the points
state with the newly drawn path. This triggers a re-render of the plot, immediately reflecting the user's drawing.
Child Events
interactivity_warning
This example demonstrates how to create an interactive scatter plot with draggable points. We will use Plot.renderChildEvents
, a render transform. It handles click and drag events for any mark which produces an ordered list of svg elements, such as Plot.dot
.
We first define a reference with initial point coordinates to represent the points that we want to interact with.
Next we define a callback function, which will receive mouse events from our plot. Each event will contain information about the child that triggered the event, as well as a reference to the current widget, which has a .state.update
method. This is what allows us to modify the plot in response to user actions.
When creating the plot, we pass Plot.render.childEvents
as a render
option to the Plot.dot
mark. For demonstration purposes we also include a Plot.ellipse
mark behind the interactive dots. The Plot.dot
mark updates immediately in JavaScript, while the Plot.Ellipse
mark updates only in response to our callback.
(
Plot.ellipse(data, {"fill": "cyan", "fillOpacity": 0.5, "r": 0.2})
+ Plot.dot(
data,
render=Plot.renderChildEvents(
{"onDrag": update_position, "onDragEnd": print, "onClick": print}
),
)
+ Plot.domain([0, 2])
+ Plot.aspectRatio(1)
).display_as("widget")