mirror of
https://github.com/wassname/baukit.git
synced 2026-06-27 17:47:31 +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 Model, Widget, Trigger, Property, Event
|
||||||
from .labwidget import Button, Label, Textbox, Numberbox, Range, ColorPicker
|
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 .labwidget import Textarea
|
||||||
from .paintwidget import PaintWidget
|
from .paintwidget import PaintWidget
|
||||||
from .plotwidget import PlotWidget
|
from .plotwidget import PlotWidget
|
||||||
|
|||||||
+24
-4
@@ -481,6 +481,9 @@ class Event(object):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.target = target
|
self.target = target
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'Event({self.value}, {self.name})'
|
||||||
|
|
||||||
|
|
||||||
entered_handler_stack = []
|
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,
|
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
|
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):
|
def __init__(self, src='', style=None, **kwargs):
|
||||||
super().__init__(style=defaulted(style, margin=0), **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()
|
self.click = Trigger()
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
@@ -1018,10 +1025,21 @@ class Image(Widget):
|
|||||||
buf.close()
|
buf.close()
|
||||||
|
|
||||||
def widget_js(self):
|
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('''
|
return minify('''
|
||||||
model.on('src', (ev) => { element.src = ev.value; });
|
model.on('src', (ev) => { element.src = ev.value; });
|
||||||
element.addEventListener('click', (ev) => {
|
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:
|
if WIDGET_ENV is None:
|
||||||
try:
|
try:
|
||||||
from ipykernel.comm import Comm as jupyter_comm
|
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'
|
WIDGET_ENV = 'jupyter'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def no_env_warning():
|
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
|
from . import show
|
||||||
|
|
||||||
|
|
||||||
class PaintWidget(Image):
|
class PaintWidget(Img):
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
width=256, height=256,
|
width=256, height=256,
|
||||||
src='', mask='', brushsize=10.0, oneshot=False, disabled=False,
|
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
|
import inspect
|
||||||
|
|
||||||
class PlotWidget(Image):
|
class PlotWidget(Img):
|
||||||
"""
|
"""
|
||||||
A widget to create interactive matplotlib plots by defining a simple function.
|
A widget to create interactive matplotlib plots by defining a simple function.
|
||||||
Example of usage:
|
Example of usage:
|
||||||
@@ -27,7 +27,7 @@ class PlotWidget(Image):
|
|||||||
import matplotlib, matplotlib.pyplot
|
import matplotlib, matplotlib.pyplot
|
||||||
super().__init__()
|
super().__init__()
|
||||||
init_args = dict(kwargs)
|
init_args = dict(kwargs)
|
||||||
render_args = dict(format='svg')
|
self.render_args = dict()
|
||||||
if rc is None:
|
if rc is None:
|
||||||
rc = {}
|
rc = {}
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class PlotWidget(Image):
|
|||||||
for name in ['format', 'metadata', 'bbox_inches', 'pad_inches',
|
for name in ['format', 'metadata', 'bbox_inches', 'pad_inches',
|
||||||
'facecolor', 'edgecolor', 'backend']:
|
'facecolor', 'edgecolor', 'backend']:
|
||||||
if name in init_args:
|
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))]:
|
for default_arg, default_value in [('figsize', (5, 3.5))]:
|
||||||
if default_arg not in init_args:
|
if default_arg not in init_args:
|
||||||
@@ -68,6 +68,47 @@ class PlotWidget(Image):
|
|||||||
for name in all_names:
|
for name in all_names:
|
||||||
args.append(getattr(self, name))
|
args.append(getattr(self, name))
|
||||||
redraw_rule(*args)
|
redraw_rule(*args)
|
||||||
self.render(self.fig, **render_args)
|
self.render(self.fig, **self.render_args)
|
||||||
self.on(' '.join(all_names), invoke_redraw)
|
self.on(' '.join(all_names), invoke_redraw)
|
||||||
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",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"id": "8ed28ab5",
|
"id": "8ed28ab5",
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
|
"scrolled": true
|
||||||
|
},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"\n",
|
|
||||||
"freq_ra = Range(min=0.1, max=5.0, step=0.01, value=1.0)\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",
|
"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_ra = Range(min=0.1, max=5.0, step=0.01, value=1.0)\n",
|
||||||
"amp_nb = Numberbox(amp_ra.prop('value'))\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",
|
"show(show.TIGHT, [[pw],\n",
|
||||||
" ['frequency', show.style(flex=10), freq_ra, freq_nb],\n",
|
" ['frequency', show.style(flex=10), freq_ra, freq_nb],\n",
|
||||||
" ['amplitude', show.style(flex=10), amp_ra, amp_nb]])"
|
" ['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",
|
"cell_type": "markdown",
|
||||||
"id": "e60ab5fa",
|
"id": "e60ab5fa",
|
||||||
|
|||||||
Reference in New Issue
Block a user