【问题标题】:Python -- Matplotlib redrawing lines without previous lines remainingPython -- Matplotlib 重绘线条而不保留先前的线条
【发布时间】:2013-11-29 18:58:19
【问题描述】:

这个类在 Matplotlib 中绘制曲线。用户鼠标输入部分将set_data() 更改为几个x,y 坐标。 PQ 似乎正在正确重置。但是,当 R 未设置为使用相同方法(set_data()set_x()set_y())的计算时,则会导致错误:

TypeError: unsupported operand type(s) for ** or pow(): 'NoneType' and 'int'

R 计算留在这会导致错误:

AttributeError: 'list' object has no attribute 'set_xdata'

整个类(它有点大,但方法是相互依赖的,我不想在这里遗漏一些可能相关的东西):

from mpl_toolkits.axes_grid.axislines import SubplotZero
import numpy as np
import matplotlib.pyplot as plt
from math import sqrt



class ECC(object):

    def __init__(self,a,b,px,qx,qy):
        """
        initialize input variables
        """
        self.a = a
        self.b = b
        self.pxlam = px
        self.qxlam = qx
        self.invertQy = qy
        self.fig = plt.figure(1)
        self.ax = SubplotZero(self.fig, 111)
        self.xr = 0
        self.yr = 0



    def onclick(self, event):
        x = event.xdata
        if event.button == 1:
            self.pxlam = x
        if event.button == 3:
            self.qxlam = x

        pylam = self.ecclambda(self.pxlam,self.a,self.b)  # calculate P from pxlam
        qylam = self.ecclambda(self.qxlam,self.a,self.b)  # calculate Q from qxlam
        if self.invertQy == 1:  qylam = -qylam # optional, inverts qy to negative on the plot
        plt.plot([self.pxlam,self.qxlam], [pylam,qylam], color = "c", linewidth=1)
        self.p = plt.plot([self.pxlam], [pylam], "mo")[0]
        self.q = plt.plot([self.qxlam], [qylam], "mo")[0]
        self.pt = plt.text(self.pxlam-0.25,pylam+0.5, '$P$')
        self.qt = plt.text(self.qxlam-0.25,self.qxlam+0.5, '$Q$')

        self.xr,self.yr = self.dataToPlotR(pylam,qylam)
        plt.plot([self.xr],[self.yr],"mo")
        plt.plot([self.xr],[-1*(self.yr)],"co")
        self.rxdata = [self.qxlam,self.xr]; self.rydata = [qylam,self.yr]
        self.r, = plt.plot(self.rxdata, self.rydata, color = "c", linewidth=1)
        #plt.plot([xr,xr], [yr,-yr], "x--")
        self.plotR(qylam)
        plt.text(self.xr+0.25,self.yr, '$-R$'); plt.text(self.xr+0.25,-1*(self.yr), '$R$')
        plt.text(-9,6,' P: (%s ,%s) \n Q: (%s ,%s) \n R: (%s ,%s) \n a: %s \n b: %s '
                %(self.pxlam,pylam,self.qxlam,qylam,self.xr,-1*(self.yr),self.a,self.b),
                fontsize=10, color = 'blue',bbox=dict(facecolor='tan', alpha=0.5))

        self.update()

    def update(self):
        pylam = self.ecclambda(self.pxlam,self.a,self.b)  # calculate P from pxlam
        qylam = self.ecclambda(self.qxlam,self.a,self.b)  # calculate Q from qxlam
        self.p.set_data([self.pxlam], [pylam])
        self.q.set_data([self.qxlam], [qylam])
        self.pt.set_x(self.pxlam-0.25)
        self.pt.set_y(pylam+0.5)
        self.qt.set_x(self.qxlam-0.25)
        self.qt.set_y(qylam+0.5)
        self.xr,self.yr = self.dataToPlotR(pylam,qylam)
        #self.rxdata.set_xdata([self.qxlam,self.xr])  # R calculations
        #self.rydata.set_ydata([qylam,self.yr])  # R calculations
        plt.gcf().canvas.draw()

        #self.plotR(self.xr,self.yr,qylam)

我上面提到的代码行,关于 R 方法是保留还是保留,在方法 update() 中注释掉了 2 行,然后用 # R calculation 注释掉。 我现在正在自学 Matplotlib,所以我确信初级程序员可以在很短的时间内看到我明显的错误,但我已经有一段时间了,而且进展不快。 我在这里要做的主要事情就是在每次点击后重新绘制线条和点,而不需要将任何先前设置的点保留在图表上。与图表左上角的文本框类似,每次单击后都应在此处重置值,而不是重写每个先前的文本字符串。

编辑:

我已经尝试过cla()clf(),但在这种情况下它们似乎不起作用。 事实上,在这个程序中,它们甚至可能在任何时候都不是必需的,因为我使用的 set_data() 方法应该足以根据每次点击的新数据重绘。为了证明这一点,只需在我的类中取消注释整个plotGraph() 方法并注释掉update() 中的相同代码,您会看到点PQ 将在单击后设置为新的。真正的问题是R 点、线条和左上角的文本框。

【问题讨论】:

  • 另外,您应该发布完整的回溯,因为它们会告诉您问题出在哪一行。
  • @stackuser 我同意@tcaswell:您可以将代码示例限制在最低限度:__init__onClick 足以描述问题仅限于更新的事实/删除一些对象。仅发布诸如“显示用户单击的框和行”之类的内容就会带来您需要处理的所有机制。此外,“路过”正是您对助手的期望,因此请接受批评。

标签: python math python-2.7 numpy matplotlib


【解决方案1】:

我已将问题简化到最低限度,通过在 SO 上搜索 set_xdata 并按照 tcaswell 提供的链接,我找到了 this subject,这真的很清楚。

