Skip to content

Commit ddd6f33

Browse files
authored
Merge pull request #49 from davidbrochart/mouse_events
Add mouse events
2 parents 8aacbd8 + 1ab9a4d commit ddd6f33

File tree

4 files changed

+102
-56
lines changed

4 files changed

+102
-56
lines changed

docs/source/events.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Interactions
44
Using built-in events
55
---------------------
66

7-
There are currently two built-in mouse events: ``click`` and ``mouse_move``.
7+
The following built-in mouse events are supported: ``mouse_down``, ``mouse_move``, ``mouse_up`` and ``mouse_out``.
88

99
.. code:: Python
1010
@@ -14,11 +14,11 @@ There are currently two built-in mouse events: ``click`` and ``mouse_move``.
1414
1515
canvas.on_mouse_move(handle_mouse_move)
1616
17-
def handle_click(x, y):
17+
def handle_mouse_down(x, y):
1818
# Do something else
1919
pass
2020
21-
canvas.on_click(handle_click)
21+
canvas.on_mouse_down(handle_mouse_down)
2222
2323
.. note::
2424
Please open an issue or a Pull Request if you want more events to be supported by ipycanvas

examples/plotting.ipynb

Lines changed: 67 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -117,49 +117,71 @@
117117
"metadata": {},
118118
"outputs": [],
119119
"source": [
120-
"def scatter_plot(x, y, size, color, scheme=branca.colormap.linear.RdBu_11, stroke_color='black', canvas=None):\n",
121-
" canvas, drawarea, scale_x, scale_y, unscale_x, unscale_y, colormap = init_2d_plot(x, y, color, scheme, canvas=canvas)\n",
122-
"\n",
123-
" with hold_canvas(canvas):\n",
124-
" canvas.clear()\n",
125-
" canvas[1].save()\n",
126-
"\n",
127-
" draw_background(canvas[0], drawarea, unscale_x, unscale_y)\n",
128-
"\n",
129-
" # Draw scatter\n",
130-
" n_marks = min(x.shape[0], y.shape[0], size.shape[0], color.shape[0])\n",
131-
"\n",
132-
" canvas[1].stroke_style = stroke_color\n",
133-
"\n",
134-
" for idx in range(n_marks):\n",
135-
" canvas[1].fill_style = colormap(color[idx])\n",
136-
" \n",
137-
" mark_x = scale_x(x[idx])\n",
138-
" mark_y = scale_y(y[idx])\n",
139-
" mark_size = size[idx]\n",
140-
"\n",
141-
" canvas[1].fill_arc(mark_x, mark_y, mark_size, 0, 2 * pi)\n",
142-
" canvas[1].stroke_arc(mark_x, mark_y, mark_size, 0, 2 * pi)\n",
143-
"\n",
144-
" canvas[1].restore()\n",
145-
"\n",
146-
" def click_handler(pixel_x, pixel_y):\n",
147-
" unscaled_x = unscale_x(pixel_x)\n",
148-
" unscaled_y = unscale_y(pixel_y)\n",
149-
"\n",
150-
" for idx in range(n_marks):\n",
151-
" mark_x = x[idx]\n",
152-
" mark_y = y[idx]\n",
153-
" mark_size = size[idx]\n",
154-
"\n",
155-
" if (pixel_x > scale_x(mark_x) - mark_size and pixel_x < scale_x(mark_x) + mark_size and\n",
156-
" pixel_y > scale_y(mark_y) - mark_size and pixel_y < scale_y(mark_y) + mark_size):\n",
157-
" canvas[2].fill_style = 'red'\n",
158-
" canvas[2].fill_arc(scale_x(mark_x), scale_y(mark_y), mark_size, 0, 2 * pi)\n",
159-
"\n",
160-
" canvas[2].on_click(click_handler)\n",
161-
"\n",
162-
" return canvas"
120+
"class Scatter_plot(object):\n",
121+
" def __init__(self, x, y, size, color, scheme=branca.colormap.linear.RdBu_11, stroke_color='black', canvas=None):\n",
122+
" \n",
123+
" self.dragging = False\n",
124+
" self.x = x\n",
125+
" self.y = y\n",
126+
" self.size = size\n",
127+
" self.color = color\n",
128+
" self.stroke_color = stroke_color\n",
129+
" self.canvas, self.drawarea, self.scale_x, self.scale_y, self.unscale_x, self.unscale_y, self.colormap = init_2d_plot(x, y, color, scheme, canvas=canvas)\n",
130+
" self.show()\n",
131+
" \n",
132+
" self.canvas[2].on_mouse_down(self.mouse_down_handler)\n",
133+
" self.canvas[2].on_mouse_move(self.mouse_move_handler)\n",
134+
" self.canvas[2].on_mouse_up(self.mouse_up_handler)\n",
135+
"\n",
136+
" def _ipython_display_(self):\n",
137+
" display(self.canvas)\n",
138+
"\n",
139+
" def show(self):\n",
140+
" with hold_canvas(self.canvas):\n",
141+
" self.canvas.clear()\n",
142+
" self.canvas[1].save()\n",
143+
"\n",
144+
" draw_background(self.canvas[0], self.drawarea, self.unscale_x, self.unscale_y)\n",
145+
"\n",
146+
" # Draw scatter\n",
147+
" self.n_marks = min(x.shape[0], y.shape[0], self.size.shape[0], self.color.shape[0])\n",
148+
"\n",
149+
" self.canvas[1].stroke_style = self.stroke_color\n",
150+
"\n",
151+
" for idx in range(self.n_marks):\n",
152+
" self.canvas[1].fill_style = self.colormap(self.color[idx])\n",
153+
"\n",
154+
" mark_x = self.scale_x(x[idx])\n",
155+
" mark_y = self.scale_y(y[idx])\n",
156+
" mark_size = self.size[idx]\n",
157+
"\n",
158+
" self.canvas[1].fill_arc(mark_x, mark_y, mark_size, 0, 2 * pi)\n",
159+
" self.canvas[1].stroke_arc(mark_x, mark_y, mark_size, 0, 2 * pi)\n",
160+
"\n",
161+
" self.canvas[1].restore()\n",
162+
"\n",
163+
" def mouse_down_handler(self, pixel_x, pixel_y):\n",
164+
" for idx in range(self.n_marks):\n",
165+
" mark_x = self.x[idx]\n",
166+
" mark_y = self.y[idx]\n",
167+
" mark_size = self.size[idx]\n",
168+
"\n",
169+
" if (pixel_x > self.scale_x(mark_x) - mark_size and pixel_x < self.scale_x(mark_x) + mark_size and\n",
170+
" pixel_y > self.scale_y(mark_y) - mark_size and pixel_y < self.scale_y(mark_y) + mark_size):\n",
171+
" self.i_mark = idx\n",
172+
" self.dragging = True\n",
173+
" break\n",
174+
" \n",
175+
" def mouse_move_handler(self, pixel_x, pixel_y):\n",
176+
" if self.dragging:\n",
177+
" unscaled_x = self.unscale_x(pixel_x)\n",
178+
" unscaled_y = self.unscale_y(pixel_y)\n",
179+
" self.x[self.i_mark] = unscaled_x\n",
180+
" self.y[self.i_mark] = unscaled_y\n",
181+
" self.show()\n",
182+
" \n",
183+
" def mouse_up_handler(self, pixel_x, pixel_y):\n",
184+
" self.dragging = False"
163185
]
164186
},
165187
{
@@ -264,7 +286,7 @@
264286
"cell_type": "markdown",
265287
"metadata": {},
266288
"source": [
267-
"### Scatter marks are clickable! Try clicking on them"
289+
"### Scatter marks are draggable! Move the mouse while clicking on them..."
268290
]
269291
},
270292
{
@@ -278,7 +300,7 @@
278300
"sizes = np.random.randint(2, 8, n_points)\n",
279301
"colors = np.random.rand(n_points) * 10 - 2\n",
280302
"\n",
281-
"plot = scatter_plot(x, y, sizes, colors, branca.colormap.linear.viridis, stroke_color='white')\n",
303+
"plot = Scatter_plot(x, y, sizes, colors, branca.colormap.linear.viridis, stroke_color='white')\n",
282304
"plot"
283305
]
284306
},

