matplotlib:从绘图中删除补丁

时间:2017-01-13 08:02:46

标签: python-2.7 matplotlib tkinter

我正在使用matplotlibinteractively绘制一些patchespoints

我通过队列从单独的进程接收数据并将它们发送到我的plot-process。代码的这一部分工作正常,点在图表上显示,并按预期在图中不断更新。

根据用户的要求,我想删除绘图中的所有旧补丁并替换为新补丁。

我认为这样就足够了:

# remove the old patch
patch.remove() 
# I also tried ax.cla() without any success
# create a new patch
monitor_box = path.Path([(305, 500), (11, -213), (300, -220), (500, 1734)])
patch = patches.PathPatch(monitor_box, facecolor='black', lw=1)
# add the patch to the axis
ax.add_patch(patch)

然后在下一次迭代期间,应使用新补丁更新绘图:

canvas.draw()

但是当我使用上面的代码时,补丁仍然保留在窗口中,没有任何变化。 (我仍然得到情节中的分数,以便至少仍在不断更新)

下面我提供了该问题的最小工作示例。运行代码时,您可以看到绘制了不同的点,但永远不会删除补丁。

import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
import multiprocessing
from Tkinter import *
import matplotlib.path as path
import matplotlib.patches as patches
import sys, thread, time

from random import randint

#Create a window
window=Tk()

sendProximityInfo = True
latest_published_msg = ""

def erasePatchesAndCreateNew_A():
    print "erasePatchesAndCreateNew_A"
    global line, ax, canvas
    global monitor_box
    global patch
    patch.remove()
    ax.cla()            
    monitor_box = path.Path([(35, 1677), (11, -213), (652, -220), (500, 1734)])
    patch = patches.PathPatch(monitor_box, facecolor='black', lw=1)

    ax.add_patch(patch)

def erasePatchesAndCreateNew_B():
    print "erasePatchesAndCreateNew_B"
    global line, ax, canvas
    global monitor_box
    global patch
    patch.remove()
    ax.cla()
    monitor_box = path.Path([(35, 500), (11, -213), (300, -220), (500, 1734)])
    patch = patches.PathPatch(monitor_box, facecolor='red', lw=1)

    ax.add_patch(patch)

monitor_box = path.Path([(35, 1677), (111, -213), (62, -220), (800, 1734)])
fig = matplotlib.figure.Figure()
ax  = fig.add_subplot(1,1,1)
ax.set_xlim(-1500,2000)
ax.set_ylim(-1500,2000)
patch = patches.PathPatch(monitor_box, facecolor='black', lw=1)
ax.add_patch(patch)

def main():
    erasePatchesAndCreateNew_B()

    #Create a queue to share data between process
    q = multiprocessing.Queue()
    #Create and start the simulation process
    simulate = multiprocessing.Process(None, simulation,args=(q,))
    simulate.start()   
    #Create the base plot
    plot()
    #Call a function to update the plot when there is new data
    updateplot(q)

    window.mainloop()
    print 'Done'
    simulate.join() # wait for the other process to finish as well

def plot():    #Function to create the base plot, make sure to make global the lines, axes, canvas and any part that you would want to update later
    global line, ax, canvas
    global monitor_box
    global patch

    fig = matplotlib.figure.Figure()
    ax  = fig.add_subplot(1,1,1)
    ax.set_xlim(-1500,2000)
    ax.set_ylim(-1500,2000)

    patch = patches.PathPatch(monitor_box, facecolor='black', lw=1)
    ax.add_patch(patch)

    ax.invert_yaxis()
    canvas = FigureCanvasTkAgg(fig, master=window)
    canvas.show()
    canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
    canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1)
    line, = ax.plot([], [], 'ro')

def updateplot(q):
    try:       #Try to check if there is data in the queue
        result = q.get_nowait()

        if result != 'Q':
            x, y = result
            line.set_data(x, y)
            ax.draw_artist(line)
            canvas.draw()
            window.after(1,updateplot,q)
        else:
            print 'done'
    except:
        window.after(1,updateplot,q)

def simulation(q):   
    try:
        while True:
            for i in range(10):
                q.put( (randint(0,1500), randint(0,1500)) )
                time.sleep(1)
            erasePatchesAndCreateNew_A()
            time.sleep(1)
            for i in range(10):
                q.put( (randint(0,1500), randint(0,1500)) )
                time.sleep(1)
            erasePatchesAndCreateNew_B()
            time.sleep(1)
    except KeyboardInterrupt:
        print "received KeyboardInterrupt"
    finally:
        print "simulation ended"
        sys.exit()

if __name__ == '__main__':
    main()

下面是该程序的截图,红点在图表中移动,但补丁(黑色形状)永远不会改变。 enter image description here

2 个答案:

答案 0 :(得分:1)

我设法解决了这个问题,或者确切地说我想出了这个问题。我在下面添加它,也许它将来会帮助别人。 我主要是在主线程中更新图表时使用队列进行通信。

