Skip to content

Commit f2da812

Browse files
committed
Add mouse events
1 parent d3b5471 commit f2da812

File tree

3 files changed

+97
-47
lines changed

3 files changed

+97
-47
lines changed

examples/plotting.ipynb

Lines changed: 64 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -117,49 +117,68 @@
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 show(self):\n",
137+
" with hold_canvas(self.canvas):\n",
138+
" self.canvas.clear()\n",
139+
" self.canvas[1].save()\n",
140+
"\n",
141+
" draw_background(self.canvas[0], self.drawarea, self.unscale_x, self.unscale_y)\n",
142+
"\n",
143+
" # Draw scatter\n",
144+
" self.n_marks = min(x.shape[0], y.shape[0], self.size.shape[0], self.color.shape[0])\n",
145+
"\n",
146+
" self.canvas[1].stroke_style = self.stroke_color\n",
147+
"\n",
148+
" for idx in range(self.n_marks):\n",
149+
" self.canvas[1].fill_style = self.colormap(self.color[idx])\n",
150+
"\n",
151+
" mark_x = self.scale_x(x[idx])\n",
152+
" mark_y = self.scale_y(y[idx])\n",
153+
" mark_size = self.size[idx]\n",
154+
"\n",
155+
" self.canvas[1].fill_arc(mark_x, mark_y, mark_size, 0, 2 * pi)\n",
156+
" self.canvas[1].stroke_arc(mark_x, mark_y, mark_size, 0, 2 * pi)\n",
157+
"\n",
158+
" self.canvas[1].restore()\n",
159+
"\n",
160+
" def mouse_down_handler(self, pixel_x, pixel_y):\n",
161+
" for idx in range(self.n_marks):\n",
162+
" mark_x = self.x[idx]\n",
163+
" mark_y = self.y[idx]\n",
164+
" mark_size = self.size[idx]\n",
165+
"\n",
166+
" if (pixel_x > self.scale_x(mark_x) - mark_size and pixel_x < self.scale_x(mark_x) + mark_size and\n",
167+
" pixel_y > self.scale_y(mark_y) - mark_size and pixel_y < self.scale_y(mark_y) + mark_size):\n",
168+
" self.i_mark = idx\n",
169+
" self.dragging = True\n",
170+
" break\n",
171+
" \n",
172+
" def mouse_move_handler(self, pixel_x, pixel_y):\n",
173+
" if (self.dragging):\n",
174+
" unscaled_x = self.unscale_x(pixel_x)\n",
175+
" unscaled_y = self.unscale_y(pixel_y)\n",
176+
" self.x[self.i_mark] = unscaled_x\n",
177+
" self.y[self.i_mark] = unscaled_y\n",
178+
" self.show()\n",
179+
" \n",
180+
" def mouse_up_handler(self, pixel_x, pixel_y):\n",
181+
" self.dragging = False"
163182
]
164183
},
165184
{
@@ -264,7 +283,7 @@
264283
"cell_type": "markdown",
265284
"metadata": {},
266285
"source": [
267-
"### Scatter marks are clickable! Try clicking on them"
286+
"### Scatter marks are draggable! Move the mouse while clicking on them..."
268287
]
269288
},
270289
{
@@ -278,7 +297,7 @@
278297
"sizes = np.random.randint(2, 8, n_points)\n",
279298
"colors = np.random.rand(n_points) * 10 - 2\n",
280299
"\n",
281-
"plot = scatter_plot(x, y, sizes, colors, branca.colormap.linear.viridis, stroke_color='white')\n",
300+
"plot = Scatter_plot(x, y, sizes, colors, branca.colormap.linear.viridis, stroke_color='white').canvas\n",
282301
"plot"
283302
]
284303
},

ipycanvas/canvas.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ class Canvas(DOMWidget):
109109

