如何修改matplotlib FuncAnimation init_func以删除使用twinx引入的工件?

时间:2017-05-28 22:49:10

标签: animation matplotlib

总结:我使用matplotlib动画框架创建了一个绘制条形图的脚本,以使用Axes.twinx()支持多个y轴,这导致了我无法删除的绘图上的工件。我想我需要修改动画init_func以考虑多个轴。

Python 2.7,带有Qt后端的matplotlib 2.0.2,以及在Ubuntu Linux 17.04上运行的conda 4.3.18。

完整描述:我根据matplotlib条形图演示创建了条形图绘制程序:

https://matplotlib.org/examples/animation/strip_chart_demo.html

我对它进行了修改以帮助一些项目的EE,包括以更加程序化的方式重写它,以便他们更舒服地使用它(不确定它是多么成功,我们还在讨论它),增加对多个的支持线条,并改变它,以便它不断向左滚动。

该脚本包含一个makeChart()函数,它在循环中创建一系列Line2D,并将它们添加到列表中,并将它们返回给调用者:

lines = [] 
for iline in range(0,linesPerPlot):
    lines.append(makeLine(ax, maxt, dt, ymin, ymax, colors[iline % len(colors)]))

return lines

当我添加多行时,在使用blit = True运行时出现了一个神器,我通过添加init_function来摆脱它。

init函数很简单,如下所示:

def initDisplay(lines):
    """Init display."""
    return lines    

当脚本执行时,调用makeChart()并返回正在绘制的Line2D列表,它会创建一个包装init函数的lambda,然后传递给FuncAnimation:

lines = makeChart(ax, secondsPerPlot, secondsPerSample, linesPerPlot, ymin, ymax)
...
init = lambda: initDisplay(lines)
ani = animation.FuncAnimation(fig, update, emitter, init_func=init, interval=millisPerFrame, blit=True)

结果合理得当:

strip chart with multiple lines

这个例子显示了一些产生的正弦波:

https://gist.github.com/mdkrajnak/f7cfd3f720453d53da4a80fa45df3b66

后来我做了一个额外的修改,以便每行使用Axes.twinx有一个独立的y轴。在修改之后,现在有一个我无法删除的工件,它似乎是在第一次渲染第一行时遗留下来的。

plot with artifact

makeChart()中的新内部循环如下所示:

lines = [] 
lines.append(makeLine(ax, maxt, dt, ymin, ymax, colors[0]))    
for iline in range(1,linesPerPlot):
    twin_ax = ax.twinx()
    lines.append(makeLine(twin_ax, maxt, dt, ymin, ymax, colors[iline % len(colors)]))

return lines

完整的代码在这里:

https://gist.github.com/mdkrajnak/e8b37300545f3ffea651d628933bd0ee

我尝试修改init函数,以便返回包含行和轴的列表:

def initDisplay(lines, axs):
    """Init display."""
    return lines + axs

makeChart()函数使它返回轴以及它返回的艺术家序列中的行:

lines = []
axs = []

# Add first line, then add subsequent lines sharing the x-axis.  
lines.append(makeLine(ax, maxt, dt, ymin, ymax, colors[0]))   
axs.append(ax) 
for iline in range(1,linesPerPlot):
    twin_ax = ax.twinx()
    lines.append(makeLine(twin_ax, maxt, dt, ymin, ymax, colors[iline % len(colors)]))
    axs.append(twin_ax)

return lines, axs

完整的代码在这里:

https://gist.github.com/mdkrajnak/e6eaca509cd8321b9b56a4d25c3e1e80

但是这个版本失败了“AttributeError:draw_artist只能在缓存渲染的初始绘制后使用”

File "/home/mdk/opt/miniconda3/envs/p2/lib/python2.7/site-packages/matplotlib/animation.py", line 1123, in _post_draw self._blit_draw(self._drawn_artists, self._blit_cache)
File "/home/mdk/opt/miniconda3/envs/p2/lib/python2.7/site-packages/matplotlib/animation.py", line 1138, in _blit_draw a.axes.draw_artist(a)
File "/home/mdk/opt/miniconda3/envs/p2/lib/python2.7/site-packages/matplotlib/axes/_base.py", line 2441, in draw_artist raise AttributeError(msg)
AttributeError: draw_artist can only be used after an initial draw which caches the render

