Obsah
1. Knihovna Bokeh: zobrazení většího množství grafů, interaktivní prvky na stránce s grafy
2. Glyfy (tvary) použité pro vykreslení bodů v grafu
3. Vykreslení průběhu parametrické funkce pomocí metody scatter
5. Vykreslení Lorenzova atraktoru v 2D grafu
6. Zobrazení dvou grafů na jediné stránce
7. Zobrazení tří grafů umístěných do mřížky
8. Explicitní specifikace velikosti grafů umístěných do mřížky
9. Specifikace průhlednosti vykreslovaných „stop“
10. Změna barvy jednotlivých bodů (stop) v grafu
11. Funkce s průběhem vykresleným skutečnými body
13. Interaktivní ovládací prvky na stránce s grafem
14. Příklad jednoduchého interaktivního prvku – tlačítka
15. Modifikace grafu s využitím ovládacích prvků
16. Přidání ovládacího prvku pro výběr barvy
17. Přidání ovládacího prvku pro výběr velikosti vykreslovaných „stop“
18. Přidání ovládacího prvku pro výběr průhlednosti vykreslovaných „stop“
19. Repositář s demonstračními příklady
1. Knihovna Bokeh: zobrazení většího množství grafů, interaktivní prvky na stránce s grafy
Ve druhém článku o knihovně Bokeh si nejdříve popíšeme, jakým způsobem je možné do plochy stránky vložit větší množství grafů. Tyto grafy mohou být na stránku umístěny vedle sebe (tedy vlastně na jeden řádek), pod sebe (v jednom sloupci), nebo je možné grafy umístit do pomyslné mřížky. Následně si ukážeme, jak se do stránky s grafy přidávají různé interaktivní ovládací prvky, tedy tlačítka, výběrové boxy, posuvníky atd. A nejenom to – tyto ovládací prvky mohou přímo ovlivňovat způsob vykreslení grafů a to bez nutnosti explicitně zapisovat příslušný kód v JavaScriptu. Propojení mezi ovládacím prvkem a vybranou vlastností grafu (například barvu zobrazení) zajišťuje jediný deklarativně zapsaný řádek ve skriptu napsaném v programovacím jazyku Python.
2. Glyfy (tvary) použité pro vykreslení bodů v grafu
Pro vykreslení průběhu funkce nebo naměřených hodnot lze v knihovně Bokeh použít nejenom klasické liniové grafy, popř. grafy sloupcové, ale můžeme se pokusit o vykreslení průběhu formou jednotlivých bodů. Přitom lze styl vykreslení bodů modifikovat – nemusí se totiž jednat o pouhé pixely, ale každý bod (tedy například naměřená hodnota) může být reprezentována určitým složitějším tvarem, resp. přesněji řečeno glyfem.
Podívejme se nyní na zdrojový kód dnešního prvního demonstračního příkladu ( https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/19_styles.py), v němž je ukázáno použití deset vybraných tvarů:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np values = 50 # hodnoty na x-ové ose x = np.linspace(0, 100, values) # hodnoty na y-ové ose y1 = np.full(values, 10) y2 = np.full(values, 9) y3 = np.full(values, 8) y4 = np.full(values, 7) y5 = np.full(values, 6) y6 = np.full(values, 5) y7 = np.full(values, 4) y8 = np.full(values, 3) y9 = np.full(values, 2) y10 = np.full(values, 1) # plocha pro graf p = figure(title="Styly vykreslení", x_axis_label="x", y_axis_label="y") # vykreslení průběhů p.square_dot(x, y1, legend_label="square_dot", line_width=1, color="#f00000") p.circle(x, y2, legend_label="circle", line_width=1, color="#f00020") p.asterisk(x, y3, legend_label="asterisk", line_width=1, color="#e00040") p.circle_cross(x, y4, legend_label="circle_cross", line_width=1, color="#c00060") p.circle_dot(x, y5, legend_label="circle_dot", line_width=1, color="#a00080") p.cross(x, y6, legend_label="cross", line_width=1, color="#8000a0") p.dot(x, y7, legend_label="dot", line_width=1, color="#6000c0") p.plus(x, y8, legend_label="plus", line_width=1, color="#4000e0") p.dash(x, y9, legend_label="dash", line_width=1, color="#2000f0") p.line(x, y10, legend_label="line", line_width=1, color="#0000f0") # vykreslení grafu do plochy webové stránky show(p)
Výsledkem bude tento graf:

Obrázek 1: Deset tvarů vykreslení průběhu funkce či naměřených hodnot.
Ve druhém demonstračním příkladu, jehož zdrojový kód je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/20_styles.py, jsou použity stejné tvary, ovšem současně je nastaven nepovinný parametr line_width na hodnotu 3:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np values = 50 # hodnoty na x-ové ose x = np.linspace(0, 100, values) # hodnoty na y-ové ose y1 = np.full(values, 10) y2 = np.full(values, 9) y3 = np.full(values, 8) y4 = np.full(values, 7) y5 = np.full(values, 6) y6 = np.full(values, 5) y7 = np.full(values, 4) y8 = np.full(values, 3) y9 = np.full(values, 2) y10 = np.full(values, 1) # plocha pro graf p = figure(title="Styly vykreslení", x_axis_label="x", y_axis_label="y") # vykreslení průběhů p.square_dot(x, y1, legend_label="square_dot", line_width=3, color="#f00000") p.circle(x, y2, legend_label="circle", line_width=3, color="#f00020") p.asterisk(x, y3, legend_label="asterisk", line_width=3, color="#e00040") p.circle_cross(x, y4, legend_label="circle_cross", line_width=3, color="#c00060") p.circle_dot(x, y5, legend_label="circle_dot", line_width=3, color="#a00080") p.cross(x, y6, legend_label="cross", line_width=3, color="#8000a0") p.dot(x, y7, legend_label="dot", line_width=3, color="#6000c0") p.plus(x, y8, legend_label="plus", line_width=3, color="#4000e0") p.dash(x, y9, legend_label="dash", line_width=3, color="#2000f0") p.line(x, y10, legend_label="line", line_width=3, color="#0000f0") # vykreslení grafu do plochy webové stránky show(p)
Výsledek bude pochopitelně vypadat odlišně:

Obrázek 2: Deset tvarů vykreslení průběhu funkce či naměřených hodnot; nastavení line_width=3.
3. Vykreslení průběhu parametrické funkce pomocí metody scatter
Připomeňme si nyní, jakým způsobem jsme vykreslili nějakou parametrickou křivku, například spirálu. Pro vykreslení jsme prozatím použili metodu line, která jednotlivé body propojila úsečkami:
... ... ... # plocha pro graf p = figure(title="Spiral", x_axis_label='x', y_axis_label='y') # vykreslení průběhu p.line(y1, y2, line_width=2, color="blue")
V případě, že je bodů velké množství, je výhodnější použít metodu scatter, která namísto úseček vykreslí nepropojené „stopy“. Volání této metody je podobné volání metody line, ovšem musíme si dát pozor na to, že se změnila jména některých pojmenovaných parametrů:
# vykreslení průběhu p.scatter(x, y, size=5, color="blue")
Výsledek může vypadat následovně:

Obrázek 3: Graf parametrické funkce vykreslený metodou scatter.
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/21_scatter.py
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # vykreslení průběhu funkce sin # počet bodů na spirále points = 150 # úhel v polárním grafu theta = np.linspace(0.01, 8 * np.pi, points) # koeficient spirály k = 0.15 # funkce: vzdálenost od středu radius = np.exp(k * theta) x = radius * np.sin(theta) y = radius * np.cos(theta) # plocha pro graf p = figure(title="Spiral", x_axis_label="x", y_axis_label="y") # vykreslení průběhu p.scatter(x, y, size=5, color="blue") # vykreslení grafu do plochy webové stránky show(p)
4. Lorenzův atraktor
Poměrně vděčným příkladem funkce, kterou lze zobrazit rozličným způsobem (trojice grafů, graf v 3D prostoru atd.) je dynamický systém s takzvaným podivným atraktorem, který je nazvaný Lorenzův atraktor podle svého objevitele. Tento systém sestávající ze tří dynamických rovnic použil Edward Lorenz v roce 1963 při simulaci vývoje počasí (resp. ve velmi zjednodušeném modelu počasí). Na tomto systému byla také numericky a analyticky ověřena velká citlivost na počáteční podmínky (někdy také nazývaná „motýlí efekt“). Pro upřesnění je však nutné říci, že při simulaci na počítači vlastně získáme atraktor, jenž je periodický. Je to z toho důvodu, že pro zobrazení číselných hodnot je použito konečného počtu bitů, z toho nutně vyplývá, že se po určitém počtu kroků (který je však obrovský, takže tento jev mnohdy nezaregistrujeme) začne dráha Lorenzova atraktoru překrývat. V matematicky přesném modelu však tato situace nenastane, každá smyčka funkce bude mít unikátní tvar a dráhy se nebudou překrývat, pouze protínat.
Diferenciální rovnice Lorenzova atraktoru mají po převodu na diferenční tvar následující formát:
dx/dt = σ (y-x) dy/dt = x(ρ - z) - y dz/dt = xy - Βz
Takže pro iterativní (samozřejmě že nepřesný) výpočet můžeme pracovat s následujícími vztahy, které pro dostatečně malé dt vedou k výpočtu bodů tvořících Lorenzův atraktor:
xn+1=xn+(σ (y-x)) dt yn+1=yn+(x(ρ - z) - y) dt zn+1=zn+(xy - Βz) dt
Podívejme se nyní na způsob implementace této funkce v Pythonu, což je snadné:
def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s*(y - x) y_dot = r*x - y - x*z z_dot = x*y - b*z return x_dot, y_dot, z_dot
A výpočtu sekvence bodů ležících na atraktoru (resp. tvořících atraktor):
# prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0., 1., 1.05) # vlastní výpočet atraktoru (resp. bodů na něm ležících) for i in range(n-1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i+1] = x[i] + x_dot * dt y[i+1] = y[i] + y_dot * dt z[i+1] = z[i] + z_dot * dt
Vlastní zobrazení pak může probíhat mnoha způsoby, z nichž některé si ukážeme dále.
5. Vykreslení Lorenzova atraktoru v 2D grafu
Nyní se podívejme na způsob zobrazení Lorenzova atraktoru v jednoduchém 2D grafu. To vlastně znamená, že původní trojrozměrný dynamický systém zjednodušíme (ovšem nutno podotknout, že jen pro potřeby zobrazení, nikoli při výpočtu) na 2D systém. Existuje mnoho způsobů zobrazení Lorenzova atraktoru. My prozatím použijeme ten nejvíce přímočarý způsob – jednotlivé vypočtené body budou skutečně zobrazeny formou bodů. Využijeme tedy tento kód:
# plocha pro graf p = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y") # vykreslení průběhu p.scatter(x, y, size=1, color="blue")
kde x a y jsou vektory hodnot vypočtené tak, jak jsme si to vysvětlili v předchozí kapitole.

Obrázek 4: Lorenzův atraktor zobrazený na ploše webové stránky.
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/22_lorenz_attractor.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro graf p = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y") # vykreslení průběhu p.scatter(x, y, size=1, color="blue") # vykreslení grafu do plochy webové stránky show(p)
6. Zobrazení dvou grafů na jediné stránce
V případě Lorenzova atraktoru, což je trojrozměrný objekt, je vhodné použít buď 3D graf (to však prozatím neumíme), nebo zobrazit atraktor z více pohledů, například z půdorysu, nárysu a bokorysu. V takovém případě je nutné zobrazit na ploše stránky větší počet grafů. Nejjednodušší je umístění grafů pod sebe (více řádků, jediný sloupec) nebo vedle sebe (více sloupců, jediný řádek). Podívejme se nyní na způsob zobrazení dvou grafů vedle sebe. Nejprve jsou vytvořeny oba grafy:
# plocha pro první graf p1 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y") # vykreslení průběhu p1.scatter(x, y, size=1, color="blue") # plocha pro druhý graf p2 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z") # vykreslení průběhu p2.scatter(x, z, size=1, color="blue")
A následně jsou oba grafy zobrazeny:
# vykreslení grafu do plochy webové stránky show(row(p1, p2))
S tímto výsledkem:

