Interaktiivsed ja animeeritud graafikud¶
TÜ Füüsika Instituut
import numpy as np
from math import pi
from matplotlib.pyplot import *
style.use({
'font.size': 11,
'figure.dpi': 100,
'lines.markersize': 5,
'lines.markeredgewidth': 0
})
Interaktiivne graafik¶
Kui kasutada %matplotlib inline
(vaikimisi) asemel %matplotlib widget
, saame interaktiivse graafiku otse Jupyteri keskkonnas. Selline graafik kuvab hiirekursori koordinaate ja sisaldab vahendeid graafiku suumimiseks, liigutamiseks ja salvestamiseks. %matplotlib widget
eeldab, et on installeeritud ipympl. Vanemas Jupyter Notebook keskkonnas (<v7) oli kasutusel direktiiv %matplotlib notebook
.
Mooduli matplotlib.pyplot
käsud figure
, plot
, text
, jne — nagu ka klasside Figure
, Axes
, jne vastavad meetodid — tagastavad viida loodud objektile. Selle vahendusel saab hiljem muuta interaktiivse graafiku sisu:
%matplotlib widget
funktsioon = lambda x: np.sin(2 * pi * x)
tuletis = lambda x: 2 * pi * np.cos(2 * pi * x)
x = np.linspace(0, 1, 200)
x0 = 0.5
tõus = tuletis(x0)
fig = figure('puutuja 1', clear=True, figsize=(5,3))
fig.canvas.header_visible = False
plot(x, funktsioon(x), 'r-')
punkt, = plot((x0,), (funktsioon(x0),), 'bo')
puutuja, = plot(x, funktsioon(x0) + (x - x0) * tõus, 'b-')
xlim(0, 1)
ylim(-1.1, 1.1)
grid()
lipik = text(0.75, 0.85, 'tõus=%.2f' % tõus)
tight_layout()
gca().format_coord = lambda x,y: f'x={x:.3f}, y={y:.3f}'
show()
Siin on ridamisi uusi elemente:
- Interaktiivsele graafikule on soovitatav anda nimi või number, et saaks taaskasutada olemasolevat joonist (muidu lahtri taaskäivitamisel joonise eelmine versioon jääb arvuti mällu).
fig.canvas.header_visible = False
peidab päise (seal kuvataks automaatselt joonise nimi)- Hiirekursoriga joonise peale liikudes ilmub vasakul nähtavale tööriistariba, kus on vahendid graafiku liigutamiseks, suumimiseks ja salvestamiseks.
- Ãœhtlasi joonise jaluses kuvatakse hiirekursori koordinaadid (v.a. kui on
fig.canvas.footer_visible = False
). Formaadi määrab teljestiku meetodformat_coord
, mille saab vajadusel üle defineerida.
Nüüd seni kuni see graafik on veel "elav" ehk pole suletud käsuga close('puutuja 1')
, saab mitmesuguste meetodite (set_data
jt) kaudu muuta selle sisu:
x0 = 0.6
tõus = tuletis(x0)
punkt.set_data((x0,), (funktsioon(x0),))
puutuja.set_ydata( funktsioon(x0) + (x - x0) * tõus )
lipik.set_text( 'tõus=%.2f' % tõus )
Et parameetreid graafiliselt muuta, tuleb kasutada graafilise kasutajaliidese elemente (widgets) moodulist ipywidgets
. Mugavaim element on liugur (slider), mille tekitab (vastavalt andmetüübile) IntSlider
või FloatSlider
. Aga võimalik on tekitada ka tekstiväli (Text
), rippmenüü (Dropdown
), loend (Select
), lüliti (ToggleButton
või Checkbox
), raadionupud (RadioButtons
), jne (vt täielikku nimekirja). Lihtsamal juhul saab interaktiivse seose arvutusoperatsiooni ja sisestuselemendi vahel luua käsuga ipywidgets.interact
.
from ipywidgets import interact, FloatSlider
fig = figure('puutuja 2', clear=True, figsize=(5,3))
fig.canvas.header_visible = False
plot(x, funktsioon(x), 'r-')
punkt, puutuja = plot((), (), 'bo', (), (), 'b-')
xlim(0, 1)
ylim(-1.1, 1.1)
grid()
lipik = text(0.75, 0.85, '')
tight_layout()
show()
def uuenda(x0):
tõus = tuletis(x0)
punkt.set_data((x0,), (funktsioon(x0),))
puutuja.set_data(x, funktsioon(x0) + (x - x0) * tõus)
lipik.set_text( 'tõus=%.2f' % tõus )
slider = FloatSlider(min=0, max=1, step=0.01, value=0.5, description='x0')
interact(uuenda, x0=slider);
Võrreldava tulemuse saab ka staatilise graafikuga (%matplotlib inline
), aga sel juhul tuleb iga kord terve graafik tekitada uuesti.
%matplotlib inline
def graafik(x0):
tõus = tuletis(x0)
figure(figsize=(5,3))
plot(x, funktsioon(x), 'r-')
plot((x0,), (funktsioon(x0),), 'bo')
plot(x, funktsioon(x0) + (x - x0) * tõus, 'b-')
xlim(0, 1)
ylim(-1.1, 1.1)
grid()
text(0.75, 0.85, 'tõus=%.2f' % tõus)
tight_layout()
show()
slider = FloatSlider(min=0, max=1, step=0.01, value=0.5, description='x0')
interact(graafik, x0=slider);
Elementide paigutamine üksteise kõrvale või nende kujunduse muutmine nõuab juba rohkem programmeerimist:
%matplotlib widget
from ipywidgets import interactive_output, HBox
from IPython.display import display
fig = figure('tuiklemine', clear=True, figsize=(6,3))
fig.canvas.header_visible = False
komp1, = plot((), (), 'g-', linewidth=1)
komp2, = plot((), (), 'r-', linewidth=1)
summa, = plot((), (), 'b-', linewidth=2)
xlim(0, 8)
ylim(-2, 2)
grid()
tight_layout()
show()
t = np.linspace(0, 8, 800)
def uuenda(sagedus, faas):
x1 = np.sin(2*pi*t)
x2 = np.sin(2*pi*sagedus*t + faas)
komp1.set_data(t, x1)
komp2.set_data(t, x2)
summa.set_data(t, x1+x2)
slider1 = FloatSlider(min=0, max=2, step=0.02, value=1, description='sagedus')
slider2 = FloatSlider(min=0, max=pi, step=0.01*pi, value=0, description='faasinihe')
display(
HBox( (slider1, slider2) ),
interactive_output(uuenda, {'sagedus': slider1, 'faas': slider2})
)
import scipy.signal
from ipywidgets import IntSlider, Layout
t = np.linspace(0, 2.5, 800)
wt = 2*pi*t
f = scipy.signal.square(wt, 0.5)
fourier = lambda k: 4 * np.sin((2 * k - 1) * wt) / (pi * (2 * k - 1))
fig = figure('fourier 1', clear=True, figsize=(6,3))
fig.canvas.header_visible = False
plot(t, f, 'r-', linewidth=1)
summa, = plot((), (), 'b-', linewidth=2)
xlim(0, 2.5)
ylim(-1.3, 1.3)
grid()
tight_layout()
show()
def uuenda(n):
x = np.zeros( len(t) )
for k in range(1, n):
x += fourier(k)
summa.set_data(t, x)
slider = IntSlider(min=2, max=30, value=2,
description='harmooniliste komponentide arv',
style = {'description_width': 'initial'},
layout=Layout(width='90%'))
interact(uuenda, n=slider);
Animeerimine¶
Lihtne liides graafiku animeerimiseks on matplotlib.animation.FuncAnimation
.
from matplotlib.animation import FuncAnimation
fig = figure('fourier 2', clear=True, figsize=(6,3))
fig.canvas.header_visible = False
plot(t, f, 'r-', linewidth=1)
summa, = plot((), (), 'b-', linewidth=2)
xlim(0, 2.5)
ylim(-1.3, 1.3)
grid()
arv = text(0.75, 0.95, '', ha='center', va='top')
tight_layout()
show()
def uuenda(kaader):
n = kaader + 2
x = np.zeros( len(t) )
for k in range(1, n):
x += fourier(k)
summa.set_data(t, x)
arv.set_text('n=' + str(n))
return summa, arv
ani = FuncAnimation(fig, uuenda, frames=20, interval=500, blit=True)
Animatsiooni võib salvestada omaette videofailina. Seda saab teha näiteks järgmisel viisil. Esmalt tuleb käsuga savefig
salvestada iga kaader eraldi (rastergraafika) faili, kus failinimes sisalduks kaadri järjekorranumber (näiteks kaader000.png
, kaader001.png
, jne):
%matplotlib inline
figure(figsize=(8,4), dpi=150)
for kaader in range(20):
n = kaader + 2
x = np.zeros( len(t) )
for k in range(1, n):
x += fourier(k)
plot(t, f, 'r-', linewidth=0.8)
plot(t, x, 'b-')
xlim(0, 2.5)
ylim(-1.3, 1.3)
grid()
text(0.75, 0.95, 'n=' + str(n), ha='center', va='top')
xlabel('aeg')
ylabel('signaal')
tight_layout(pad=0.2)
savefig('kaadrid/kaader%03d.png' % kaader)
clf() # pärast salvestamist kustutame graafiku
<matplotlib.figure.Figure at 0x33ed2c1e48>
Seejärel tuleb kasutada mõnda programmi, mis võimaldaks kompileerida kõik kaadrid üheks videoks või animeeritud GIF-failiks. Näiteks programm ffmpeg teeb selle töö käsuga:
ffmpeg -r 10 -i kaader%03d.png -b:v 2M animation.mp4
Siin kaadrisagedust (-r
) ja bitikiirust (-b:v
) võib muuta vastavalt vajadusele. Veidi põhjalikum näide, kus täpsustatakse videokoodek ja antakse erinevad kaadrisagedused sisend- ja väljundvoo jaoks:
ffmpeg -r 2 -i kaader%03d.png -c:v libx264 -pix_fmt yuv420p -r 10 animation.mp4
Lihtsaim käsk sama programmiga GIF animatsiooni loomiseks:
ffmpeg -r 2 -i kaader%03d.png animation.gif
GIF animatsioonide tegemiseks leidub ka veebipõhiseid vahendeid.