2015-11-20 9 views
6

ich auf der Grundlage der Antwort auf diese Frage eine scrollbare Multiplot erstellen bin versucht: Creating a scrollable multiplot with python's pylabWie Künstler in scrollbaren, matplotlib zu aktualisieren und Multiplot

Linien erstellt mit ax.plot() korrekt aktualisieren, aber ich bin nicht in der Lage finde heraus, wie man Künstler, die mit xvlines() und fill_between() erstellt wurden, aktualisieren kann.

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() 

Dies ist, wie es im Moment enter image description here

aussieht, kann ich den gleichen Ansatz wie für plot() nicht gelten, da die folgenden eine Fehlermeldung erzeugt:

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 

Diese ist, wie es auf jedem Rahmen aussehen soll enter image description here

Antwort

1

fill_between gibt eine PolyCollection zurück, die beim Erstellen eine Liste (oder mehrere Listen) von Scheitelpunkten erwartet. Leider habe ich keine Möglichkeit gefunden, die Scheitelpunkte zu erhalten, die zum Erstellen der gegebenen PolyCollection verwendet wurden, aber in Ihrem Fall ist es einfach genug, die PolyCollection direkt zu erstellen (wodurch die Verwendung von fill_between vermieden wird) und dann die Scheitelpunkte bei Rahmenwechsel zu aktualisieren .

Unterhalb einer Version des Codes, das tut, was Sie nach:

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() 

Jede der drei PolyCollections (reds, blues und greens) nur vier Ecken hat (die Ränder der Rechtecke), die werden basierend auf den gegebenen Daten bestimmt (was in update_collections getan wird). Das Ergebnis sieht wie folgt aus:

example result of given code

in Python Getestet 3.5