Obrázek 5: Dva grafy zobrazené na jedné stránce (oříznuto).
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/23_row_plots.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show, row # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro první graf p1 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y") # vykreslení průběhu p1.scatter(x, y, size=1, color="blue") # plocha pro druhý graf p2 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z") # vykreslení průběhu p2.scatter(x, z, size=1, color="blue") # vykreslení grafu do plochy webové stránky show(row(p1, p2))
7. Zobrazení tří grafů umístěných do mřížky
Ve skutečnosti můžeme grafy zobrazit i do mřížky, čímž se do určité míry přibližujeme například k možnostem balíčku Lattice pro programovací jazyk R (viz též článek Základy tvorby grafů v programovacím jazyku R: knihovna lattice). Podívejme se nyní na způsob vykreslení tří pohledů na Lorenzův atraktor. Nejdříve opět vytvoříme trojici grafů:
# plocha pro první graf p1 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y") # vykreslení průběhu p1.scatter(x, y, size=1, color="blue") # plocha pro druhý graf p2 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z") # vykreslení průběhu p2.scatter(x, z, size=1, color="darkred") # plocha pro třetí graf p3 = figure(title="Lorenz attractor", x_axis_label="y", y_axis_label="z") # vykreslení průběhu p3.scatter(y, z, size=1, color="darkgreen")
A následně s využitím správce rozvržení grid tyto tři grafy umístíme na plochu stránky:
# vykreslení grafu do plochy webové stránky show(grid([[p2, p3], p1]))
S následujícím výsledkem:

Obrázek 6: Trojice grafů zobrazená na ploše stránky (zmenšeno).
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/24_grid_plots.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.layouts import grid # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro první graf p1 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y") # vykreslení průběhu p1.scatter(x, y, size=1, color="blue") # plocha pro druhý graf p2 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z") # vykreslení průběhu p2.scatter(x, z, size=1, color="darkred") # plocha pro třetí graf p3 = figure(title="Lorenz attractor", x_axis_label="y", y_axis_label="z") # vykreslení průběhu p3.scatter(y, z, size=1, color="darkgreen") # vykreslení grafu do plochy webové stránky show(grid([[p2, p3], p1]))
8. Explicitní specifikace velikosti grafů umístěných do mřížky
Mnohdy je nutné upravit velikost grafů umístěných do mřížky – v naprosté většině případů budeme chtít grafy zmenšit. To se provede velmi snadno – specifikací šířky a výšky jednotlivých grafů tak, jak je to patrné z následujícího kódu:
# plocha pro první graf p1 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y", width=300, height=300) # plocha pro druhý graf p2 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z", width=300, height=300) # plocha pro třetí graf p3 = figure(title="Lorenz attractor", x_axis_label="y", y_axis_label="z", width=300, height=300)
Výsledkem by měla být stránka, v níž jsou jednotlivé grafy vykresleny o velikosti přibližně 300×300 pixelů:

Obrázek 7: Trojice grafů, z nichž každý má velikost přibližně 300×300 pixelů.
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/25_grid_plot_sizes.py
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.layouts import grid # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro první graf p1 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y", width=300, height=300) # vykreslení průběhu p1.scatter(x, y, size=0.5, color="blue") # plocha pro druhý graf p2 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z", width=300, height=300) # vykreslení průběhu p2.scatter(x, z, size=0.5, color="darkred") # plocha pro třetí graf p3 = figure(title="Lorenz attractor", x_axis_label="y", y_axis_label="z", width=300, height=300) # vykreslení průběhu p3.scatter(y, z, size=0.5, color="darkgreen") # vykreslení grafu do plochy webové stránky show(grid([[p2, p3], p1]))
9. Specifikace průhlednosti vykreslovaných „stop“
Zejména v případě vykreslování parametrických funkcí s využitím metody splatter může být počet vykreslovaných bodů velmi velký a můžeme chtít zvýraznit míru „nahuštění“ těchto bodů. Pro tyto účely nám může pomoci změnit průhlednost vykreslování jednotlivých bodů (připomeňme si, že se vlastně nejedná o body, ale mnohdy o větší glyfy – tvary). Průhlednost se specifikuje nepovinnými parametry alpha a line_alpha tak, jak je to ukázáno na následujícím úryvku kódu. Typicky bývá průhlednost nastavena v rozsahu od 0 do 1:
# vykreslení prvního průběhu p1.scatter(x, y, size=0.5, color="blue", alpha=0.4) # vykreslení druhého průběhu p2.scatter(x, z, size=0.5, color="darkred", alpha=0.4) # vykreslení třetího průběhu p3.scatter(y, z, size=0.5, color="darkgreen", alpha=0.4)
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/26_alpha.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.layouts import grid # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro první graf p1 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y", width=300, height=300) # vykreslení průběhu p1.scatter(x, y, size=0.5, color="blue", alpha=0.4) # plocha pro druhý graf p2 = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z", width=300, height=300) # vykreslení průběhu p2.scatter(x, z, size=0.5, color="darkred", alpha=0.4) # plocha pro třetí graf p3 = figure(title="Lorenz attractor", x_axis_label="y", y_axis_label="z", width=300, height=300) # vykreslení průběhu p3.scatter(y, z, size=0.5, color="darkgreen", alpha=0.4) # vykreslení grafu do plochy webové stránky show(grid([[p2, p3], p1]))
10. Změna barvy jednotlivých bodů (stop) v grafu
Nyní se podívejme na řešení dalšího zajímavého problému a tím je obarvení každého bodu v grafu odlišnou barvou. Jedná se o poměrně často používanou techniku v případě, že je průběh zobrazované funkce (nebo naměřených hodnot) velmi složitý, s překrývajícími se větvemi atd. V knihovně Bokeh máme pro řešení tohoto problému možnost vytvořit vektor barev, přičemž počet prvků tohoto vektoru ideálně odpovídá počtu zobrazených bodů. Každý prvek „vektoru barev“ obsahuje jméno barvy, popř. trojici nebo šestici hexadecimálních cifer s RGB kódem barvy.
Můžeme se například pokusit o vytvoření gradientního přechodu mezi červenou a modrou barvou. Algoritmus je založen na vytvoření sekvence hodnot od 0 do 255 a následně o vygenerování RGB kódů barev na základě těchto hodnot:
# gradient od 0 do 255 l = np.linspace(0, 255, num=n) # barvová paleta colors = ["#%02x%02x%02x" % (255-int(value), 0, int(value)) for value in l]
Tento vektor se předá metodě scatter v nepovinném parametru color:
# vykreslení průběhu p.scatter(x, y, size=1, color=colors)
Výsledek může vypadat takto:

Obrázek 8: Modifikace barvy jednotlivých bodů v grafu.
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/27_colors.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # gradient od 0 do 255 l = np.linspace(0, 255, num=n) # barvová paleta colors = ["#%02x%02x%02x" % (255-int(value), 0, int(value)) for value in l] # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro graf p = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y") # vykreslení průběhu p.scatter(x, y, size=1, color=colors) # vykreslení grafu do plochy webové stránky show(p)
11. Funkce s průběhem vykresleným skutečnými body
Pokud se budeme snažit vykreslit „přesnější“ průběh Lorenzova atraktoru, může být jedním možným řešením razantní zvýšení počtu zobrazených bodů. Můžeme tedy změnit tuto hodnotu:
# celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000
na mnohem vyšší hodnotu:
# celkový počet vypočtených bodů na Lorenzově atraktoru n = 100000
Problém ovšem spočívá v tom, že vykreslení tolika bodů pomocí metody scatter může být dosti pomalé:
# vykreslení průběhu p.scatter(x, y, size=1, color=colors)
Urychlení dosáhneme tak, že namísto metody scatter zavoláme metodu dot, jejíž vykreslovací algoritmus je rychlejší:
# vykreslení průběhu p.dot(x, y, line_color=colors, line_alpha=0.2)
Výsledek by mohl vypadat následovně:

