总结:我使用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)
结果合理得当:
这个例子显示了一些产生的正弦波:
https://gist.github.com/mdkrajnak/f7cfd3f720453d53da4a80fa45df3b66
后来我做了一个额外的修改,以便每行使用Axes.twinx有一个独立的y轴。在修改之后,现在有一个我无法删除的工件,它似乎是在第一次渲染第一行时遗留下来的。
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函数之前初始绘制轴。有什么东西我可以去预先斧头,还是我需要做些什么呢?
答案 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
完整代码:
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;