110110
_client_ready_callbacks = Instance(CallbackDispatcher, ())
111111
_mouse_move_callbacks = Instance(CallbackDispatcher, ())
112+
_mouse_down_callbacks = Instance(CallbackDispatcher, ())
113+
_mouse_up_callbacks = Instance(CallbackDispatcher, ())
114+
_mouse_out_callbacks = Instance(CallbackDispatcher, ())
112115
_click_callbacks = Instance(CallbackDispatcher, ())
113116

114117
def __init__(self, *args, **kwargs):
@@ -445,6 +448,18 @@ def on_mouse_move(self, callback, remove=False):
445448
"""Register a callback that will be called on mouse mouse_move."""
446449
self._mouse_move_callbacks.register_callback(callback, remove=remove)
447450

451+
def on_mouse_down(self, callback, remove=False):
452+
"""Register a callback that will be called on mouse mouse_down."""
453+
self._mouse_down_callbacks.register_callback(callback, remove=remove)
454+
455+
def on_mouse_up(self, callback, remove=False):
456+
"""Register a callback that will be called on mouse mouse_up."""
457+
self._mouse_up_callbacks.register_callback(callback, remove=remove)
458+
459+
def on_mouse_out(self, callback, remove=False):
460+
"""Register a callback that will be called on mouse mouse_out."""
461+
self._mouse_out_callbacks.register_callback(callback, remove=remove)
462+
448463
def on_click(self, callback, remove=False):
449464
"""Register a callback that will be called on mouse click."""
450465
self._click_callbacks.register_callback(callback, remove=remove)
@@ -484,8 +499,13 @@ def _handle_frontend_event(self, _, content, buffers):
484499
self._client_ready_callbacks()
485500
if content.get('event', '') == 'mouse_move':
486501
self._mouse_move_callbacks(content['x'], content['y'])
487-
if content.get('event', '') == 'click':
502+
if content.get('event', '') == 'mouse_down':
488503
self._click_callbacks(content['x'], content['y'])
504+
self._mouse_down_callbacks(content['x'], content['y'])
505+
if content.get('event', '') == 'mouse_up':
506+
self._mouse_up_callbacks(content['x'], content['y'])
507+
if content.get('event', '') == 'mouse_out':
508+
self._mouse_out_callbacks(content['x'], content['y'])
489509

490510

491511
class MultiCanvas(DOMWidget):

src/widget.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,10 @@ class CanvasView extends DOMWidgetView {
296296
this.resizeCanvas();
297297
this.model.on('change:size', this.resizeCanvas.bind(this));
298298

299-
this.canvas.addEventListener('click', { handleEvent: this.onMouseDown.bind(this) });
300299
this.canvas.addEventListener('mousemove', { handleEvent: this.onMouseMove.bind(this) });
300+
this.canvas.addEventListener('mousedown', { handleEvent: this.onMouseDown.bind(this) });
301+
this.canvas.addEventListener('mouseup', { handleEvent: this.onMouseUp.bind(this) });
302+
this.canvas.addEventListener('mouseout', { handleEvent: this.onMouseOut.bind(this) });
301303

302304
this.updateCanvas();
303305
}
@@ -319,12 +321,21 @@ class CanvasView extends DOMWidgetView {
319321

320322
private onMouseDown(event: MouseEvent) {
321323
this.model.send({ event: 'click', ...this.getMouseCoordinate(event) }, {});
324+
this.model.send({ event: 'mouse_down', ...this.getMouseCoordinate(event) }, {});
322325
}
323326

324327
private onMouseMove(event: MouseEvent) {
325328
this.model.send({ event: 'mouse_move', ...this.getMouseCoordinate(event) }, {});
326329
}
327330

331+
private onMouseUp(event: MouseEvent) {
332+
this.model.send({ event: 'mouse_up', ...this.getMouseCoordinate(event) }, {});
333+
}
334+
335+
private onMouseOut(event: MouseEvent) {
336+
this.model.send({ event: 'mouse_out', ...this.getMouseCoordinate(event) }, {});
337+
}
338+
328339
private getMouseCoordinate(event: MouseEvent) {
329340
const rect = this.canvas.getBoundingClientRect();
330341
const x = event.clientX - rect.left;

0 commit comments

Comments
 (0)