Mouse events
import colight.plot as Plot
from colight.plot import js
interactivity_warning = Plot.html(
[
"div.bg-black.text-white.p-3",
"""This example depends on communication with a python backend, and will not be interactive on the docs website.""",
],
)
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.State({"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.
import colight.plot as Plot
from colight.plot import js
(
Plot.State({"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.
import colight.plot as Plot
data = Plot.ref([[1, 1], [2, 2], [0, 2], [2, 0]])
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.
def update_position(widget, event):
x = event["x"]
y = event["y"]
index = event["index"]
widget.state.update([data, "setAt", [index, [x, y]]])
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")