python tkinter-带有跨度小部件的交互式绘图和列表框

时间:2018-12-05 09:05:20

标签: python matplotlib tkinter

我尝试执行的步骤如下:
1.插入值对列表[xmin,xmax]
2.创建跨度对象的初始列表
3.绘制跨度并将标签写到列表框中
4.添加跨度到绘图(通过onlick事件)并更新列表框
5.删除并突出显示跨度

这是示例代码:

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib.widgets import SpanSelector
import tkinter as tk
import numpy as np

class App:
     def __init__(self, master, l_val_pair):
         # Create a container
         frame = tk.Frame(master)

         # Create fields
         self.button_left = tk.Button(frame,text="Export")
         self.button_left.pack(side="left")
         self.button_right = tk.Button(frame,text="Delete", command = 
                                       lambda: self.delete(self.l_spans))
         self.button_right.pack(side="left")

         self.listbox = tk.Listbox(master)
         self.listbox.pack(side='bottom', fill=tk.X) 

         #define figure
         self.fig = Figure()
         self.ax = self.fig.add_subplot(111)

         #sample data for a line
         self.x = np.arange(0.0, 5.0, 0.01)
         self.y = np.sin(2*np.pi*self.x) + 0.5*np.random.randn(len(self.x))

         #plot line and list of spans
         self.ax.plot(self.x,self.y)

         self.l_spans = [self.ax.axes.axvspan(
                 val_pair[0],val_pair[1], facecolor='red', alpha=0.3, label=val_pair) 
                    for val_pair in l_val_pair]

         #create a listbox      
         [self.listbox.insert(tk.END, item.get_label()) for item in self.l_spans]
         self.listbox.bind('<<ListboxSelect>>', self.highlight_span)         

         #plot figure
         self.canvas = FigureCanvasTkAgg(self.fig,master=master)
         self.canvas.show()
         self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
         frame.pack()

         #use span selector
         self.span = SpanSelector(self.ax, self.sel_span, 'horizontal', useblit=True,
                             rectprops=dict(alpha=0.5, facecolor='red'), span_stays=False)

         #connect
         self.canvas.mpl_connect('key_press_event', self.span)


     def sel_span(self,xmin, xmax):
         indmin, indmax = np.searchsorted(self.x, (xmin, xmax))
         indmax = min(len(self.x) - 1, indmax)

         span = self.ax.axes.axvspan(xmin=self.x[indmin], xmax=self.x[indmax], 
                              alpha = 0.5, facecolor='red')

         #add actual span to list of spans
         self.l_spans.append(span)

         #update view (spans and listbox) with central list of spans          
         self.update_view()


     def update_view(self):
         #clear visualisation
         self.listbox.delete(0,tk.END)
         [span.remove() for span in self.l_spans]


         #fill with new data
         [self.listbox.insert(tk.END, span.get_label()) for span in self.l_spans]
         [span.draw(self.ax) for span in self.l_spans] #HERE IS THE PROBLEM!!!

         #update view
         #...


     def highlight_span():
         pass

     def delete(self,item):
         pass

list_vals= [[1,2],[2.5,4]]
root = tk.Tk()
app = App(root, list_vals)
root.mainloop()

函数update_view中的问题:
我需要在span.draw()中插入什么渲染器?

我不确定我的方法是否合适,因为我没有使用tkinter的经验。你觉得呢?

1 个答案:

答案 0 :(得分:1)

我已经稍微简化了您的代码,删除了(我认为)非严格必要的update_view方法。我在sel_span方法中添加了所有相关内容,每次您在图形中选择一个区域时都会调用该方法。

我还创建了另一种方法remove_spans来删除所选区域并清除下面的列表框。我还绑定了Delete按钮,因此,如果按下该按钮,则选择内容和文本框将被清除。

我还删除了列表框与ListboxSelect事件的绑定,因为列表框将在调用insert方法时更新。

最后:我已经将您的xminxmax值添加到了列表框中,而不是span.get_label()项中。这是示例代码:

class App:
    def __init__(self, master, l_val_pair):
         # Create a container
        frame = tk.Frame(master)

        # Create fields
        self.button_left = tk.Button(frame,text="Export")
        self.button_left.pack(side="left")
        self.button_right = tk.Button(frame,text="Delete", command = self.remove_spans)
        self.button_right.pack(side="left")

        self.listbox = tk.Listbox(master)
        self.listbox.pack(side='bottom', fill=tk.X) 

        #define figure
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)

        #sample data for a line
        self.x = np.arange(0.0, 5.0, 0.01)
        self.y = np.sin(2*np.pi*self.x) + 0.5*np.random.randn(len(self.x))

        #plot line and list of spans
        self.ax.plot(self.x,self.y)

        self.l_spans = [self.ax.axes.axvspan(val_pair[0],val_pair[1], facecolor='red', alpha=0.3, label=val_pair) 
                    for val_pair in l_val_pair]

        #create a listbox      
        [self.listbox.insert(tk.END, item.get_label()) for item in self.l_spans]          

        #plot figure
        self.canvas = FigureCanvasTkAgg(self.fig,master=master)
        self.canvas.show()
        self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
        frame.pack()

        #use span selector
        self.span = SpanSelector(self.ax, self.sel_span, 'horizontal', useblit=True,
                             rectprops=dict(alpha=0.5, facecolor='red'), span_stays=False)

        #connect
        self.canvas.mpl_connect('key_press_event', self.span)


    def sel_span(self, xmin, xmax):
        #clear visualisation
        self.remove_spans()
        indmin, indmax = np.searchsorted(self.x, (xmin, xmax))
        indmax = min(len(self.x) - 1, indmax)
        span = self.ax.axes.axvspan(xmin=self.x[indmin], xmax=self.x[indmax], 
                              alpha = 0.5, facecolor='red')

        #add actual span to list of spans
        self.l_spans.append(span)

        #for span in self.l_spans:
        self.listbox.insert(tk.END, xmin)
        self.listbox.insert(tk.END, xmax)


    def remove_spans(self):
        self.listbox.delete(0,tk.END)

        for span in self.l_spans:
           try:
               span.remove()
           except:
               pass
        self.canvas.draw_idle()


list_vals= [[1,2],[2.5,4]]
root = tk.Tk()
app = App(root, list_vals)
root.mainloop()

编辑: 注释进一步说明了用例,因此这里是代码的修改版本。我重新引入了对<<ListboxSelect>>事件的绑定,并编写了highlight_span方法:

  • 检查已选择列表中的哪个范围
  • 以绿色突出显示图中的数字(只是为了使其更加可见)

我还更改了代码,以使旧选择不会从列​​表框中删除:

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib.widgets import SpanSelector
import tkinter as tk
import numpy as np
import ast

class App:
    def __init__(self, master, l_val_pair):
         # Create a container
        frame = tk.Frame(master)

        # Create fields
        self.button_left = tk.Button(frame,text="Export")
        self.button_left.pack(side="left")
        self.button_right = tk.Button(frame,text="Delete", command = self.remove_spans)
        self.button_right.pack(side="left")

        self.listbox = tk.Listbox(master)
        self.listbox.pack(side='bottom', fill=tk.X) 
        self.listbox.bind('<<ListboxSelect>>', self.highlight_span)   

        #define figure
        self.fig = Figure()
        self.ax = self.fig.add_subplot(111)

        #sample data for a line
        self.x = np.arange(0.0, 5.0, 0.01)
        self.y = np.sin(2*np.pi*self.x) + 0.5*np.random.randn(len(self.x))

        #plot line and list of spans
        self.ax.plot(self.x,self.y)

        self.l_spans = [self.ax.axes.axvspan(val_pair[0],val_pair[1], facecolor='red', alpha=0.3, label=val_pair) 
                    for val_pair in l_val_pair]

        #create a listbox      
        [self.listbox.insert(tk.END, item.get_label()) for item in self.l_spans]

        #plot figure
        self.canvas = FigureCanvasTkAgg(self.fig,master=master)
        self.canvas.show()
        self.canvas.get_tk_widget().pack(side='top', fill='both', expand=1)
        frame.pack()

        #use span selector
        self.span = SpanSelector(self.ax, self.sel_span, 'horizontal', useblit=True,
                             rectprops=dict(alpha=0.5, facecolor='red'), span_stays=False)

        #connect
        self.canvas.mpl_connect('key_press_event', self.span)


    def sel_span(self, xmin, xmax):
        #clear visualisation        
        for span in self.l_spans:
           try:
               span.remove()
           except:
               pass
        self.canvas.draw_idle()        

        indmin, indmax = np.searchsorted(self.x, (xmin, xmax))
        indmax = min(len(self.x) - 1, indmax)
        span = self.ax.axes.axvspan(xmin=self.x[indmin], 
                                    xmax=self.x[indmax], 
                                    alpha = 0.5,  
                                    facecolor='red',
                                    label = [xmin, xmax])

        #add actual span to list of spans
        self.l_spans.append(span)

        self.listbox.insert(tk.END, span.get_label())        


    def remove_spans(self):
        self.listbox.delete(0,tk.END)

        for span in self.l_spans:
           try:
               span.remove()
           except:
               pass
        self.canvas.draw_idle()

    def highlight_span(self, evt):        
        w = evt.widget
        index = w.curselection()[0]
        value = w.get(index)
        value = list(map(float, ast.literal_eval(value)))

        xmin = value[0]
        xmax = value[1]

        indmin, indmax = np.searchsorted(self.x, (xmin, xmax))
        indmax = min(len(self.x) - 1, indmax)
        span = self.ax.axes.axvspan(xmin=self.x[indmin], 
                                    xmax=self.x[indmax], 
                                    alpha = 0.5,  
                                    facecolor='green',
                                    label = [xmin, xmax])
        self.l_spans.append(span)
        self.canvas.draw_idle()



list_vals= [[1,2],[2.5,4]]
root = tk.Tk()
app = App(root, list_vals)
root.mainloop()

我希望这会有所帮助。