这是演示代码,用 5 分钟编写:

import matplotlib.pyplot as plt

class OnClickTest(object):
    def __init__(self):
        self.fig = plt.figure()

        plt.plot([0, 1, 2], [0, 4, 3])
        self.line, self.text, self.prev_click = None, None, None
        self.fig.canvas.mpl_connect('button_press_event', self.onClick)

        plt.show()

    def onClick(self, event):
        x, y = event.xdata, event.ydata
        if self.line is None:
            # creating the object
            self.line, = plt.plot([0, x], [0, y])
            self.text = plt.text(x, y, "My click")
            self.prev_click = (x, y)
        else:
            # updating the object
            self.line.set_xdata([self.prev_click[0], x])
            self.line.set_ydata([self.prev_click[1], y])
            self.text.set_position((x, y))
            self.prev_click = (x, y)
        self.fig.canvas.draw()

o = OnClickTest()

【讨论】:

    【解决方案2】:

    好吧,起初self.rxdataself.rydata 是列表,它们没有set_xdata 方法,因此出现错误。也许你想做self.my_plot.set_xdata(...)之类的事情?

    无论如何,有一个非常聪明 其他方法可以做到这一点:matplotlib 是面向对象的,这意味着它将图形内容作为对象处理;并且你可以添加东西,你可以also remove some of them by calling their methods,但为此你需要他们的参考。

    因此,在您的情况下,您只需要保留对您想要添加/删除的对象的引用:

    • __init_()定义中,只需添加一个引用跟踪器:

      def __init__(...):
          (...)
          self.text = None
      
    • 并在onClick() 定义中使用此引用:

      if self.text is not None:     # if text has already been drawn before,
          self.text.remove()        # simply remove it
      self.text = plt.text(-9, 6,   # and re-create it, keeping the reference
                           ' P: (%s ,%s) \n Q: (%s ,%s) \n R: (%s ,%s) \n a: %s \n b: %s '
                           (...))
      

    有了这个唯一的添加,并保持注释返回错误的两行,文本框在每次点击图表时都会刷新。

    我认为您明白了,并且能够将其复制到您要删除和重新创建的每个对象;没有兴趣重新绘制省略号图。


    评论后编辑

    好的,remove 退出所有对象,但实际上,plt.plot 返回一个包含一个元素的列表。因此,一个解决方案是简单地创建一个所有将被刷新的对象的列表,并为每个人调用这个remove 方法:

    • __init__():

      def __init__(...):
          (...)
          self._tracker = []
      
    • plotR(),我们必须返回引用:

      def plotR(self,qylam):
          r1, = plt.plot([self.qxlam, self.xr], [qylam, self.yr], color = "c", linewidth=1)
          r2, = plt.plot([self.xr, self.xr], [self.yr, -1*(self.yr)], "x--")
          return r1, r2
      
    • onClick(),我提出以下代码:

      def onclick(self, event):
           # removing elements that will be regenerated
          for element in self._tracker:      #print "trying to remove", element
              element.remove()
          # reinitializing the tracker
          self._tracker = []
          (...)
      
          _e1 = plt.plot([self.pxlam,self.qxlam], [pylam,qylam], 
                         color = "c", linewidth=1)[0]
          (...)
          _e2 = plt.plot([self.xr],[self.yr],"mo")[0]
          _e3 = plt.plot([self.xr],[-1*(self.yr)],"co")[0]
          (...)
          _e4, _e5 = self.plotR(qylam)
          _e6 = plt.text(self.xr+0.25,self.yr, '$-R$'); plt.text(self.xr+0.25,-1*(self.yr), '$R$')
          _e7 = plt.text(-9,6,' P: (%s ,%s) \n Q: (%s ,%s) \n R: (%s ,%s) \n a: %s \n b: %s '
                         %(self.pxlam,pylam,self.qxlam,qylam,self.xr,-1*(self.yr),self.a,self.b),
                         fontsize=10, color = 'blue',bbox=dict(facecolor='tan', alpha=0.5))
          (...)
      
          # adding in the tracker
          self._tracker.extend([self.p, self.q, self.pt, self.qt, self.r,
                                _e1, _e2, _e3, _e4, _e5, _e6, _e7])
      
          self.update()
      

    这能解决问题吗?

    注意:另一种解决方案可能是更改对象的参数(位置、数据),{edit} 如下面的 tcaswell 评论所示,并在我对该问题的其他答案中实现。

    【讨论】:

    • 好吧,其实我随机选择了plt.text,貌似是最简单的例子;对于其他对象,remove 存在但更难使用(请参阅this long post。)我会尝试相应地更新我的答案。
    • @Joël 该帖子的重点是“跟踪返回的对象”,这使得它比需要的复杂得多。 ln, = ax.plot(...) 然后ln.remove(); del ln 应该可以正常工作。在新版本的 mpl 中(OP 使用的是 0.99)
    • 我刚刚按照我最初采取的方向更新了我的答案。但是,@tcaswell 和ln, = (...),您不只是在“跟踪对象”吗?我不明白其中的区别。
    • 是的,但是这篇文章强调了将从plot 返回的列表与ax.lines 属性进行比较,除了名称相同之外,这些属性是不相关的。这不是只是不必要的混乱。它正在解决一个存在于非常旧的 mpl 版本中的错误
    • Text 对象可以更改其所有属性:matplotlib.org/api/artist_api.html#matplotlib.text.Text
    猜你喜欢
    • 2018-10-02
    • 2015-02-07
    • 2014-04-05
    • 2018-11-26
    • 1970-01-01
    • 1970-01-01
    • 2016-03-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多