2017-09-20 3 views
0

Während nach einer Möglichkeit, animierte interaktive Handlung mit matplotlib zu machen, stieß ich auf dieses Stück Code auf Stack-Überlauf-Dokumentation:animierte interaktive Handlung mit matplotlib

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 
from matplotlib.widgets import Slider 

TWOPI = 2*np.pi 

fig, ax = plt.subplots() 

t = np.arange(0.0, TWOPI, 0.001) 
initial_amp = .5 
s = initial_amp*np.sin(t) 
l, = plt.plot(t, s, lw=2) 

ax = plt.axis([0,TWOPI,-1,1]) 

axamp = plt.axes([0.25, .03, 0.50, 0.02]) 
# Slider 
samp = Slider(axamp, 'Amp', 0, 1, valinit=initial_amp) 

def update(val): 
    # amp is the current value of the slider 
    amp = samp.val 
    # update curve 
    l.set_ydata(amp*np.sin(t)) 
    # redraw canvas while idle 
    fig.canvas.draw_idle() 

# call update function on slider value change 
samp.on_changed(update) 

plt.show() 

Dieser Code fast genau das tut, was ich suche , aber ich möchte das Diagramm animieren, dh den Schieberegler wird automatisch von links nach rechts bewegt, zum Beispiel um 0,01 pro Sekunde. Gibt es dafür einen einfachen Weg? Wissend, dass ich auch die manuelle Steuerung auf dem Schieberegler behalten möchte (mit click event).

Antwort

0

Hier ist eine einfache Anpassung des Codes Animation hinzuzufügen:

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 
from matplotlib.widgets import Slider 

TWOPI = 2*np.pi 

fig, ax = plt.subplots() 

t = np.arange(0.0, TWOPI, 0.001) 
initial_amp = .5 
s = initial_amp*np.sin(t) 
l, = plt.plot(t, s, lw=2) 

ax = plt.axis([0,TWOPI,-1,1]) 

axamp = plt.axes([0.25, .03, 0.50, 0.02]) 
# Slider 
samp = Slider(axamp, 'Amp', 0, 1, valinit=initial_amp) 

# Animation controls 
is_manual = False # True if user has taken control of the animation 
interval = 100 # ms, time between animation frames 
loop_len = 5.0 # seconds per loop 
scale = interval/1000/loop_len 

def update_slider(val): 
    global is_manual 
    is_manual=True 
    update(val) 

def update(val): 
    # update curve 
    l.set_ydata(val*np.sin(t)) 
    # redraw canvas while idle 
    fig.canvas.draw_idle() 

def update_plot(num): 
    global is_manual 
    if is_manual: 
     return l, # don't change 

    val = (samp.val + scale) % samp.valmax 
    samp.set_val(val) 
    is_manual = False # the above line called update_slider, so we need to reset this 
    return l, 

def on_click(event): 
    # Check where the click happened 
    (xm,ym),(xM,yM) = samp.label.clipbox.get_points() 
    if xm < event.x < xM and ym < event.y < yM: 
     # Event happened within the slider, ignore since it is handled in update_slider 
     return 
    else: 
     # user clicked somewhere else on canvas = unpause 
     global is_manual 
     is_manual=False 

# call update function on slider value change 
samp.on_changed(update_slider) 

fig.canvas.mpl_connect('button_press_event', on_click) 

ani = animation.FuncAnimation(fig, update_plot, interval=interval) 

plt.show() 

Die wichtigste Änderung ist die Zugabe der update_plot-Funktion, die eine FuncAnimation machen verwendet wird, in die vorletzte Zeile. Die Animation wird ausgehend vom zuletzt eingestellten Schieberegler erhöht.

Die Variable is_manual verfolgt, wenn der Benutzer auf den Schieberegler geklickt hat. Nachdem der Benutzer darauf geklickt hat, wird die Variable auf True gesetzt und die Animation aktualisiert das Diagramm nicht mehr.

Zum Fortsetzen der Animation habe ich eine on_click-Funktion hinzugefügt, die is_manual = False festlegt, wenn der Benutzer irgendwo auf der Leinwand ANDERE als den Schieberegler klickt.

Da dies ein schnell und schmutzig Skript ist, habe ich Variablen als global, aber Sie könnten es leicht in eine richtige Klasse schreiben.

