Interaktiivsed ja animeeritud graafikud¶

Valter Kiisk
TÜ Füüsika Instituut
Viimati muudetud: 27.06.2024
$\renewcommand{\vec}{\boldsymbol}$ $\newcommand{\erf}{\mathop{\rm erf}\nolimits}$ $\newcommand{\mod}{\mathop{\rm mod}\nolimits}$
In [1]:
import numpy as np
from math import pi
from matplotlib.pyplot import *
In [2]:
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:

In [17]:
%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()
puutuja 1
No description has been provided for this image

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 meetod format_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:

In [18]:
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.

In [19]:
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);
puutuja 2
No description has been provided for this image

Võrreldava tulemuse saab ka staatilise graafikuga (%matplotlib inline), aga sel juhul tuleb iga kord terve graafik tekitada uuesti.

In [20]:
%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:

In [21]:
%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})
)
In [22]:
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.

In [23]:
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):

In [131]:
%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.

Äsjase ülesande animatsioon: MP4 video ja GIF.