mirror of
https://github.com/wassname/baukit.git
synced 2026-06-27 16:14:33 +08:00
Add ability to decode x,y location of PlotWidget clicks.
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
from .labwidget import Model, Widget, Trigger, Property, Event
|
||||
from .labwidget import Button, Label, Textbox, Numberbox, Range, ColorPicker
|
||||
from .labwidget import Choice, Menu, Datalist, Div, ClickDiv, Image
|
||||
from .labwidget import Choice, Menu, Datalist, Div, ClickDiv, Img
|
||||
from .labwidget import Textarea
|
||||
from .paintwidget import PaintWidget
|
||||
from .plotwidget import PlotWidget
|
||||
|
||||
+24
-4
@@ -481,6 +481,9 @@ class Event(object):
|
||||
self.name = name
|
||||
self.target = target
|
||||
|
||||
def __repr__(self):
|
||||
return f'Event({self.value}, {self.name})'
|
||||
|
||||
|
||||
entered_handler_stack = []
|
||||
|
||||
@@ -986,7 +989,7 @@ class ClickDiv(Div):
|
||||
''')
|
||||
|
||||
|
||||
class Image(Widget):
|
||||
class Img(Widget):
|
||||
"""
|
||||
Just a IMG element. Use the src property to change its contents by url,
|
||||
or use the clear() and render(imgdata) methods to convert PIL or
|
||||
@@ -995,7 +998,11 @@ class Image(Widget):
|
||||
|
||||
def __init__(self, src='', style=None, **kwargs):
|
||||
super().__init__(style=defaulted(style, margin=0), **kwargs)
|
||||
self.src = Property(src)
|
||||
if (hasattr(src, 'save') or hasattr(src, 'savefig')):
|
||||
self.src = Property('')
|
||||
self.render(src)
|
||||
else:
|
||||
self.src = Property(src)
|
||||
self.click = Trigger()
|
||||
|
||||
def clear(self):
|
||||
@@ -1018,10 +1025,21 @@ class Image(Widget):
|
||||
buf.close()
|
||||
|
||||
def widget_js(self):
|
||||
# The click event has four properties that indicate the pixel
|
||||
# location within the image that was clicked: imgx, imgy measure
|
||||
# from the top-left. imgyb measures from the bottom, and imgxr
|
||||
# measures from the right.
|
||||
return minify('''
|
||||
model.on('src', (ev) => { element.src = ev.value; });
|
||||
element.addEventListener('click', (ev) => {
|
||||
model.trigger('click');
|
||||
var b=element.getBoundingClientRect();
|
||||
console.log(ev.pageX, ev.pageY)
|
||||
model.trigger('click', {
|
||||
x: (ev.pageX-b.left)*element.naturalWidth/element.clientWidth,
|
||||
y: (ev.pageY-b.top)*element.naturalHeight/element.clientHeight,
|
||||
width: element.naturalWidth,
|
||||
height: element.naturalHeight
|
||||
});
|
||||
});
|
||||
''')
|
||||
|
||||
@@ -1107,9 +1125,11 @@ if WIDGET_ENV is None:
|
||||
if WIDGET_ENV is None:
|
||||
try:
|
||||
from ipykernel.comm import Comm as jupyter_comm
|
||||
COMM_MANAGER = get_ipython().kernel.comm_manager
|
||||
from IPython import get_ipython as ipython_get_ipython
|
||||
COMM_MANAGER = ipython_get_ipython().kernel.comm_manager
|
||||
WIDGET_ENV = 'jupyter'
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
def no_env_warning():
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from .labwidget import Widget, Property, Image, minify
|
||||
from .labwidget import Widget, Property, Img, minify
|
||||
from . import show
|
||||
|
||||
|
||||
class PaintWidget(Image):
|
||||
class PaintWidget(Img):
|
||||
def __init__(self,
|
||||
width=256, height=256,
|
||||
src='', mask='', brushsize=10.0, oneshot=False, disabled=False,
|
||||
|
||||
+46
-5
@@ -1,7 +1,7 @@
|
||||
from .labwidget import Image, Property
|
||||
from .labwidget import Img, Property
|
||||
import inspect
|
||||
|
||||
class PlotWidget(Image):
|
||||
class PlotWidget(Img):
|
||||
"""
|
||||
A widget to create interactive matplotlib plots by defining a simple function.
|
||||
Example of usage:
|
||||
@@ -27,7 +27,7 @@ class PlotWidget(Image):
|
||||
import matplotlib, matplotlib.pyplot
|
||||
super().__init__()
|
||||
init_args = dict(kwargs)
|
||||
render_args = dict(format='svg')
|
||||
self.render_args = dict()
|
||||
if rc is None:
|
||||
rc = {}
|
||||
|
||||
@@ -47,7 +47,7 @@ class PlotWidget(Image):
|
||||
for name in ['format', 'metadata', 'bbox_inches', 'pad_inches',
|
||||
'facecolor', 'edgecolor', 'backend']:
|
||||
if name in init_args:
|
||||
render_args[name] = init_args.pop(name)
|
||||
self.render_args[name] = init_args.pop(name)
|
||||
|
||||
for default_arg, default_value in [('figsize', (5, 3.5))]:
|
||||
if default_arg not in init_args:
|
||||
@@ -68,6 +68,47 @@ class PlotWidget(Image):
|
||||
for name in all_names:
|
||||
args.append(getattr(self, name))
|
||||
redraw_rule(*args)
|
||||
self.render(self.fig, **render_args)
|
||||
self.render(self.fig, **self.render_args)
|
||||
self.on(' '.join(all_names), invoke_redraw)
|
||||
invoke_redraw()
|
||||
|
||||
def event_location(self, event):
|
||||
'''
|
||||
Transform a click event from pixel coordinates to plot data coordinates.
|
||||
'''
|
||||
# Image natural size and image-relative pixel location.
|
||||
w = event.value['width']
|
||||
h = event.value['height']
|
||||
px = event.value['x']
|
||||
py = event.value['y']
|
||||
# To convert from image to display coords, we need the bbox.
|
||||
# https://stackoverflow.com/questions/28692981
|
||||
if self.render_args.get('bbox_inches', None) == 'tight':
|
||||
bbox = self.fig.get_tightbbox(self.fig.canvas.get_renderer()).padded(0)
|
||||
bbox.set_points(bbox.get_points() * self.fig.dpi)
|
||||
else:
|
||||
bbox = self.fig.bbox
|
||||
# Matplotlib display-coordinate location
|
||||
dx = (bbox.x1 - bbox.x0) * px / w + bbox.x0
|
||||
dy = (bbox.y1 - bbox.y0) * (h - py) / h + bbox.y0
|
||||
# Identify the first axis that contains the point
|
||||
for inside in self.fig.axes:
|
||||
if inside.get_window_extent().contains(dx, dy):
|
||||
break
|
||||
else:
|
||||
inside = None
|
||||
ax = self.fig.axes[0] if (len(self.fig.axes) and inside is None) else inside
|
||||
# Axis-data-relative coordinate location.
|
||||
# https://stackoverflow.com/questions/59794014
|
||||
if ax is not None:
|
||||
x, y = ax.transData.inverted().transform((dx, dy))
|
||||
else:
|
||||
x, y = None, None
|
||||
|
||||
class PlotLocation():
|
||||
def __init__(self, x, y, axis, inside):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.axis = axis
|
||||
self.inside = inside
|
||||
return PlotLocation(x, y, ax, inside is not None)
|
||||
|
||||
@@ -476,20 +476,47 @@
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8ed28ab5",
|
||||
"metadata": {},
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"\n",
|
||||
"freq_ra = Range(min=0.1, max=5.0, step=0.01, value=1.0)\n",
|
||||
"freq_nb = Numberbox(freq_ra.prop('value'))\n",
|
||||
"amp_ra = Range(min=0.1, max=5.0, step=0.01, value=1.0)\n",
|
||||
"amp_nb = Numberbox(amp_ra.prop('value'))\n",
|
||||
"pw = PlotWidget(myplot, frequency=freq_ra.prop('value'), amplitude=amp_ra.prop('value'))\n",
|
||||
"pw = PlotWidget(myplot, frequency=freq_ra.prop('value'), amplitude=amp_ra.prop('value'), format='svg')\n",
|
||||
"show(show.TIGHT, [[pw],\n",
|
||||
" ['frequency', show.style(flex=10), freq_ra, freq_nb],\n",
|
||||
" ['amplitude', show.style(flex=10), amp_ra, amp_nb]])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d58975ba",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Handling PlotWidget clicks\n",
|
||||
"\n",
|
||||
"When an image or PlotWidget is clicked, you can get the coordinates by listening to the click event."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ba3cbe25",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from baukit import Textbox\n",
|
||||
"coord_b = Textbox()\n",
|
||||
"def handle_click(e):\n",
|
||||
" loc = pw.event_location(e)\n",
|
||||
" coord_b.value = f'x,y=({loc.x:.2f}, {loc.y:.2f}) inside={loc.inside}'\n",
|
||||
"pw.on('click', handle_click)\n",
|
||||
"show(show.TIGHT, [['Click the previous plot to see coordinates here:', coord_b]])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e60ab5fa",
|
||||
|
||||
Reference in New Issue
Block a user