ipycanvas/canvas.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,9 @@ class Canvas(DOMWidget):
117117

118118
_client_ready_callbacks = Instance(CallbackDispatcher, ())
119119
_mouse_move_callbacks = Instance(CallbackDispatcher, ())
120-
_click_callbacks = Instance(CallbackDispatcher, ())
120+
_mouse_down_callbacks = Instance(CallbackDispatcher, ())
121+
_mouse_up_callbacks = Instance(CallbackDispatcher, ())
122+
_mouse_out_callbacks = Instance(CallbackDispatcher, ())
121123

122124
def __init__(self, *args, **kwargs):
123125
"""Create a Canvas widget."""
@@ -466,9 +468,17 @@ def on_mouse_move(self, callback, remove=False):
466468
"""Register a callback that will be called on mouse mouse_move."""
467469
self._mouse_move_callbacks.register_callback(callback, remove=remove)
468470

469-
def on_click(self, callback, remove=False):
470-
"""Register a callback that will be called on mouse click."""
471-
self._click_callbacks.register_callback(callback, remove=remove)
471+
def on_mouse_down(self, callback, remove=False):
472+
"""Register a callback that will be called on mouse mouse_down."""
473+
self._mouse_down_callbacks.register_callback(callback, remove=remove)
474+
475+
def on_mouse_up(self, callback, remove=False):
476+
"""Register a callback that will be called on mouse mouse_up."""
477+
self._mouse_up_callbacks.register_callback(callback, remove=remove)
478+
479+
def on_mouse_out(self, callback, remove=False):
480+
"""Register a callback that will be called on mouse mouse_out."""
481+
self._mouse_out_callbacks.register_callback(callback, remove=remove)
472482