Obrázek 10: Graf vykreslený metodou dot namísto scatter.
Zdrojový kód takto upraveného demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/28_colors.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 100000 # gradient od 0 do 255 l = np.linspace(0, 255, num=n) # barvová paleta colors = ["#%02x%02x%02x" % (255-int(value), 0, int(value)) for value in l] # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro graf p = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="y") # vykreslení průběhu p.dot(x, y, line_color=colors, line_alpha=0.2) # vykreslení grafu do plochy webové stránky show(p)
12. Vykreslení heatmapy
V případě, že je nutné nějakým způsobem zobrazit funkci typu z=f(x,y), je nutné použít buď nějakou formu 3D grafu nebo takzvanou teplotní mapu (heat map, heatmap). I ta je v knihovně Bokeh podporována, o čemž se můžeme snadno přesvědčit. Nejprve vytvoříme seznam barev, které budou zobrazeny v teplotní mapě o rozměrech 3×3 prvky:
# barvy jednotlivých čtverečků colors = [ "#0B486B", "#79BD9A", "#CFF09E", "#79BD9A", "#0B486B", "#79BD9A", "#CFF09E", "#79BD9A", "#0B486B" ]
Následně je nutno vytvořit n-tice či seznamy s popisem buněk na x-ové ose a y-ové ose. Pro jednoduchost ponecháme popis na obou osách stejný, tj. hodnoty „a“ až „c“:
factors = ["a", "b", "c"] x = ["a"]*3 + ["b"]*3 + ["c"]*3 y = factors*3
A nakonec postačuje specifikovat popisy os i vlastní zdroj dat:
# plocha pro graf p = figure(title="Categorical Heatmap", tools="hover", toolbar_location=None, x_range=factors, y_range=factors) # vykreslení heatmapy p.rect(x, y, color=colors, width=1, height=1)
Výsledkem bude heat mapa s 3×3 prvky:

Obrázek 11: Heat mapa.
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/29_heatmap.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show factors = ["a", "b", "c"] x = ["a"]*3 + ["b"]*3 + ["c"]*3 y = factors*3 print(factors) print(x) print(y) # barvy jednotlivých čtverečků colors = [ "#0B486B", "#79BD9A", "#CFF09E", "#79BD9A", "#0B486B", "#79BD9A", "#CFF09E", "#79BD9A", "#0B486B" ] # plocha pro graf p = figure(title="Categorical Heatmap", tools="hover", toolbar_location=None, x_range=factors, y_range=factors) # vykreslení heatmapy p.rect(x, y, color=colors, width=1, height=1) # vykreslení grafu do plochy webové stránky show(p)
13. Interaktivní ovládací prvky na stránce s grafem
Knihovna Bokeh se sice může podobat již několikrát zmíněné knihovně Matplotlib, ovšem ve skutečnosti můžeme mezi oběma knihovnami nalézt velké množství rozdílů. Jedním z nich je fakt, že Bokeh umožňuje přidat na plochu webové stránky s grafem i interaktivní ovládací prvky. A navíc je možné tyto prvky svázat s některými vlastnostmi grafu, takže je například snadné přidat ovládací prvek určený pro výběr barvy z barvové palety, přičemž uživatelem zvolená barva bude přímo použita v grafu. Nebo můžeme na stránku přidat posuvník (slider) a svázat jeho hodnotu (například) s tloušťkou vykreslovaných čar, průhledností atd. Propojení mezi ovládacími prvky a jednotlivými atributy grafu je přitom provedeno bez nutnosti explicitního zápisu programového kódu, který toto propojení provede – a vzhledem k tomu, že se jedná o webovou stránku, by musel být tento kód zapsán v JavaScriptu (kterému jsme se doposud vyhýbali).
14. Příklad jednoduchého interaktivního prvku – tlačítka
Podívejme se nyní na základní způsob práce s interaktivními ovládacími prvky, které je možné vložit na plochu webové stránky. Vložíme na stránku tlačítko neboli ovládací prvek typu button:
# vytvoření ovládacího prvku button = Button(label="Foo", button_type="success")
Následně je možné propojit událost „stisk tlačítka“ s nějakou akcí. V knihovně Bokeh se takové propojení zapíše následujícím způsobem:
# specifikace handleru button.js_on_click(CustomJS(code="console.log('button: click!', this.toString())"))
Tento příkaz způsobí, že se po stisku tlačítka přidá do logu webového prohlížeče (další) zpráva o stisku tlačítka.
V posledním kroku se tlačítko zobrazí:
# zobrazení ovládacího prvku show(button)

