Skip to content

Commit

Permalink
feat: mouse move events exposed
Browse files Browse the repository at this point in the history
  • Loading branch information
maartenbreddels committed Feb 5, 2021
1 parent 4eea0ad commit 3227348
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 3 deletions.
4 changes: 3 additions & 1 deletion bqplot_image_gl/interacts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from bqplot.interacts import BrushSelector, Interaction
from bqplot.scales import Scale
from traitlets import Float, Unicode, Dict, Instance
from traitlets import Float, Unicode, Dict, Instance, Int
from ipywidgets.widgets.widget import widget_serialization
from bqplot_image_gl._version import __version__

Expand Down Expand Up @@ -79,6 +79,7 @@ class MouseInteraction(Interaction):
y_scale: An instance of Scale
This is the scale which is used for inversion from the pixels to data
co-ordinates in the y-direction.
move_throttle: Send mouse move events only every specified milliseconds.
"""
_view_module = Unicode('bqplot-image-gl').tag(sync=True)
_model_module = Unicode('bqplot-image-gl').tag(sync=True)
Expand All @@ -91,3 +92,4 @@ class MouseInteraction(Interaction):
y_scale = Instance(Scale, allow_none=True, default_value=None)\
.tag(sync=True, dimension='y', **widget_serialization)
cursor = Unicode('auto').tag(sync=True)
move_throttle = Int(50).tag(sync=True)
171 changes: 171 additions & 0 deletions examples/mouse.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "discrete-retention",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import math\n",
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ethical-posting",
"metadata": {},
"outputs": [],
"source": [
"with open('./data.json') as f:\n",
" data = json.load(f)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "emerging-marking",
"metadata": {},
"outputs": [],
"source": [
"values = np.array(data['values'], dtype='float32')\n",
"values = values.reshape((data['height'], data['width']))[:10,]\n",
"values.shape"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "twelve-cabinet",
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from bqplot import Figure, LinearScale, Axis, ColorScale\n",
"from bqplot_image_gl import ImageGL, Contour\n",
"import ipywidgets as widgets\n",
"scale_x = LinearScale(min=-1, max=4, allow_padding=False)\n",
"scale_y = LinearScale(min=-1, max=4, allow_padding=False)\n",
"scales = {'x': scale_x, 'y': scale_y}\n",
"axis_x = Axis(scale=scale_x, label='x')\n",
"axis_y = Axis(scale=scale_y, label='y', orientation='vertical')\n",
"scales_image = {'x': scale_x, 'y': scale_y, 'image': ColorScale(min=np.min(values).item(), max=np.max(values).item())}\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "appreciated-transformation",
"metadata": {},
"outputs": [],
"source": [
"figure = Figure(scales=scales, axes=[axis_x, axis_y])\n",
"image = ImageGL(image=values, scales=scales_image, x=[0, 2], y=[0, 2])\n",
"figure.marks = (image, )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "buried-cooperation",
"metadata": {},
"outputs": [],
"source": [
"figure"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "sufficient-shirt",
"metadata": {},
"outputs": [],
"source": [
"from bqplot_image_gl.interacts import MouseInteraction"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "empirical-model",
"metadata": {},
"outputs": [],
"source": [
"widget_label = widgets.Label(value=\"move cursor for information\")\n",
"widget_label"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "known-possession",
"metadata": {},
"outputs": [],
"source": [
"interaction = MouseInteraction(x_scale=scales_image['x'], y_scale=scales_image['y'], move_throttle=70)\n",
"figure.interaction = interaction\n",
"def on_mouse_msg(interaction, data, buffers):\n",
" # it might be a good idea to throttle on the Python side as well, for instance when many computations\n",
" # happen, we can effectively ignore the queue of messages\n",
" if data['event'] == 'mousemove':\n",
" domain_x = data['domain']['x']\n",
" domain_y = data['domain']['y']\n",
" normalized_x = (domain_x - image.x[0]) / (image.x[1] - image.x[0])\n",
" normalized_y = (domain_y - image.y[0]) / (image.y[1] - image.y[0])\n",
" # TODO: think about +/-1 and pixel edges\n",
" pixel_x = int(math.floor(normalized_x * image.image.shape[1]))\n",
" pixel_y = int(math.floor(normalized_y * image.image.shape[0]))\n",
" if pixel_x >= 0 and pixel_x < image.image.shape[1] and pixel_y >= 0 and pixel_y < image.image.shape[0]:\n",
" value = str(image.image[pixel_y, pixel_x])\n",
" else:\n",
" value = \"out of range\"\n",
" msg = f\"x={pixel_x} y={pixel_y} value={value} (nx={normalized_x} ny={normalized_y})\"\n",
" widget_label.value = msg\n",
" elif data['event'] == 'mouseleave':\n",
" widget_label.value = \"Bye!\"\n",
" elif data['event'] == 'mouseenter':\n",
" widget_label.value = \"Almost there...\" # this is is not visible because mousemove overwrites the msg\n",
" \n",
"interaction.on_msg(on_mouse_msg)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "federal-domestic",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.6"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
"state": {},
"version_major": 2,
"version_minor": 0
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}
22 changes: 20 additions & 2 deletions js/lib/MouseInteraction.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const Interaction_1 = require("bqplot");
const base_1 = require("@jupyter-widgets/base");
const d3 = require("d3");
const d3_drag_1 = require("d3-drag");
const _ = require("lodash");
const d3_selection_1 = require("d3-selection");
const d3GetEvent = function () { return require("d3-selection").event; }.bind(this);

Expand All @@ -21,6 +22,8 @@ class MouseInteractionModel extends base_1.WidgetModel {
_view_module_version: version,
scale_x: null,
scale_y: null,
scale_y: null,
move_throttle: 50,
cursor: 'auto' });
}
}
Expand All @@ -41,6 +44,12 @@ class MouseInteraction extends Interaction_1.Interaction {
};
this.listenTo(this.model, "change:cursor", updateCursor);
updateCursor();
const updateThrottle = () => {
this._emitThrottled = _.throttle(this._emit, this.model.get('move_throttle'));
}
updateThrottle();
this.listenTo(this.model, 'change:move_throttle', updateThrottle);

eventElement.call(d3_drag_1.drag().on("start", () => {
const e = d3GetEvent();
this._emit('dragstart', { x: e.x, y: e.y });
Expand All @@ -52,14 +61,24 @@ class MouseInteraction extends Interaction_1.Interaction {
this._emit('dragend', { x: e.x, y: e.y });
}));
// and click events
['click', 'dblclick'].forEach(eventName => {
['click', 'dblclick', 'mouseenter', 'mouseleave'].forEach(eventName => {
eventElement.on(eventName, () => {
this._emitThrottled.flush(); // we don't want mousemove events to come after enter/leave
const e = d3GetEvent();
// to be consistent with drag events, we need to user clientPoint
const [x, y] = d3_selection_1.clientPoint(eventElement.node(), e);
this._emit(eventName, { x, y });
});
});
// throttled events
['mousemove'].forEach(eventName => {
eventElement.on(eventName, () => {
const e = d3GetEvent();
// to be consistent with drag events, we need to user clientPoint
const [x, y] = d3_selection_1.clientPoint(eventElement.node(), e);
this._emitThrottled(eventName, { x, y });
});
});
}
updateScaleRanges() {
this.x_scale.set_range(this.parent.padded_range("x", this.x_scale.model));
Expand All @@ -75,4 +94,3 @@ class MouseInteraction extends Interaction_1.Interaction {
}
}
exports.MouseInteraction = MouseInteraction;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTW91c2VJbnRlcmFjdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9Nb3VzZUludGVyYWN0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsK0NBQTRDO0FBQzVDLGdEQUFtRTtBQUNuRSxxQ0FBK0I7QUFDL0IsK0NBQTJDO0FBQzNDLE1BQU0sVUFBVSxHQUFHLGNBQVcsT0FBTyxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUMsS0FBSyxDQUFBLENBQUEsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztBQUMvRSx1Q0FBeUM7QUFFekMsTUFBYSxxQkFBc0IsU0FBUSxrQkFBVztJQUNsRCxRQUFRO1FBQ0oseUJBQVcsa0JBQVcsQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFLElBQ3ZDLFdBQVcsRUFBRSx1QkFBdUIsRUFDcEMsVUFBVSxFQUFFLGtCQUFrQixFQUM5QixhQUFhLEVBQUUsUUFBUSxFQUN2QixZQUFZLEVBQUUsUUFBUSxFQUN0QixxQkFBcUIsRUFBRSxzQkFBWSxFQUNuQyxvQkFBb0IsRUFBRSxzQkFBWSxFQUNsQyxPQUFPLEVBQUUsSUFBSSxFQUNiLE9BQU8sRUFBRSxJQUFJLEVBQ2IsTUFBTSxFQUFFLE1BQU0sSUFDaEI7SUFDTixDQUFDOztBQUVNLGlDQUFXLHFCQUNYLGtCQUFXLENBQUMsV0FBVyxJQUMxQixPQUFPLEVBQUUsRUFBRSxXQUFXLEVBQUUsb0JBQWEsRUFBRSxFQUN2QyxPQUFPLEVBQUUsRUFBRSxXQUFXLEVBQUUsb0JBQWEsRUFBRSxJQUMxQztBQW5CTCxzREFvQkM7QUFFRCxNQUFhLGdCQUFpQixTQUFRLHlCQUFXO0lBRzdDLEtBQUssQ0FBQyxNQUFNO1FBQ1IsMEJBQTBCO1FBQzFCLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUM7UUFDL0IsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDO1FBQ2YsSUFBSSxDQUFDLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxp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

0 comments on commit 3227348

Please sign in to comment.