高级互动情节:下拉菜单、可拖动标记和按钮

问题描述 投票:0回答:0

利用这个 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)

这段代码的初始输出还不错。但是:

  1. 点不可拖动
  2. 按钮没用。他们不工作

此外,每当我尝试通过滑块更改值时,我都会收到此错误:

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

我对这样的话题很陌生,所以任何帮助将不胜感激

python matplotlib plot drag-and-drop interactive
© www.soinside.com 2019 - 2024. All rights reserved.