我正在尝试根据这个问题的答案创建一个可滚动的多重绘图: Creating a scrollable multiplot with python's pylab
使用ax.plot()
创建的行正在正确更新,但我无法弄清楚如何更新使用xvlines()
和fill_between()
创建的艺术家。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.widgets import Slider
#create dataframes
dfs={}
for x in range(100):
col1=np.random.normal(10,0.5,30)
col2=(np.repeat([5,8,7],np.round(np.random.dirichlet(np.ones(3),size=1)*31)[0].tolist()))[:30]
col3=np.random.randint(4,size=30)
dfs[x]=pd.DataFrame({'col1':col1,'col2':col2,'col3':col3})
#create figure,axis,subplot
fig = plt.figure()
gs = gridspec.GridSpec(1,1,hspace=0,wspace=0,left=0.1,bottom=0.1)
ax = plt.subplot(gs[0])
ax.set_ylim([0,12])
#slider
frame=0
axframe = plt.axes([0.13, 0.02, 0.75, 0.03])
sframe = Slider(axframe, 'frame', 0, 99, valinit=0,valfmt='%d')
#plots
ln1,=ax.plot(dfs[0].index,dfs[0]['col1'])
ln2,=ax.plot(dfs[0].index,dfs[0]['col2'],c='black')
#artists
ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==5,facecolor='r',edgecolors='none',alpha=0.5)
ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==8,facecolor='b',edgecolors='none',alpha=0.5)
ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==7,facecolor='g',edgecolors='none',alpha=0.5)
ax.vlines(x=dfs[0]['col3'].index,ymin=0,ymax=dfs[0]['col3'],color='black')
#update plots
def update(val):
frame = np.floor(sframe.val)
ln1.set_ydata(dfs[frame]['col1'])
ln2.set_ydata(dfs[frame]['col2'])
ax.set_title('Frame ' + str(int(frame)))
plt.draw()
#connect callback to slider
sframe.on_changed(update)
plt.show()
我无法应用与plot()
相同的方法,因为以下内容会产生错误消息:
ln3,=ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==5,facecolor='r',edgecolors='none',alpha=0.5)
TypeError: 'PolyCollection' object is not iterable
答案 0 :(得分:2)
fill_between
返回PolyCollection
,它在创建时需要一个列表(或几个列表)顶点。不幸的是,我还没有找到一种方法来检索用于创建给定PolyCollection
的顶点,但在您的情况下,直接创建PolyCollection
很容易(从而避免使用fill_between
)然后在帧更改时更新其顶点。
您的代码版本低于您的目标:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.widgets import Slider
from matplotlib.collections import PolyCollection
#create dataframes
dfs={}
for x in range(100):
col1=np.random.normal(10,0.5,30)
col2=(np.repeat([5,8,7],np.round(np.random.dirichlet(np.ones(3),size=1)*31)[0].tolist()))[:30]
col3=np.random.randint(4,size=30)
dfs[x]=pd.DataFrame({'col1':col1,'col2':col2,'col3':col3})
#create figure,axis,subplot
fig = plt.figure()
gs = gridspec.GridSpec(1,1,hspace=0,wspace=0,left=0.1,bottom=0.1)
ax = plt.subplot(gs[0])
ax.set_ylim([0,12])
#slider
frame=0
axframe = plt.axes([0.13, 0.02, 0.75, 0.03])
sframe = Slider(axframe, 'frame', 0, 99, valinit=0,valfmt='%d')
#plots
ln1,=ax.plot(dfs[0].index,dfs[0]['col1'])
ln2,=ax.plot(dfs[0].index,dfs[0]['col2'],c='black')
##additional code to update the PolyCollections
val_r = 5
val_b = 8
val_g = 7
def update_collection(collection, value, frame = 0):
xs = np.array(dfs[frame].index)
ys = np.array(dfs[frame]['col2'])
##we need to catch the case where no points with y == value exist:
try:
minx = np.min(xs[ys == value])
maxx = np.max(xs[ys == value])
miny = value-0.5
maxy = value+0.5
verts = np.array([[minx,miny],[maxx,miny],[maxx,maxy],[minx,maxy]])
except ValueError:
verts = np.zeros((0,2))
finally:
collection.set_verts([verts])
#artists
##ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==5,facecolor='r',edgecolors='none',alpha=0.5)
reds = PolyCollection([],facecolors = ['r'], alpha = 0.5)
ax.add_collection(reds)
update_collection(reds,val_r)
##ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==8,facecolor='b',edgecolors='none',alpha=0.5)
blues = PolyCollection([],facecolors = ['b'], alpha = 0.5)
ax.add_collection(blues)
update_collection(blues, val_b)
##ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==7,facecolor='g',edgecolors='none',alpha=0.5)
greens = PolyCollection([],facecolors = ['g'], alpha = 0.5)
ax.add_collection(greens)
update_collection(greens, val_g)
ax.vlines(x=dfs[0]['col3'].index,ymin=0,ymax=dfs[0]['col3'],color='black')
#update plots
def update(val):
frame = np.floor(sframe.val)
ln1.set_ydata(dfs[frame]['col1'])
ln2.set_ydata(dfs[frame]['col2'])
ax.set_title('Frame ' + str(int(frame)))
##updating the PolyCollections:
update_collection(reds,val_r, frame)
update_collection(blues,val_b, frame)
update_collection(greens,val_g, frame)
plt.draw()
#connect callback to slider
sframe.on_changed(update)
plt.show()
三个PolyCollections
(reds
,blues
和greens
)中的每一个都只有四个顶点(矩形的边缘),这些顶点是根据给定数据(在update_collections
中完成)。结果如下:
在Python 3.5中测试
答案 1 :(得分:0)
您的错误
TypeError: 'PolyCollection' object is not iterable
通过在l3
之后删除逗号可以避免:
l3 = ax.fill_between(xx, y1, y2, **kwargs)
返回值为PolyCollection,您需要在update()
函数期间更新其顶点。这里发布的其他答案的替代方法是使fill_between()
为您提供一个新的PolyCollection,然后获取其顶点并使用它们来更新l3
的顶点:
def update(val):
dummy_l3 = ax.fill_between(xx, y1, y2, **kwargs)
verts = [ path._vertices for path in dummy_l3.get_paths() ]
codes = [ path._codes for path in dummy_l3.get_paths() ]
dummy_l3.remove()
l3.set_verts_and_codes(verts, codes)
plt.draw()