我的想法仍然是init函数需要返回轴和线,但是我需要以某种方式在调用init函数之前初始绘制轴。有什么东西我可以去预先斧头,还是我需要做些什么呢?

1 个答案:

答案 0 :(得分:1)

似乎每个轴执行blitting。所以可能是程序

for ax in all axes:
    get axes background
    draw line

这意味着第一行是来自第二轴的背景的一部分,因此将成为每个连续帧的一部分。

我现在能想到的唯一解决方案是让线条不可见,直到所有轴的背景都存储为blitting。

line = Line2D(tdata, ydata, color=color, visible=False)

只有在第一次致电updateLines后,才能再次看到它们。

n = [0]
def updateLines(lines, arrays):
    """Update individual lines and return a sequence of artists to the animator."""
    artists = []
    for iline in range(len(lines)):
        artists.append(updateLine(lines[iline], arrays[iline]))
        if n[0] > 0:
            lines[iline].set_visible(True)
    n[0] += 1
    return artists

enter image description here

完整代码:



import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.lines import Line2D

import math

# Initalize script constants
ymin = -1.1
ymax = 1.1

linesPerPlot = 3

samplesPerFrame = 1
framesPerSecond = 20
secondsPerPlot = 5

# Calculate dependent constants
samplesPerSecond = samplesPerFrame * framesPerSecond
samplesPerPlot =  samplesPerSecond * secondsPerPlot
secondsPerSample = 1.0/samplesPerSecond
millisPerFrame = 1000.0/framesPerSecond

# Define core functions

def makeLine(ax, maxt, dt, ymin, ymax, color):
"""Make an empty Line2D for the initial chart."""
nvalues = int(round(maxt/dt))

tdata = [dt*tm for tm in range(nvalues)]
ydata = [0 for tm in range(nvalues)]

line = Line2D(tdata, ydata, color=color, visible=False) ### <- visible false
ax.add_line(line)
ax.set_ylim(ymin, ymax)

return line

    
def makeChart(ax, maxt, dt, linesPerPlot, ymin, ymax):
"""Make a chart and return a list of the lines it contains."""
colors = [ 'r', 'b', 'g', 'k' ]
lines = []

# Add first line, then add subsequent lines sharing the x-axis.  
lines.append(makeLine(ax, maxt, dt, ymin, ymax, colors[0]))    
for iline in range(1,linesPerPlot):
    twin_ax = ax.twinx()
    lines.append(makeLine(twin_ax, maxt, dt, ymin, ymax, colors[iline % len(colors)]))

ax.set_xlim(0, maxt)

return lines


def initDisplay(lines):
"""Init display."""
return lines


def updateLine(line, ys):
"""Update the data in one line, popping off the last value."""
tdata, ydata = line.get_data()
for y in ys:
    ydata.append(y)
    ydata.pop(0)
line.set_data(tdata, ydata)

return line

n = [0]
def updateLines(lines, arrays):
"""Update individual lines and return a sequence of artists to the animator."""
artists = []
for iline in range(len(lines)):
    artists.append(updateLine(lines[iline], arrays[iline]))
    if n[0] > 0:
        lines[iline].set_visible(True)
n[0] += 1
return artists


def emitData(linesPerPlot, samplesPerFrame):
"""Create the data that will be plotted."""
nsample = 0
while True:
    samples = [[] for i in range(linesPerPlot)]
    for isample in range(samplesPerFrame):
        nsample = nsample + 1
        for iline in range(linesPerPlot):
            pi_increment = (math.pi/(10.0 * (iline+1)))
            samples[iline].append(math.sin(nsample * pi_increment))

    yield samples


# Make chart.
fig, ax = plt.subplots()
lines = makeChart(ax, secondsPerPlot, secondsPerSample, linesPerPlot, ymin, ymax)

# Start the animator.
update = lambda samples: updateLines(lines, samples)
emitter = lambda: emitData(linesPerPlot, samplesPerFrame)
init = lambda: initDisplay(lines)

ani = animation.FuncAnimation(fig, update, emitter, init_func=init, interval=millisPerFrame, blit=True)

plt.show()
&#13;
&#13;
&#13;