利用这个 topic,并使用 ipywidgets 的文档,我想创建一个带有下拉菜单(主要用于过滤数据和绘制不同的线)和可拖动标记的交互式绘图。此外,我想添加一个“导出”按钮来导出用户拖动操作创建的曲线的新 y 值。该工具的主要目标是用户可以为下拉菜单中的每个对象定义自定义曲线,并导出将用作另一个工具输入的新值。
这里是代码草图(内置于 Jupyter):
import matplotlib.animation as animation
from matplotlib.widgets import Slider, Button
import matplotlib as mpl
from matplotlib import pyplot as plt
from ipywidgets import interactive
%matplotlib widget
def brand_selection(brand):
df_by_brand = df.query("Brand == @brand and Time == -1")
df_by_brand[['Label','Qty']]
x = df_by_brand['Label'].tolist()
N = len(x)
y = df_by_brand['Qty'].tolist()
yvals = y
fig,axes = plt.subplots(1,1,figsize=(9.0,8.0),sharex=True)
#mpl.rcParams['figure.subplot.right'] = 0.8
ax1 = axes
pind = None #active point
epsilon = 5 #max pixel distance
def update(val):
global yvals
# update curve
for i in np.arange(N):
yvals[i] = sliders[i].val
l.set_ydata(yvals)
# redraw canvas while idle
fig.canvas.draw_idle()
def reset(event):
global yvals
#reset the values
yvals = y
for i in np.arange(N):
sliders[i].reset()
l.set_ydata(yvals)
# redraw canvas while idle
fig.canvas.draw_idle()
def export(event):
global yvals
np.savetxt(f'{brand}.csv', yvals)
def button_press_callback(event):
'whenever a mouse button is pressed'
global pind
if event.inaxes is None:
return
if event.button != 1:
return
#print(pind)
pind = get_ind_under_point(event)
def button_release_callback(event):
'whenever a mouse button is released'
global pind
if event.button != 1:
return
pind = None
def get_ind_under_point(event):
'get the index of the vertex under point if within epsilon tolerance'
# display coords
#print('display x is: {0}; display y is: {1}'.format(event.x,event.y))
t = ax1.transData.inverted()
tinv = ax1.transData
xy = t.transform([event.x,event.y])
#print('data x is: {0}; data y is: {1}'.format(xy[0],xy[1]))
xr = np.reshape(x,(np.shape(x)[0],1))
yr = np.reshape(yvals,(np.shape(yvals)[0],1))
xy_vals = np.append(xr,yr,1)
xyt = tinv.transform(xy_vals)
xt, yt = xyt[:, 0], xyt[:, 1]
d = np.hypot(xt - event.x, yt - event.y)
indseq, = np.nonzero(d == d.min())
ind = indseq[0]
#print(d[ind])
if d[ind] >= epsilon:
ind = None
#print(ind)
return ind
def motion_notify_callback(event):
'on mouse movement'
global yvals
if pind is None:
return
if event.inaxes is None:
return
if event.button != 1:
return
#update yvals
#print('motion x: {0}; y: {1}'.format(event.xdata,event.ydata))
yvals[pind] = event.ydata
# update curve via sliders and draw
sliders[pind].set_val(yvals[pind])
fig.canvas.draw_idle()
#ax1.plot (x, y_original, 'k--', label='original', alpha=0.2)
l, = ax1.plot (x,yvals,color='k',linestyle='-',marker='o',markersize=8)
ax1.set_yscale('linear')
ax1.set_xlim(0, N)
ax1.set_ylim(0,100)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.grid(True)
ax1.yaxis.grid(True,which='minor',linestyle='--')
ax1.legend(loc=2,prop={'size':22})
sliders = []
for i in np.arange(N):
axamp = plt.axes([0.84, 0.8-(i*0.05), 0.12, 0.02])
# Slider
s = Slider(axamp, 'p{0}'.format(i), 0, 100, valinit=yvals[i])
sliders.append(s)
for i in np.arange(N):
#samp.on_changed(update_slider)
sliders[i].on_changed(update)
axres = plt.axes([0.84, 0.8-((N)*0.05), 0.12, 0.02])
bres = Button(axres, 'Reset')
bres.on_clicked(reset)
axres = plt.axes([0.84, 0.7-((N)*0.05), 0.12, 0.02])
bres = Button(axres, 'Export')
bres.on_clicked(export)
fig.canvas.mpl_connect('button_press_event', button_press_callback)
fig.canvas.mpl_connect('button_release_event', button_release_callback)
fig.canvas.mpl_connect('motion_notify_event', motion_notify_callback)
plt.show()
brand_list = df.Brand.unique()
interactive_plot = interactive(brand_selection, brand =brand_list)
output = interactive_plot.children[-1]
output.layout.height = '950px'
interactive_plot
此外,这是我用于测试的
df
数据框:
import pandas as pd
data = {'Time': [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2],
'Brand': ['CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'DG', 'DG', 'DG', 'DG', 'DG', 'DG', 'DG', 'DG', 'DG', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'CH', 'DG', 'DG', 'DG', 'DG', 'DG', 'DG', 'DG', 'DG', 'DG'],
'Label': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6],
'Qty': [50, 86, 90, 78, 2, 66, 17, 79, 0, 40, 9, 84, 68, 30, 39, 31, 33, 53, 58, 22, 88, 55, 46, 49, 21, 69, 86, 66, 91, 44, 72, 63, 1, 94, 79, 95, 32, 30, 91, 16, 57, 44]}
df = pd.DataFrame(data)
这段代码的初始输出还不错。但是:
此外,每当我尝试通过滑块更改值时,我都会收到此错误:
NameError Traceback (most recent call last)
File c:\Users\mottad\AppData\Local\Programs\Python\Python39\lib\site-packages\ipympl\backend_nbagg.py:279, in Canvas._handle_message(self, object, content, buffers)
276 self.manager.handle_json(content)
278 else:
--> 279 self.manager.handle_json(content)
File ~\AppData\Roaming\Python\Python39\site-packages\matplotlib\backends\backend_webagg_core.py:462, in FigureManagerWebAgg.handle_json(self, content)
461 def handle_json(self, content):
--> 462 self.canvas.handle_event(content)
File ~\AppData\Roaming\Python\Python39\site-packages\matplotlib\backends\backend_webagg_core.py:266, in FigureCanvasWebAggCore.handle_event(self, event)
263 e_type = event['type']
264 handler = getattr(self, 'handle_{0}'.format(e_type),
265 self.handle_unknown_event)
--> 266 return handler(event)
File ~\AppData\Roaming\Python\Python39\site-packages\matplotlib\backends\backend_webagg_core.py:296, in FigureCanvasWebAggCore._handle_mouse(self, event)
294 guiEvent = event.get('guiEvent')
295 if e_type in ['button_press', 'button_release']:
--> 296 MouseEvent(e_type + '_event', self, x, y, button,
297 modifiers=modifiers, guiEvent=guiEvent)._process()
298 elif e_type == 'dblclick':
299 MouseEvent('button_press_event', self, x, y, button, dblclick=True,
300 modifiers=modifiers, guiEvent=guiEvent)._process()
...
---> 24 yvals[i] = sliders[i].val
25 l.set_ydata(yvals)
26 # redraw canvas while idle
NameError: name 'yvals' is not defined
我不明白为什么它告诉我未定义yvals。
问题:遇到过类似问题的人可以帮助我修复和改进代码吗? 我对新的解决方案持开放态度,我可以利用其他软件包,例如 Dash