Beachten Sie, dass samp.set_val implizit ruft die update_slider Funktion aufrufen, die auch aufgerufen, wenn der Benutzer direkt auf den Schieberegler klickt, so haben wir is_manual in der update_plot Funktion zurückgesetzt.

1

Sie können den Code von this answer mit einem Schieberegler anpassen.

import numpy as np 
import matplotlib.pyplot as plt 
from matplotlib.animation import FuncAnimation 
import mpl_toolkits.axes_grid1 
import matplotlib.widgets 

class Player(FuncAnimation): 
    def __init__(self, fig, func, frames=None, init_func=None, fargs=None, 
       save_count=None, mini=0, maxi=100, pos=(0.125, 0.92), **kwargs): 
     self.i = 0 
     self.min=mini 
     self.max=maxi 
     self.runs = True 
     self.forwards = True 
     self.fig = fig 
     self.func = func 
     self.setup(pos) 
     FuncAnimation.__init__(self,self.fig, self.update, frames=self.play(), 
              init_func=init_func, fargs=fargs, 
              save_count=save_count, **kwargs)  

    def play(self): 
     while self.runs: 
      self.i = self.i+self.forwards-(not self.forwards) 
      if self.i > self.min and self.i < self.max: 
       yield self.i 
      else: 
       self.stop() 
       yield self.i 

    def start(self): 
     self.runs=True 
     self.event_source.start() 

    def stop(self, event=None): 
     self.runs = False 
     self.event_source.stop() 

    def forward(self, event=None): 
     self.forwards = True 
     self.start() 
    def backward(self, event=None): 
     self.forwards = False 
     self.start() 
    def oneforward(self, event=None): 
     self.forwards = True 
     self.onestep() 
    def onebackward(self, event=None): 
     self.forwards = False 
     self.onestep() 

    def onestep(self): 
     if self.i > self.min and self.i < self.max: 
      self.i = self.i+self.forwards-(not self.forwards) 
     elif self.i == self.min and self.forwards: 
      self.i+=1 
     elif self.i == self.max and not self.forwards: 
      self.i-=1 
     self.func(self.i) 
     self.slider.set_val(self.i) 
     self.fig.canvas.draw_idle() 

    def setup(self, pos): 
     playerax = self.fig.add_axes([pos[0],pos[1], 0.64, 0.04]) 
     divider = mpl_toolkits.axes_grid1.make_axes_locatable(playerax) 
     bax = divider.append_axes("right", size="80%", pad=0.05) 
     sax = divider.append_axes("right", size="80%", pad=0.05) 
     fax = divider.append_axes("right", size="80%", pad=0.05) 
     ofax = divider.append_axes("right", size="100%", pad=0.05) 
     sliderax = divider.append_axes("right", size="500%", pad=0.07) 
     self.button_oneback = matplotlib.widgets.Button(playerax, label=ur'$\u29CF$') 
     self.button_back = matplotlib.widgets.Button(bax, label=ur'$\u25C0$') 
     self.button_stop = matplotlib.widgets.Button(sax, label=ur'$\u25A0$') 
     self.button_forward = matplotlib.widgets.Button(fax, label=ur'$\u25B6$') 
     self.button_oneforward = matplotlib.widgets.Button(ofax, label=ur'$\u29D0$') 
     self.button_oneback.on_clicked(self.onebackward) 
     self.button_back.on_clicked(self.backward) 
     self.button_stop.on_clicked(self.stop) 
     self.button_forward.on_clicked(self.forward) 
     self.button_oneforward.on_clicked(self.oneforward) 
     self.slider = matplotlib.widgets.Slider(sliderax, '', 
               self.min, self.max, valinit=self.i) 
     self.slider.on_changed(self.set_pos) 

    def set_pos(self,i): 
     self.i = int(self.slider.val) 
     self.func(self.i) 

    def update(self,i): 
     self.slider.set_val(i) 


### using this class is as easy as using FuncAnimation:    

fig, ax = plt.subplots() 
x = np.linspace(0,6*np.pi, num=100) 
y = np.sin(x) 

ax.plot(x,y) 
point, = ax.plot([],[], marker="o", color="crimson", ms=15) 

def update(i): 
    point.set_data(x[i],y[i]) 

ani = Player(fig, update, maxi=len(y)-1) 

plt.show() 

enter image description here

Verwandte Themen