import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
import multiprocessing
from Tkinter import *
import matplotlib.path as path
import matplotlib.patches as patches
import matplotlib.pyplot as plt
import sys, thread, time

from random import randint

#Create a window
window=Tk()

sendProximityInfo = True
latest_published_msg = ""

monitor_box = path.Path([(1000, -1000), (111, -213), (62, -220), (800, 1734)])
fig = matplotlib.figure.Figure()
ax  = fig.add_subplot(1,1,1)
ax.set_xlim(-1500,2000)
ax.set_ylim(-1500,2000)
patch = patches.PathPatch(monitor_box, facecolor='black', lw=1)
ax.add_patch(patch)

def main():
    #Create a queue to share data between process
    q = multiprocessing.Queue()
    #Create and start the simulation process
    simulate = multiprocessing.Process(target=simulation,args=(q,))
    simulate.start()   
    #Create the base plot
    plot()
    #Call a function to update the plot when there is new data
    updateplot(q)

    window.mainloop()
    print 'Done'
    simulate.join() # wait for the other process to finish as well

def plot():    #Function to create the base plot, make sure to make global the lines, axes, canvas and any part that you would want to update later
    global line, ax, canvas, fig, monitor_box, patch
    patch.remove()
    monitor_box = path.Path([(500, -500), (111, -213), (62, -220), (800, 1734)])
    patch = patches.PathPatch(monitor_box, facecolor='pink', lw=1)
    ax.add_patch(patch)

    canvas = FigureCanvasTkAgg(fig, master=window)
    canvas.show()
    canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
    canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1)
    line, = ax.plot([], [], 'ro')

def erasePatchesAndCreateNew_A():
    print "erasePatchesAndCreateNew_A"
    global ax, monitor_box, patch
    patch.remove()
    monitor_box = path.Path([(35, 1677), (11, -213), (652, -220), (500, 1734)])
    patch = patches.PathPatch(monitor_box, facecolor='red', lw=1)
    ax.add_patch(patch)


def erasePatchesAndCreateNew_B():
    print "erasePatchesAndCreateNew_B"
    global ax, monitor_box, patch
    patch.remove()
    monitor_box = path.Path([(-2000, 2000), (11, -213), (300, -220), (500, 1734)])
    patch = patches.PathPatch(monitor_box, facecolor='blue', lw=1)
    ax.add_patch(patch)


def updateplot(q):
    try:       #Try to check if there is data in the queue
        result = q.get_nowait()

        if result != 'A' and result != 'B':
            x, y = result
            line.set_data(x, y)
            ax.draw_artist(line)
            canvas.draw()
            window.after(10,updateplot,q)
        elif result == 'A':
            erasePatchesAndCreateNew_A()
            canvas.draw()
            window.after(10,updateplot,q)
        elif result == 'B':
            erasePatchesAndCreateNew_B()
            canvas.draw()
            window.after(10,updateplot,q)
    except:
        window.after(10,updateplot,q)

def simulation(q):   
    try:
        while True:
            for i in range(5):
                q.put( (randint(0,1500), randint(0,1500)) )
                time.sleep(0.5)
            #erasePatchesAndCreateNew_A()
            q.put('A')
            time.sleep(1)
            for i in range(5):
                q.put( (randint(0,1500), randint(0,1500)) )
                time.sleep(0.5)
            #erasePatchesAndCreateNew_B()
            q.put('B')
            time.sleep(1)
    except KeyboardInterrupt:
        print "received KeyboardInterrupt"
    finally:
        print "simulation ended"
        sys.exit()

if __name__ == '__main__':
    main()

答案 1 :(得分:1)

我试图了解您的问题,看看我是否可以解决它。我设法在用户请求时替换LIVE matplotlib聊天中的补丁图。我的工作代码如下所示。

在消化你的问题时,我认为多处理部分对主要问题有点分心,即无法刷新嵌入在tkinter窗口中的“matplotlib patches.Path”图。因此,我采用了Sentdex的LIVE matplotlib Chart解决方案(在我的脚本中引用),它与您的代码非常相似,作为研究主要问题的基础。我认为他制作现场情节的方法很容易理解。

所以在我的代码中,当tkinter窗口启动时,实时数据正在流入tkinter窗口LIVE图表,它直接从文件“patchesCoor读取”matplotlib patches.Path“的坐标.txt“并用它更新LIVE图表。您必须在与脚本相同的文件夹中创建“patchesCoor.txt”,并且它应包含一行条目,其中包含由空格分隔的8个数字。每次修改该文件中的坐标并保存时,更改都将显示在tkinter窗口的LIVE图表中。我的解决方案排除了多处理,尽管可以将其作为此脚本的下一个阶段实现。

负责更新LIVE图表中的图表的部分可在“def animate(i)”中找到。看那里的评论。

希望您发现这个解决方案很有用,虽然它是在您发布的答案后几个小时发布的。 :)