Obrázek 12: Webová stránka s tlačítkem vytvořená knihovnou Bokeh.
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/30_button.py:
# naimportujeme vybrané funkce z knihovny `bokeh.io` from bokeh.io import show # naimportujeme vybrané funkce z knihovny `bokeh.models` from bokeh.models import Button, CustomJS # vytvoření ovládacího prvku button = Button(label="Foo", button_type="success") # specifikace handleru button.js_on_click(CustomJS(code="console.log('button: click!', this.toString())")) # zobrazení ovládacího prvku show(button)
15. Modifikace grafu s využitím ovládacích prvků
V navazujících třech kapitolách si ukážeme, jakým způsobem je možné na plochu stránky přidat takové ovládací prvky, jejichž modifikací může uživatel měnit například způsob zobrazení grafu, nastavovat vstupní data atd. Prozatím budeme všechny operace provádět s grafem Lorenzova atraktoru, ovšem jak uvidíme příště, mnohem větší možnosti budeme mít ve chvíli, kdy budou zdrojová data popsána datovými rámci (data frames).
16. Přidání ovládacího prvku pro výběr barvy
Do grafu nyní přidáme ovládací prvek určený pro výběr barvy. Jedná se o prvek pojmenovaný přímočaře Color Picker:
# výběr barvy picker = ColorPicker(title="Color")
Následně propojíme barvu vybranou v tomto prvku (atribut „color“) s atributem grafu nazvaným „line_color“. Pouze toto propojení, resp. tento řádek zajistí, že barva vykreslení bude plně pod kontrolou uživatele:
picker.js_link("color", scatter.glyph, "line_color")
Při vykreslení nesmíme zapomenout na to, že kromě grafu musíme zobrazit i příslušný ovládací prvek:
# vykreslení grafu do plochy webové stránky show(column(p, picker))
Webová stránka vykreslená a zobrazená knihovnou Bokeh by měla vypadat takto:

Obrázek 13: Interaktivní ovládací prvek určený pro výběr vykreslovaných bodů (resp. stop) v grafu.
Zdrojový kód tohoto demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/31_select_color.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` i dalších knihoven from bokeh.plotting import figure, show from bokeh.layouts import column from bokeh.models import ColorPicker # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro graf p = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z", width=500, height=500) # vykreslení průběhu scatter = p.scatter(x, z, size=1, color="blue") # výběr barvy picker = ColorPicker(title="Color") picker.js_link("color", scatter.glyph, "line_color") # vykreslení grafu do plochy webové stránky show(column(p, picker))
17. Přidání ovládacího prvku pro výběr velikosti vykreslovaných „stop“
V grafu, v němž je průběh funkce vykreslen metodou splatter, lze s využitím vhodných ovládacích prvků modifikovat i velikost vykreslovaných „stop“. Pro tento účel použijeme posuvník neboli slider, jehož hodnotu (interaktivně vybranou uživatelem změnou pozice posuvníku) navážeme na vlastnost size objektu scatter.glyph. Programový kód se tedy rozšíří o následující trojici řádků:
# posuvník slider = Slider(start=0, end=10, value=1, step=.1, title="Splatter size") slider.js_link("value", scatter.glyph, "size")
Slider je navíc nutné přidat na plochu webové stránky:
# vykreslení grafu do plochy webové stránky show(column(p, picker, slider))
Výsledek:

Obrázek 14: Další interaktivní ovládací prvek přidaný na stránku s grafem.
Zdrojový kód dnešního předposledního demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/32_select_color_size.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` i dalších knihoven from bokeh.plotting import figure, show from bokeh.layouts import column from bokeh.models import ColorPicker, Slider # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro graf p = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z", width=500, height=500) # vykreslení průběhu scatter = p.scatter(x, z, size=1, color="blue") # výběr barvy picker = ColorPicker(title="Color") picker.js_link("color", scatter.glyph, "line_color") # posuvník slider = Slider(start=0, end=10, value=1, step=.1, title="Splatter size") slider.js_link("value", scatter.glyph, "size") # vykreslení grafu do plochy webové stránky show(column(p, picker, slider))
18. Přidání ovládacího prvku pro výběr průhlednosti vykreslovaných „stop“
V dnešním posledním demonstračním příkladu přidáme na plochu stránky ovládací prvek určený pro změnu průhlednosti vykreslovaných „stop“. Nejprve nepatrně pozměníme parametry předané do metody scatter. Budeme explicitně specifikovat průhlednost vykreslení „stop“ nastavenou na 50%:
# vykreslení průběhu scatter = p.scatter(x, z, size=1, color="blue", alpha=0.5)
Následně vytvoříme posuvník s výchozí hodnotou nastavenou právě na 0,5 (tedy na oněch 50%) a s povoleným rozsahem hodnot od 0 do 1 (tedy od 0% do 100%). Hodnota zvolená na posuvníku je následně propojena s atributem line_alpha grafu scatter. Deklarace posuvníku i propojení s grafem vypadá následovně:
# posuvník pro změnu průblednosti slider_alpha = Slider(start=0, end=1, value=0.5, step=.01, title="Alpha") slider_alpha.js_link("value", scatter.glyph, "line_alpha")
A konečně jak graf, tak i všechny (nyní již tři) ovládací prvky přidáme na plochu webové stránky:
# vykreslení grafu do plochy webové stránky show(column(p, picker, slider_alpha, slider_size))
S tímto výsledkem:

Obrázek 15: Graf s trojicí ovládacích prvků vložených na plochu webové stránky.
Zdrojový kód dnešního posledního demonstračního příkladu je dostupný na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/33_select_color_size_alpha.py. Vypadá následovně:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` i dalších knihoven from bokeh.plotting import figure, show from bokeh.layouts import column from bokeh.models import ColorPicker, Slider # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # funkce pro výpočet dalšího bodu Lorenzova atraktoru def lorenz(x, y, z, s=10, r=28, b=2.667): x_dot = s * (y - x) y_dot = r * x - y - x * z z_dot = x * y - b * z return x_dot, y_dot, z_dot # krok (změna času) dt = 0.01 # celkový počet vypočtených bodů na Lorenzově atraktoru n = 10000 # prozatím prázdné pole připravené pro výpočet x = np.zeros((n,)) y = np.zeros((n,)) z = np.zeros((n,)) # počáteční hodnoty x[0], y[0], z[0] = (0.0, 1.0, 1.05) # vlastní výpočet atraktoru for i in range(n - 1): x_dot, y_dot, z_dot = lorenz(x[i], y[i], z[i]) x[i + 1] = x[i] + x_dot * dt y[i + 1] = y[i] + y_dot * dt z[i + 1] = z[i] + z_dot * dt # plocha pro graf p = figure(title="Lorenz attractor", x_axis_label="x", y_axis_label="z", width=500, height=500) # vykreslení průběhu scatter = p.scatter(x, z, size=1, color="blue", alpha=0.5) # výběr barvy picker = ColorPicker(title="Color") picker.js_link("color", scatter.glyph, "line_color") # posuvník pro změnu průblednosti slider_alpha = Slider(start=0, end=1, value=0.5, step=.01, title="Alpha") slider_alpha.js_link("value", scatter.glyph, "line_alpha") # posuvník pro změnu velikosti slider_size = Slider(start=0, end=10, value=1, step=.1, title="Splatter size") slider_size.js_link("value", scatter.glyph, "size") # vykreslení grafu do plochy webové stránky show(column(p, picker, slider_alpha, slider_size))
19. Repositář s demonstračními příklady
Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro programovací jazyk Python 3 byly uloženy do Git repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem stále velmi malý, dnes má velikost zhruba několik desítek kilobajtů), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- bokeh na GitHubu
https://github.com/bokeh/bokeh - First steps 1: Creating a line chart
https://docs.bokeh.org/en/latest/docs/first_steps/first_steps1.html - Python Bokeh tutorial – Interactive Data Visualization with Bokeh
https://www.geeksforgeeks.org/python-bokeh-tutorial-interactive-data-visualization-with-bokeh/ - The R Project for Statistical Computing
https://www.r-project.org/ - An Introduction to R
https://cran.r-project.org/doc/manuals/r-release/R-intro.pdf - R (programming language)
https://en.wikipedia.org/wiki/R_(programming_language) - Graphics, ggplot2
http://r4stats.com/examples/graphics-ggplot2/ - Seriál Programovací jazyk Julia
https://www.root.cz/serialy/programovaci-jazyk-julia/ - Plotly
https://plotly.com/ - pyecharts
https://github.com/pyecharts/pyecharts/blob/master/README.en.md - Tvorba grafů v Jupyter Notebooku s využitím knihovny Matplotlib
https://www.root.cz/clanky/tvorba-grafu-v-jupyter-notebooku-s-vyuzitim-knihovny-matplotlib/ - Lorenzův atraktor
https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-vi/#k02 - Lorenzův atraktor
https://www.root.cz/clanky/fraktaly-v-pocitacove-grafice-iii/#k03 - Lorenz system
https://en.wikipedia.org/wiki/Lorenz_system