473483
def __setattr__(self, name, value):
474484
super(Canvas, self).__setattr__(name, value)
@@ -505,8 +515,12 @@ def _handle_frontend_event(self, _, content, buffers):
505515
self._client_ready_callbacks()
506516
if content.get('event', '') == 'mouse_move':
507517
self._mouse_move_callbacks(content['x'], content['y'])
508-
if content.get('event', '') == 'click':
509-
self._click_callbacks(content['x'], content['y'])
518+
if content.get('event', '') == 'mouse_down':
519+
self._mouse_down_callbacks(content['x'], content['y'])
520+
if content.get('event', '') == 'mouse_up':
521+
self._mouse_up_callbacks(content['x'], content['y'])
522+
if content.get('event', '') == 'mouse_out':
523+
self._mouse_out_callbacks(content['x'], content['y'])
510524

511525

512526
class MultiCanvas(DOMWidget):

src/widget.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,10 @@ class CanvasView extends DOMWidgetView {
315315
this.resizeCanvas();
316316
this.model.on('change:size', this.resizeCanvas.bind(this));
317317

318-
this.canvas.addEventListener('click', { handleEvent: this.onMouseDown.bind(this) });
319318
this.canvas.addEventListener('mousemove', { handleEvent: this.onMouseMove.bind(this) });
319+
this.canvas.addEventListener('mousedown', { handleEvent: this.onMouseDown.bind(this) });
320+
this.canvas.addEventListener('mouseup', { handleEvent: this.onMouseUp.bind(this) });
321+
this.canvas.addEventListener('mouseout', { handleEvent: this.onMouseOut.bind(this) });
320322

321323
this.updateCanvas();
322324
}
@@ -337,13 +339,21 @@ class CanvasView extends DOMWidgetView {
337339
}
338340

339341
private onMouseDown(event: MouseEvent) {
340-
this.model.send({ event: 'click', ...this.getMouseCoordinate(event) }, {});
342+
this.model.send({ event: 'mouse_down', ...this.getMouseCoordinate(event) }, {});
341343
}
342344

343345
private onMouseMove(event: MouseEvent) {
344346
this.model.send({ event: 'mouse_move', ...this.getMouseCoordinate(event) }, {});
345347
}
346348

349+
private onMouseUp(event: MouseEvent) {
350+
this.model.send({ event: 'mouse_up', ...this.getMouseCoordinate(event) }, {});
351+
}
352+
353+
private onMouseOut(event: MouseEvent) {
354+
this.model.send({ event: 'mouse_out', ...this.getMouseCoordinate(event) }, {});
355+
}
356+
347357
private getMouseCoordinate(event: MouseEvent) {
348358
const rect = this.canvas.getBoundingClientRect();
349359
const x = event.clientX - rect.left;

0 commit comments

Comments
 (0)