#!/usr/bin/python3.5
# -*- coding: utf-8 -*-
"""
1. Script for creating LIVE matplotlib figure inside a tkinter window via 
   tkinter backend TkAgg (see Reference 1). I replaced the Pack system with a
   Grid system for the Tk objects (see Reference 2), created the scripts to input 
   the live data and added your random coordinate generator. 

2. It requires 1 input file:
   patchesCoor.txt - 4 x,y coordinate points for the patches.PathPatch plot
                     space seperated type.

References:
1. https://www.youtube.com/watch?v=Zw6M-BnAPP0
2. http://stackoverflow.com/questions/12913854/displaying-matplotlib-navigation-toolbar-in-tkinter-via-grid

Author: Sun Bear
Created on: 17 Jan 2017
"""

import matplotlib
matplotlib.use('TkAgg') # Backend of matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.animation as animation
from matplotlib import style
style.use('ggplot')

try:
    # for Python2
    import Tkinter as tk   ## notice capitalized T in Tkinter 
    import ttk
except ImportError:
    # for Python3
    import tkinter as tk
    import tkinter.ttk as ttk 

import random


class App(ttk.Frame):
    ''' Create tkinter window frame with base matplotlib figure, subplot and
        toolbar. '''

    def __init__(self, parent, *args, **kwargs):

        # Customise ttk styles
        s=ttk.Style()
        s.configure(".", font=('URW Gothic L', '11', 'bold'), foreground='#3D3C3A',
                    cap=tk.ROUND, join=tk.ROUND)
        s.configure('App.TFrame', background='pink')

        # Initialise App Frame
        ttk.Frame.__init__(self, parent, style='App.TFrame', borderwidth=20,
                           relief=tk.FLAT)
        self.grid(row=0, column=0, sticky='nsew')

        # Create tk Canvas
        canvas = FigureCanvasTkAgg(f, self)
        canvas.show()
        canvas.get_tk_widget().grid(row=0, column=0, sticky='nsew')

        # Create matplotlib navigation toolbar in a grid frame
        toolbar_frame = ttk.Frame(self, style='App.TFrame', borderwidth=2,
                           relief=tk.RAISED)
        toolbar_frame.grid(row=1, column=0, sticky='nsew')
        toolbar = NavigationToolbar2TkAgg(canvas, toolbar_frame)

        root.rowconfigure(0, weight=1)
        root.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

def animate(i):
    '''Provide matplotlib figure with latest plots coordinates and refresh 
       matplotlib figure.'''

    # 1. Obtain x, y coordinates for Live data plot
    xList, yList = simulate()

    # 2. Obtain x, y coordinates for patches.PathPatch plot
    patchesData = open('patchesCoor.txt', 'r').read()
    print('patchesData = {}'.format(patchesData))
    patchesList = patchesData.split()
    print('patchesList = {}'.format(patchesList))
    if len(patchesList) > 1:
        x1,y1,x2,y2,x3,y3,x4,y4 = tuple(int(x) for x in patchesList)
    print('patchesCoor = {0} {1} {2} {3} {4} {5} {6} {7}'.
          format(x1,y1,x2,y2,x3,y3,x4,y4))
    monitor_box = path.Path([(x1, y1), (x2, y2), (x3, y3), (x4, y4)])
    patch = patches.PathPatch(monitor_box, facecolor='blue', lw=1)

    # 3. Clear LIVE Chart and update it with latest plot data
    ax.clear()
    ax.plot(xList, yList)   # For random x, y data plot 
    ax.add_patch(patch)     # For patches.PathPatch plot
    ax.set_xlim(-1500,2000) #   Need the following 2 lines to ensure 
    ax.set_ylim(-1500,2000) #   the Live Chart axis is updated at every read

def simulate():
    ''' Generate random x, y coordinate for Live data'''
    xList = []
    yList = []
    for i in range(100):
        x, y = random.randint(0,1500), random.randint(0,1500)
        xList.append(int(x))
        yList.append(int(y))
    return xList, yList

def matplotlib_base_figure():
    ''' Create matplotlib base figure '''
    f = Figure(figsize=(5,5), dpi=100)
    ax = f.add_subplot(111) # One chart created
    ax.plot([1,2,3,4,5,6,7,8], [5,6,1,8,6,10,2,7])
    monitor_box = path.Path([(35, 1677), (111, -213), (62, -220), (800, 1734)])
    patch = patches.PathPatch(monitor_box, facecolor='black', lw=1)
    ax.add_patch(patch)
    ax.set_xlim(-1500,2000)
    ax.set_ylim(-1500,2000)
    return f, ax 

if __name__ == '__main__':

    # 1. Create matplotlib base figure and subplot
    f, ax = matplotlib_base_figure()

    # 2. Create tkinter GUI with matplotlib base figure, subplot & toolbar.
    root = tk.Tk()
    app = App(root)

    # 3. Make matplotlib figure LIVE via animation
    ani = animation.FuncAnimation(f, animate, interval=1000)
    # 'interval' control the update rate of the LIVE Chart. 

    # 4. Activate GUI continually via an infinite loop.
    app.mainloop()

原文: Before 已更新: Updated