Obsah
2. Témata ovlivňující barvy i styl vykreslení grafů
3. Ukázky podporovaných témat stylů grafů
4. Graf obsahující data ve formě rastrového obrázku
5. Rastrová data s proměnným počtem sloupců a řádků
7. Použití externí barvové palety (LUT)
8. Rastrové obrázky a interní barvové palety Bokehu
9. Zobrazení obrázku s interní barvovou paletou Bokehu
10. Popisky na x-ové ose sloupcového grafu
11. Seřazení hodnot podle zadaného kritéria
12. Použití struktury ColumnDataSource
13. Zvýraznění hodnot ve sloupcovém grafu s využitím různých barev sloupců
14. Interaktivní přepočet grafů
15. Přidání posuvníku pro interaktivní změnu amplitudy jednoho průběhu v grafu
16. Data pro vykreslení grafu dostupná ve formě ColumnDataSource
17. Temná strana Bokehu – přepočet dat v JavaScriptu
19. Repositář s demonstračními příklady
1. Knihovna Bokeh: dokončení
Ve třetím a současně i posledním článku o knihovně Bokeh, který navazuje na články [1] a [2] si ukážeme způsob výběru a použití témat (ovlivňujících barvy, fonty apod. zobrazených grafů), vykreslení dat reprezentovaných formou matic (N-dimenzionální pole knihovny NumPy) nebo formou rastrových obrázků, seřazení hodnot podle zvolených kritérií i kooperaci mezi částí psanou v Pythonu a částí naprogramovanou v JavaScriptu. Na závěr si řekneme, kde je vhodné Bokeh použít a v jakých oblastech naopak tato knihovna neposkytuje požadovanou funkcionalitu a je lepší ji nahradit odlišnou technologií (Jupyter Notebook, Plotly atd.).
2. Témata ovlivňující barvy i styl vykreslení grafů
Všechny grafy, které jsme si až doposud ukazovali, byly zobrazeny s využitím standardního tématu, které ovlivňuje především barvy použité nejenom v samotném grafu, ale i všemi dalšími ovládacími prvky. Kromě toho téma ovlivňuje i použité fonty – a teoreticky všechny vizuální atributy, které lze nastavit přes kaskádní styly (CSS). Bokeh kromě standardního tématu obsahuje i několik témat dalších. Samotný výběr tématu je až triviálně jednoduchý. Nejdříve je nutné naimportovat další dva balíčky:
from bokeh.themes import built_in_themes from bokeh.io import curdoc
Zvolené téma (resp. jeho jméno – řetězec) se následně pouze uloží do atributu pojmenovaného theme objektu, jenž je získán zavoláním tovární metody curdoc() (current document):
# nastavení tématu curdoc().theme = 'caliber'
3. Ukázky podporovaných témata stylů grafů
Pojďme si nyní ukázat, jak budou po vizuální stránce vypadat grafy, které sice zobrazují stejná data, ovšem pokaždé s využitím odlišného tématu. Začneme tématem nazvaným „caliber“:
Obrázek 1: Graf zobrazený s využitím tématu „caliber“.
Úplný zdrojový kód dnešního prvního demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/theme_caliber.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.themes import built_in_themes from bokeh.io import curdoc # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # nastavení tématu curdoc().theme = 'caliber' # vykreslení průběhu funkce sin # hodnoty na x-ové ose x = np.linspace(0, 2 * np.pi, 100) # hodnoty na y-ové ose y1 = np.sin(x) # hodnoty na y-ové ose y2 = np.cos(x) # plocha pro graf p = figure(title="sin(x) a cos(x)", x_axis_label="x", y_axis_label="sin(x) a cos(x)") # vykreslení průběhu p.line(x, y1, legend_label="sin(x)", line_width=2, color="#0aa") p.line(x, y2, legend_label="cos(x)", line_width=2, color="#aa0") # vykreslení grafu do plochy webové stránky show(p)
Další dostupné téma se jmenuje „dark_minimal“:
Obrázek 2: Graf zobrazený s využitím tématu „dark_minimal“.
Úplný zdrojový kód dnešního druhého demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/theme_dark_minimal.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.themes import built_in_themes from bokeh.io import curdoc # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # nastavení tématu curdoc().theme = 'dark_minimal' # vykreslení průběhu funkce sin # hodnoty na x-ové ose x = np.linspace(0, 2 * np.pi, 100) # hodnoty na y-ové ose y1 = np.sin(x) # hodnoty na y-ové ose y2 = np.cos(x) # plocha pro graf p = figure(title="sin(x) a cos(x)", x_axis_label="x", y_axis_label="sin(x) a cos(x)") # vykreslení průběhu p.line(x, y1, legend_label="sin(x)", line_width=2, color="#0aa") p.line(x, y2, legend_label="cos(x)", line_width=2, color="#aa0") # vykreslení grafu do plochy webové stránky show(p)
Třetí téma, které si ukážeme, se jmenuje „light_minimal“:
Obrázek 3: Graf zobrazený s využitím tématu „light_minimal“.
Úplný zdrojový kód dnešního třetího demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/theme_light_minimal.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.themes import built_in_themes from bokeh.io import curdoc # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # nastavení tématu curdoc().theme = 'light_minimal' # vykreslení průběhu funkce sin # hodnoty na x-ové ose x = np.linspace(0, 2 * np.pi, 100) # hodnoty na y-ové ose y1 = np.sin(x) # hodnoty na y-ové ose y2 = np.cos(x) # plocha pro graf p = figure(title="sin(x) a cos(x)", x_axis_label="x", y_axis_label="sin(x) a cos(x)") # vykreslení průběhu p.line(x, y1, legend_label="sin(x)", line_width=2, color="#0aa") p.line(x, y2, legend_label="cos(x)", line_width=2, color="#aa0") # vykreslení grafu do plochy webové stránky show(p)
A konečně čtvrté dostupné téma nese název „night_sky“:
Obrázek 4: Graf zobrazený s využitím tématu „night_sky“.
Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/theme_night_sky.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.themes import built_in_themes from bokeh.io import curdoc # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # nastavení tématu curdoc().theme = 'night_sky' # vykreslení průběhu funkce sin # hodnoty na x-ové ose x = np.linspace(0, 2 * np.pi, 100) # hodnoty na y-ové ose y1 = np.sin(x) # hodnoty na y-ové ose y2 = np.cos(x) # plocha pro graf p = figure(title="sin(x) a cos(x)", x_axis_label="x", y_axis_label="sin(x) a cos(x)") # vykreslení průběhu p.line(x, y1, legend_label="sin(x)", line_width=2, color="#0aa") p.line(x, y2, legend_label="cos(x)", line_width=2, color="#aa0") # vykreslení grafu do plochy webové stránky show(p)
4. Graf obsahující data ve formě rastrového obrázku
Knihovna Bokeh dokáže v případě potřeby vizualizovat i data dostupná ve formě 2D matice popř. ve formě rastrového obrázku, který je možné interaktivně zvětšovat, zmenšovat a posouvat, stejně jako jakýkoli jiný typ grafu. Podívejme se nyní na způsob reprezentace takového obrázku i na postup pro jeho vykreslení.
V prvním kroku si připravíme dvourozměrnou (prázdnou) matici o rozměrech IMAGE_HEIGHT×IMAGE_WIDTH, tedy matici s IMAGE_HEIGHT řádky a IMAGE_WIDTH sloupci (IMAGE_WIDTH a IMAGE_HEIGHT jsou pochopitelně celá kladná čísla). Povšimněte si, že typ prvků je nastaven na uint32, protože každá hodnota bude reprezentována 32bitovým celým číslem bez znaménka (používáme NumPy, kde se s datovými typy pracuje podobně jako v C a Fortranu):
image = np.empty((IMAGE_HEIGHT, IMAGE_WIDTH), dtype=np.uint32)
Následně si vytvoříme pohled (view) na tuto matici. Tento pohled bude matici zvnějšku reprezentovat jako trojrozměrné pole IMAGE_HEIGHT×IMAGE_WIDTH×4 bajtů (typ uint8). Důvod je jednoduchý – každý pixel původní matice je logicky rozložen na tři barvové složky + hodnoty průhlednosti:
view = image.view(dtype=np.uint8).reshape((IMAGE_HEIGHT, IMAGE_WIDTH, 4))
Matici naplníme vhodnými daty, například kombinovaným gradientním přechodem černá-červená v horizontálním směru a černá-modrá ve směru vertikálním. Zelená složka je nastavena na nulu a průhlednost na zcela neprůhlednou barvu:
for j in range(IMAGE_HEIGHT): for i in range(IMAGE_WIDTH): view[j, i, 0] = int(255 * j / IMAGE_HEIGHT) # red view[j, i, 1] = 0 # green view[j, i, 2] = int(255 * i / IMAGE_WIDTH) # blue view[j, i, 3] = 255 # alpha
Nyní již postačuje obsah matice vykreslit ve formě rastrového obrázku. Povšimněte si, že rozměry obrázku v ploše prohlížeče mohou být odlišné od původního (nativního) rozlišení. Zde konkrétně použijeme desetinásobné zvětšení v obou osách:
# plocha pro graf p = figure(width=IMAGE_WIDTH*10, height=IMAGE_HEIGHT*10, x_range=(0, 10), y_range=(0, 10)) # vykreslení rastrového obrázku typu RGBA p.image_rgba(image=[image], x=[0], y=[0], dw=[10], dh=[10]) # vykreslení grafu do plochy webové stránky show(p)
Výsledek bude vypadat následovně:
Obrázek 5: Rastrový obrázek zobrazený knihovnou Bokeh v ploše prohlížeče.
Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/raster1.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, output_file, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # velikost rastrového obrázku IMAGE_WIDTH = 32 IMAGE_HEIGHT = 32 # matice představující bázi rastrového obrázku image = np.empty((IMAGE_HEIGHT, IMAGE_WIDTH), dtype=np.uint32) # "pohled" na matici jako na trojrozměrné pole view = image.view(dtype=np.uint8).reshape((IMAGE_HEIGHT, IMAGE_WIDTH, 4)) # vyplnění obrázku vzorkem for j in range(IMAGE_HEIGHT): for i in range(IMAGE_WIDTH): view[j, i, 0] = int(255 * j / IMAGE_HEIGHT) # red view[j, i, 1] = 0 # green view[j, i, 2] = int(255 * i / IMAGE_WIDTH) # blue view[j, i, 3] = 255 # alpha # plocha pro graf p = figure(width=IMAGE_WIDTH*10, height=IMAGE_HEIGHT*10, x_range=(0, 10), y_range=(0, 10)) # vykreslení rastrového obrázku typu RGBA p.image_rgba(image=[image], x=[0], y=[0], dw=[10], dh=[10]) # vykreslení grafu do plochy webové stránky show(p)
5. Rastrová data s proměnným počtem sloupců a řádků
V předchozím příkladu byl rastrový obrázek čtvercový, konkrétně měl rozměry 32×32 pixelů:
# velikost rastrového obrázku IMAGE_WIDTH = 32 IMAGE_HEIGHT = 32
Nic nám však nebrání v používání obrázků, v nichž je horizontální a vertikální rozlišení odlišné, tj. kde počet sloupců neodpovídá počtu řádků:
# velikost rastrového obrázku IMAGE_WIDTH = 16 IMAGE_HEIGHT = 10
Při vykreslení jednotlivé pixely zvětšíme 30×:
# plocha pro graf p = figure(width=IMAGE_WIDTH*30, height=IMAGE_HEIGHT*30, x_range=(0, 10), y_range=(0, 10))
S následujícím výsledkem:
Obrázek 6: Rastrová data v původním rozlišení 16×10 pixelů.
Úplný zdrojový kód tohoto pozměněného demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/raster2.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, output_file, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # velikost rastrového obrázku IMAGE_WIDTH = 16 IMAGE_HEIGHT = 10 # matice představující bázi rastrového obrázku image = np.empty((IMAGE_HEIGHT, IMAGE_WIDTH), dtype=np.uint32) # "pohled" na matici jako na trojrozměrné pole view = image.view(dtype=np.uint8).reshape((IMAGE_HEIGHT, IMAGE_WIDTH, 4)) # vyplnění obrázku vzorkem for j in range(IMAGE_HEIGHT): for i in range(IMAGE_WIDTH): view[j, i, 0] = int(255 * j / IMAGE_HEIGHT) # red view[j, i, 1] = 0 # green view[j, i, 2] = int(255 * i / IMAGE_WIDTH) # blue view[j, i, 3] = 255 # alpha # plocha pro graf p = figure(width=IMAGE_WIDTH*30, height=IMAGE_HEIGHT*30, x_range=(0, 10), y_range=(0, 10)) # vykreslení rastrového obrázku typu RGBA p.image_rgba(image=[image], x=[0], y=[0], dw=[10], dh=[10]) # vykreslení grafu do plochy webové stránky show(p)
6. Textura – moiré
Můžeme se pokusit i o programové vytvoření složitějších vzorků (textury). Bude se konkrétně jednat o procedurální texturu založenou na efektu takzvaného moaré. Tuto procedurální texturu (či možná lépe řečeno rastrový vzorek) vytvořil John Connett z Minnesotské univerzity. O tomto vzorku, který v podstatě názorně ukazuje vliv aliasu při tvorbě rastrových obrázků, později pojednal i A. K. Dewdney v časopise Scientific American. Popisovaný vzorek je generovaný velmi jednoduchým a taktéž dostatečně rychlým způsobem: každému pixelu ve vytvářeném rastrovém obrázku (bitmapě) je přiřazena dvojice souřadnic [x, y]. Tyto souřadnice obecně neodpovídají celočíselným indexům pixelu, které můžeme například označit [i, j] (záleží totiž na zvoleném faktoru zvětšení popř. zmenšení vzorku). Posléze je pro každý pixel vypočtena hodnota k na základě jednoduchého vztahu k=i2+j2. Důležité je, že tato hodnota (typicky) přeteče přes 255, takže výsledkem je zajímavý vzorek.
Implementace výpočtu i s přetečením
# vyplnění obrázku vzorkem for j in range(IMAGE_HEIGHT): for i in range(IMAGE_WIDTH): k = i*i + j*j val = int(k) & 255 view[i, j, 0] = val # red view[i, j, 1] = val # green view[i, j, 2] = val # blue view[i, j, 3] = 255 # alpha
Výsledek může po zobrazení v ploše prohlížeče vypadat takto:
Obrázek 7: Kruhové moaré vytvořené předchozím algoritmem.
Teprve po zvětšení se ukáže, jakým způsobem je vlastně vzorek vytvořen:
Obrázek 8: Zvětšená část předchozího obrázku.
Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/raster3.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, output_file, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # textura by měla být čtvercová a její šířka i výška by měla být # mocninou čísla 2 IMAGE_WIDTH = 512 IMAGE_HEIGHT = 512 # matice představující bázi rastrového obrázku image = np.empty((IMAGE_HEIGHT, IMAGE_WIDTH), dtype=np.uint32) # "pohled" na matici jako na trojrozměrné pole view = image.view(dtype=np.uint8).reshape((IMAGE_HEIGHT, IMAGE_WIDTH, 4)) # vyplnění obrázku vzorkem for j in range(IMAGE_HEIGHT): for i in range(IMAGE_WIDTH): k = i*i + j*j val = int(k) & 255 view[i, j, 0] = val # red view[i, j, 1] = val # green view[i, j, 2] = val # blue view[i, j, 3] = 255 # alpha # plocha pro graf p = figure(width=IMAGE_WIDTH, height=IMAGE_HEIGHT, x_range=(0, 10), y_range=(0, 10)) # vykreslení rastrového obrázku typu RGBA p.image_rgba(image=[image], x=[0], y=[0], dw=[10], dh=[10]) # vykreslení grafu do plochy webové stránky show(p)
7. Použití externí barvové palety (LUT)
K výše popsanému algoritmu ještě přidáme část, která na základě vypočtené hodnoty k vybere vhodnou barvu z barvové palety a pixel následně touto barvou vyplní. Tímto přímočarým, rychlým a současně i jednoduchým způsobem je možné vytvářet mnohdy fantastické vzorky; pouze stačí měnit barvovou paletu (ideální jsou plynulé přechody mezi barvami – gradient) a měřítko, pomocí kterého se převádí celočíselné pozice pixelů v rastru [i, j] na souřadnice [x, y].
Obrázek 9: Moaré s kružnicovým vzorkem.
Obrázek 10: Mez zvětšení, při kterém již kružnicový vzorek začíná mizet.
Přidání barvové palety:
# vyplnění obrázku vzorkem for j in range(IMAGE_HEIGHT): for i in range(IMAGE_WIDTH): k = i*i + j*j val = int(k) & 255 # aplikace barvové palety view[i, j, 0] = palette[val][0] # red view[i, j, 1] = palette[val][1] # green view[i, j, 2] = palette[val][2] # blue view[i, j, 3] = 255 # alpha
Přičemž barvová paleta je reprezentována polem s 256 trojicemi:
palette = ( (000, 000, 0), (000, 000, 0), (000, 000, 8), (000, 000, 16), (000, 000, 24), ... ... ... )
Výsledek bude po zobrazení knihovnou Bokeh vypadat následovně:
Obrázek 11: Moaré s aplikací barev z palety (LUTu).
Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/raster4.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, output_file, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # barvová paleta import palette_ice # textura by měla být čtvercová a její šířka i výška by měla být # mocninou čísla 2 IMAGE_WIDTH = 512 IMAGE_HEIGHT = 512 # matice představující bázi rastrového obrázku image = np.empty((IMAGE_HEIGHT, IMAGE_WIDTH), dtype=np.uint32) # "pohled" na matici jako na trojrozměrné pole view = image.view(dtype=np.uint8).reshape((IMAGE_HEIGHT, IMAGE_WIDTH, 4)) # výběr barvové palety palette = palette_ice.palette # vyplnění obrázku vzorkem for j in range(IMAGE_HEIGHT): for i in range(IMAGE_WIDTH): k = i*i + j*j val = int(k) & 255 # aplikace barvové palety view[i, j, 0] = palette[val][0] # red view[i, j, 1] = palette[val][1] # green view[i, j, 2] = palette[val][2] # blue view[i, j, 3] = 255 # alpha # plocha pro graf p = figure(width=IMAGE_WIDTH, height=IMAGE_HEIGHT, x_range=(0, 10), y_range=(0, 10)) # vykreslení rastrového obrázku typu RGBA p.image_rgba(image=[image], x=[0], y=[0], dw=[10], dh=[10]) # vykreslení grafu do plochy webové stránky show(p)
8. Rastrové obrázky a interní barvové palety Bokehu
Ve skutečnosti nemusíme ve všech případech používat vlastní barvové palety, protože knihovna Bokeh nám nabízí již předpřipravené palety, které jsou popsány na stránce https://docs.bokeh.org/en/latest/docs/reference/palettes.html. Podívejme se nyní na způsob použití těchto palet. Postupovat budeme odlišným způsobem, protože nebudeme rastrový obrázek vyplňovat s využitím dvou vnořených programových smyček. Namísto toho využijeme dvojici velmi užitečných funkcí knihovny NumPy – jedná se o funkce linspace a meshgrid. Vhodnou kombinací těchto funkcí můžeme vytvořit „mřížku souřadnic“, s nimiž dokáže knihovna NumPy velmi dobře pracovat.
Nejprve si necháme vygenerovat dva vektory, z nichž každý obsahuje souřadnice mřížky na x-ové resp. na y-ové ose:
# souřadnice mřížky na x-ové a y-ové ose x = np.linspace(-300, 300, IMAGE_WIDTH) y = np.linspace(-300, 300, IMAGE_HEIGHT)
Na základě těchto dvou vektorů si necháme vygenerovat matice se všemi kombinacemi souřadnic:
# matice s x-ovými a y-ovými souřadnicemi tvořícími mřížku xx, yy = np.meshgrid(x, y)
Podívejme se, jaké výsledky získáme pro vektory s pouhými čtyřmi resp. pěti prvky:
>>> import numpy as np >>> x = np.linspace(1, 5, 5) >>> x array([1., 2., 3., 4., 5.]) >>> y = np.linspace(1, 6, 6) >>> y array([1., 2., 3., 4., 5., 6.]) >>> xx, yy = np.meshgrid(x, y) >>> xx array([[1., 2., 3., 4., 5.], [1., 2., 3., 4., 5.], [1., 2., 3., 4., 5.], [1., 2., 3., 4., 5.], [1., 2., 3., 4., 5.], [1., 2., 3., 4., 5.]]) >>> yy array([[1., 1., 1., 1., 1.], [2., 2., 2., 2., 2.], [3., 3., 3., 3., 3.], [4., 4., 4., 4., 4.], [5., 5., 5., 5., 5.], [6., 6., 6., 6., 6.]])
Vidíme, že korespondující prvky matic skutečně tvoří dvojice souřadnic určujících jeden průsečík mřížky. A právě s těmito dvojici hodnot můžeme provádět různé operace:
>>> xx*yy array([[ 1., 2., 3., 4., 5.], [ 2., 4., 6., 8., 10.], [ 3., 6., 9., 12., 15.], [ 4., 8., 12., 16., 20.], [ 5., 10., 15., 20., 25.], [ 6., 12., 18., 24., 30.]])
9. Zobrazení obrázku s interní barvovou paletou Bokehu
Ve chvíli, kdy máme k dispozici dvojici matic s x-ovými a y-ovými souřadnicemi mřížky, můžeme výpočet textury založený na použití programových smyček:
# vyplnění obrázku vzorkem for j in range(IMAGE_HEIGHT): for i in range(IMAGE_WIDTH): k = i*i + j*j val = int(k) & 255 view[i, j, 0] = val # red view[i, j, 1] = val # green view[i, j, 2] = val # blue view[i, j, 3] = 255 # alpha
Nahradit za maticové operace (součin prvek po prvku, součet matic, převod hodnot na zvolený datový typ, výpočet zbytku po dělení pro všechny prvky matice):
# výpočet vzorku, který se má zobrazit d = np.mod((xx*xx + yy*yy).astype(int), 255)
Následně již „pouze“ výslednou matici d zobrazíme metodou image (nikoli image_rgba) se specifikací barvové palety, kterou nám knihovna Bokeh nabízí:
# plocha pro graf p = figure(width=IMAGE_WIDTH, height=IMAGE_HEIGHT) # vykreslení rastrového obrázku do grafu p.image(image=[d], x=0, y=0, dw=10, dh=10, palette="Spectral11", level="image")
Výsledek by měl vypadat následovně:
Obrázek 12: Hodnoty z matice vykreslené metodou image s využitím zvolené barvové palety.
Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/raster5.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, output_file, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # textura by měla být čtvercová a její šířka i výška by měla být # mocninou čísla 2 IMAGE_WIDTH = 512 IMAGE_HEIGHT = 512 # souřadnice mřížky na x-ové a y-ové ose x = np.linspace(-300, 300, IMAGE_WIDTH) y = np.linspace(-300, 300, IMAGE_HEIGHT) # matice s x-ovými a y-ovými souřadnicemi tvořícími mřížku xx, yy = np.meshgrid(x, y) # výpočet vzorku, který se má zobrazit d = np.mod((xx*xx + yy*yy).astype(int), 255) # plocha pro graf p = figure(width=IMAGE_WIDTH, height=IMAGE_HEIGHT) p.x_range.range_padding = p.y_range.range_padding = 0 # vykreslení rastrového obrázku do grafu p.image(image=[d], x=0, y=0, dw=10, dh=10, palette="Spectral11", level="image") p.grid.grid_line_width = 0.5 # vykreslení grafu do plochy webové stránky show(p)
Pochopitelně je možné si zvolit i jinou barvovou paletu nabízenou knihovnou Bokeh, například:
p.image(image=[d], x=0, y=0, dw=10, dh=10, palette="RdGy11", level="image")
S výsledkem:
Obrázek 13: Hodnoty z matice vykreslené metodou image s využitím zvolené barvové palety.
Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/most-popular-python-libs/blob/master/bokeh/raster6.py:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, output_file, show # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # textura by měla být čtvercová a její šířka i výška by měla být # mocninou čísla 2 IMAGE_WIDTH = 512 IMAGE_HEIGHT = 512 # souřadnice mřížky na x-ové a y-ové ose x = np.linspace(-500, 500, IMAGE_WIDTH) y = np.linspace(-500, 500, IMAGE_HEIGHT) # matice s x-ovými a y-ovými souřadnicemi tvořícími mřížku xx, yy = np.meshgrid(x, y) # výpočet vzorku, který se má zobrazit d = np.mod((xx*xx + yy*yy).astype(int), 255) # plocha pro graf p = figure(width=IMAGE_WIDTH, height=IMAGE_HEIGHT) p.x_range.range_padding = p.y_range.range_padding = 0 # vykreslení rastrového obrázku do grafu p.image(image=[d], x=0, y=0, dw=10, dh=10, palette="RdGy11", level="image") p.grid.grid_line_width = 0.5 # vykreslení grafu do plochy webové stránky show(p)
10. Popisky na x-ové ose sloupcového grafu
Vraťme se ještě jednou k problematice zobrazení sloupcových grafů, které se používají ve dvou oblastech:
- Hodnoty na x-ové ose tvoří například časovou řadu (tedy jedná se o seřazené hodnoty)
- Hodnoty na x-ové ose spolu sice souvisí, ale nemusí být nutně ve vstupních datech správně seřazeny
Podívejme se na druhý případ. Budeme chtít zobrazit formou sloupcového grafu údaje získané ze stránky https://www.tiobe.com/tiobe-index/:
# jména na X-ové ose languages = ("Python", "C", "Java", "C++", "C#", "Visual Basic", "JavaScript", "SQL", "Assembly", "Swift") # hodnoty na Y-ové ose ratings = (12.20, 11.91, 10.47, 9.63, 6.12, 5.42, 2.09, 1.94, 1.85, 1.55)
To znamená, že na x-ové ose budou zobrazena jména jazyků a na y-ové ose jejich hodnocení. Při definici plochy pro graf použijeme parametr x_range:
p = figure(x_range=languages, height=250, title="TIOBE index", toolbar_location=None, tools="")
A sloupcový graf následně vykreslíme:
p.vbar(x=languages, top=ratings, width=0.9)
S výsledkem:
Obrázek 14: Sloupcový graf se zobrazenými popularitami programovacích jazyků.
Úplný zdrojový kód tohoto příkladu vypadá následovně:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.io import output_file, show from bokeh.plotting import figure # jména na X-ové ose languages = ("Python", "C", "Java", "C++", "C#", "Visual Basic", "JavaScript", "SQL", "Assembly", "Swift") # hodnoty na Y-ové ose ratings = (12.20, 11.91, 10.47, 9.63, 6.12, 5.42, 2.09, 1.94, 1.85, 1.55) # plocha pro graf p = figure(x_range=languages, height=250, title="TIOBE index", toolbar_location=None, tools="") # vykreslení průběhu hodnot p.vbar(x=languages, top=ratings, width=0.9) # styl vykreslení p.xgrid.grid_line_color = None p.y_range.start = 0 # vykreslení grafu do plochy webové stránky show(p)
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.io import output_file, show from bokeh.plotting import figure # jména na X-ové ose languages = ["Python", "C++", "C#", "Visual Basic", "JavaScript", "SQL", "Assembly", "Java", "C", "Swift"] # hodnoty na Y-ové ose ratings = [12.20, 9.63, 6.12, 5.42, 2.09, 1.94, 1.85, 10.47, 11.91, 1.55] # plocha pro graf p = figure(x_range=languages, height=250, title="TIOBE index", toolbar_location=None, tools="") # vykreslení průběhu hodnot p.vbar(x=languages, top=ratings, width=0.9) # styl vykreslení p.xgrid.grid_line_color = None p.y_range.start = 0 # vykreslení grafu do plochy webové stránky show(p)
11. Seřazení hodnot podle zadaného kritéria
Jazyky lze ještě před zobrazením grafu seřadit podle jejich popularity. Použijeme přitom následující trik, který zajistí i korektní prohození názvů sloupců:
sorted_ratings = sorted(languages, key=lambda x: ratings[languages.index(x)])
Výsledek nyní bude vypadat takto:
Obrázek 15: Sloupcový graf se zobrazenými popularitami programovacích jazyků; jazyky jsou seřazeny podle popularity.
Kód takto upraveného příkladu se zvýrazněnými rozdíly:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.io import output_file, show from bokeh.plotting import figure # jména na X-ové ose languages = ["Python", "C++", "C#", "Visual Basic", "JavaScript", "SQL", "Assembly", "Java", "C", "Swift"] # hodnoty na Y-ové ose ratings = [12.20, 9.63, 6.12, 5.42, 2.09, 1.94, 1.85, 10.47, 11.91, 1.55] sorted_ratings = sorted(languages, key=lambda x: ratings[languages.index(x)]) # plocha pro graf p = figure(x_range=sorted_ratings, height=350, title="TIOBE index", toolbar_location=None, tools="") # vykreslení průběhu hodnot p.vbar(x=languages, top=ratings, width=0.9) # styl vykreslení p.xgrid.grid_line_color = None p.y_range.start = 0 # vykreslení grafu do plochy webové stránky show(p)
Opačné seřazení (nejprve jazyky s vyšší popularitou) je stejně snadné:
sorted_ratings = (sorted(languages, key=lambda x: ratings[languages.index(x)]))[::-1]
Obrázek 16: Sloupcový graf se zobrazenými popularitami programovacích jazyků; jazyky jsou seřazeny podle popularity – první je jazyk s nejvyšší popularitou.
Kód takto upraveného příkladu se zvýrazněnými rozdíly:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.io import output_file, show from bokeh.plotting import figure # jména na X-ové ose languages = ["Python", "C++", "C#", "Visual Basic", "JavaScript", "SQL", "Assembly", "Java", "C", "Swift"] # hodnoty na Y-ové ose ratings = [12.20, 9.63, 6.12, 5.42, 2.09, 1.94, 1.85, 10.47, 11.91, 1.55] sorted_ratings = (sorted(languages, key=lambda x: ratings[languages.index(x)]))[::-1] # plocha pro graf p = figure(x_range=sorted_ratings, height=350, title="TIOBE index", toolbar_location=None, tools="") # vykreslení průběhu hodnot p.vbar(x=languages, top=ratings, width=0.9) # styl vykreslení p.xgrid.grid_line_color = None p.y_range.start = 0 # vykreslení grafu do plochy webové stránky show(p)
12. Použití struktury ColumnDataSource
Prozatím byla data pro graf reprezentována formou sekvencí hodnot, n-tic, seznamů nebo vektorů a matic (NumPy). Ve skutečnosti je to však polovičaté řešení, protože knihovna Bokeh umožňuje vstupní data „obalit“ strukturou ColumnDataSource, která nám umožňuje specifikaci barev pro každou hodnotu zvlášť atd. Podívejme se, jak taková struktura vznikne. Je to jednoduché:
# definice zdroje dat source = ColumnDataSource(data=dict(languages=languages, ratings=ratings))
Specifikovali jsme tedy jak hodnoty na x-ové ose (názvy jazyků), tak i na ose y-ové (jejich popularita).
Takto použitý zdroj dat lze snadno vykreslit do sloupcového grafu:
# vykreslení průběhu hodnot p.vbar(x='languages', top='ratings', source=source, width=0.9)
Výsledek bude vypadat následovně:
Obrázek 17: Sloupcový graf se zobrazenými popularitami programovacích jazyků.
Opět se pochopitelně podíváme na úplný zdrojový kód příkladu:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.io import output_file, show from bokeh.plotting import figure from bokeh.models import ColumnDataSource # jména na X-ové ose languages = ["Python", "C++", "C#", "Visual Basic", "JavaScript", "SQL", "Assembly", "Java", "C", "Swift"] # hodnoty na Y-ové ose ratings = [12.20, 9.63, 6.12, 5.42, 2.09, 1.94, 1.85, 10.47, 11.91, 1.55] # definice zdroje dat source = ColumnDataSource(data=dict(languages=languages, ratings=ratings)) # plocha pro graf p = figure(x_range=languages, height=250, title="TIOBE index", toolbar_location=None, tools="") # vykreslení průběhu hodnot p.vbar(x='languages', top='ratings', source=source, width=0.9) # styl vykreslení p.xgrid.grid_line_color = None p.y_range.start = 0 # vykreslení grafu do plochy webové stránky show(p)
13. Zvýraznění hodnot ve sloupcovém grafu s využitím různých barev sloupců
Díky umístění dat, která se mají vykreslit v grafu, do datové struktury ColumnDataSource je možné specifikovat barvy hodnot. Použít můžeme například již předpřipravenou barvovou paletu, což je koncept, který jsme viděli v souvislosti s rastrovými obrázky a s maticovými daty. Tentokrát však budeme obarvovat jednotlivé sloupce v grafu:
# definice zdroje dat source = ColumnDataSource(data=dict(languages=languages, ratings=ratings, color=Spectral10))
Výsledek by měl vypadat takto:
Obrázek 18: Sloupcový graf se sloupci vybarvenými na základě hodnot a zvolené barvové palety.
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.io import output_file, show from bokeh.plotting import figure from bokeh.models import ColumnDataSource from bokeh.palettes import Spectral10 # jména na X-ové ose languages = ["Python", "C++", "C#", "Visual Basic", "JavaScript", "SQL", "Assembly", "Java", "C", "Swift"] # hodnoty na Y-ové ose ratings = [12.20, 9.63, 6.12, 5.42, 2.09, 1.94, 1.85, 10.47, 11.91, 1.55] # definice zdroje dat source = ColumnDataSource(data=dict(languages=languages, ratings=ratings, color=Spectral10)) # plocha pro graf p = figure(x_range=languages, height=250, title="TIOBE index", toolbar_location=None, tools="") # vykreslení průběhu hodnot p.vbar(x='languages', top='ratings', color='color', source=source, width=0.9) # styl vykreslení p.xgrid.grid_line_color = None p.y_range.start = 0 # vykreslení grafu do plochy webové stránky show(p)
14. Interaktivní přepočet grafů
Další požadavek, s nímž se velmi často setkáme, a který – nutno zdůraznit – není v knihovně Bokeh (prozatím) možné rozumně vyřešit, je interaktivní přepočet grafů po změně nějaké hodnoty či vstupní podmínky. Zkusme si tento problém alespoň částečně vyřešit. Začneme velmi jednoduchým příkladem, který pouze vykreslí sinusovku a kosinusovku:
# 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 # hodnoty na x-ové ose x = np.linspace(0, 2 * np.pi, 100) # hodnoty na y-ové ose y1 = np.sin(x) # hodnoty na y-ové ose y2 = np.cos(x) # plocha pro graf p = figure(title="sin(x) a cos(x)", x_axis_label="x", y_axis_label="sin(x) a cos(x)") # vykreslení průběhu p.line(x, y1, legend_label="sin(x)", line_width=2, color="#00a0a0") p.line(x, y2, legend_label="cos(x)", line_width=2, color="#a0a000") # vykreslení grafu do plochy webové stránky show(p)
Obrázek 19: Graf se sinusovkou a kosinusovkou.
15. Přidání posuvníku pro interaktivní změnu amplitudy jednoho průběhu v grafu
Nyní do grafu přidáme posuvník, kterým budeme posléze měnit amplitudu jedné z funkcí:
# posuvník pro změnu amplitudy amplitude_slider = Slider(start=-1, end=1, value=1, step=0.05, title="Amplitude")
Při vykreslení nesmíme zapomenout na to, že se kromě grafu má vykreslit i onen posuvník:
# vykreslení grafu do plochy webové stránky show(row(p, amplitude_slider))
Výsledek by měl vypadat takto:
Obrázek 20: Graf se sinusovkou a kosinusovkou, přidán je i posuvník.
Opět si ukažme úplný zdrojový kód tohoto příkladu:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.layouts import row from bokeh.models import Slider # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # vykreslení průběhu funkce sin # hodnoty na x-ové ose x = np.linspace(0, 2 * np.pi, 100) # hodnoty na y-ové ose y1 = np.sin(x) # hodnoty na y-ové ose y2 = np.cos(x) # plocha pro graf p = figure(title="sin(x) a cos(x)", x_axis_label="x", y_axis_label="sin(x) a cos(x)") # vykreslení průběhu p.line(x, y1, legend_label="sin(x)", line_width=2, color="#00a0a0") p.line(x, y2, legend_label="cos(x)", line_width=2, color="#a0a000") # posuvník pro změnu amplitudy amplitude_slider = Slider(start=-1, end=1, value=1, step=0.05, title="Amplitude") # vykreslení grafu do plochy webové stránky show(row(p, amplitude_slider))
16. Data pro vykreslení grafu dostupná ve formě ColumnDataSource
V dalším kroku budeme muset data zabalit do nám již známé struktury ColumnDataSource. Je to nutné z toho důvodu, že s daty (hodnotami) budeme později manipulovat v JavaScriptu:
# zdroj dat source = ColumnDataSource(data=dict(x=x, y=y1))
Nepatrně se změní i způsob vykreslení:
p.line(source=source, line_width=2, color="#00a0a0") p.line(x, y2, legend_label="cos(x)", line_width=2, color="#a0a000")
Zdrojový kód příkladu se změní pouze nepatrně:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.layouts import row from bokeh.models import Slider, ColumnDataSource, CustomJS # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # vykreslení průběhu funkce sin # hodnoty na x-ové ose x = np.linspace(0, 2 * np.pi, 100) # hodnoty na y-ové ose y1 = np.sin(x) # hodnoty na y-ové ose y2 = np.cos(x) # zdroj dat source = ColumnDataSource(data=dict(x=x, y=y1)) # plocha pro graf p = figure(title="sin(x) a cos(x)", x_axis_label="x", y_axis_label="sin(x) a cos(x)") # vykreslení průběhu p.line(source=source, line_width=2, color="#00a0a0") p.line(x, y2, legend_label="cos(x)", line_width=2, color="#a0a000") # posuvník pro změnu amplitudy amplitude_slider = Slider(start=-1, end=1, value=1, step=0.05, title="Amplitude") # vykreslení grafu do plochy webové stránky show(row(p, amplitude_slider))
17. Temná strana Bokehu – přepočet dat v JavaScriptu
Nyní se ovšem ocitáme ve složité situaci, protože budeme chtít, aby se graf (a v něm zobrazené hodnoty) přepočetl po změně posuvníku. Vzhledem k tomu, že Bokeh vytváří statické HTML stránky a neběží jako webový server, nemá možnost nijak do zobrazených dat zasahovat. Musíme si pomoci oklikou a způsobem, který není obecně použitelný – necháme si data přepočítat přímo na straně webového klienta, tj. kódem napsaným v JavaScriptu. Tento kód propojíme s posuvníkem:
amplitude_slider = Slider(start=-1, end=1, value=1, step=0.05, title="Amplitude") amplitude_slider.js_on_change("value", on_amplitude_change)
Po změně posuvníku se zavolá callback funkce on_amplitude_change. Tato funkce dokáže získat data „zabalená“ do webové stránky a modifikuje je. Zde tedy vlastně opakujeme výpočet, který již jednou proběhl na straně Pythonu. Původní data jsou uložena v source:
# callback zavolaný po změně souřadnic posuvníku on_amplitude_change = CustomJS(args=dict(source=source), code=""" const data = source.data; const a = cb_obj.value const x = data['x'] const y = data['y'] for (let i = 0; i < x.length; i++) { y[i] = a*Math.sin(x[i]) } source.change.emit(); """)
Obrázek 21: Změna amplitudy sinusovky.
Obrázek 22: Změna amplitudy i fáze sinusovky.
Zdrojový kód dnešního posledního demonstračního příkladu vypadá takto:
# naimportujeme vybrané funkce z knihovny `bokeh.plotting` from bokeh.plotting import figure, show from bokeh.layouts import row from bokeh.models import Slider, ColumnDataSource, CustomJS # taktéž budeme potřebovat některé funkce z knihovny `numpy` import numpy as np # vykreslení průběhu funkce sin # hodnoty na x-ové ose x = np.linspace(0, 2 * np.pi, 100) # hodnoty na y-ové ose y1 = np.sin(x) # hodnoty na y-ové ose y2 = np.cos(x) # zdroj dat source = ColumnDataSource(data=dict(x=x, y=y1)) # plocha pro graf p = figure(title="sin(x) a cos(x)", x_axis_label="x", y_axis_label="sin(x) a cos(x)") # vykreslení průběhu p.line(source=source, line_width=2, color="#00a0a0") p.line(x, y2, legend_label="cos(x)", line_width=2, color="#a0a000") # callback zavolaný po změně souřadnic posuvníku on_amplitude_change = CustomJS(args=dict(source=source), code=""" const data = source.data; const a = cb_obj.value const x = data['x'] const y = data['y'] for (let i = 0; i < x.length; i++) { y[i] = a*Math.sin(x[i]) } source.change.emit(); """) # posuvník pro změnu amplitudy amplitude_slider = Slider(start=-1, end=1, value=1, step=0.05, title="Amplitude") amplitude_slider.js_on_change("value", on_amplitude_change) # vykreslení grafu do plochy webové stránky show(row(p, amplitude_slider))
18. Závěrečné shrnutí
Knihovna Bokeh je dobře využitelná především těmi vývojáři, kteří pracují v Pythonu (simulace, datové analýzy atd.) a potřebují zobrazit výsledky měření nebo výpočtů pomocí grafů zobrazených formou statických (resp. „statických“ HTML stránek). Tyto stránky mohou být součástí prezentace, mohou být uloženy v systému pro správu dokumentů, ve Wiki, na GitHub pages atd. Předností Bokehu je, že generované HTML stránky obsahují jak data, tak i automaticky generovaný kód psaný v JavaScriptu, takže jsou grafy do určité míry interaktivní – a to bez toho, aby vývojář musel aktivně používat či vůbec znát webové (frontend) technologie. Taktéž je možné, aby uživatel, který stránku používá, graf zvětšil/zmenšil/posunul a posléze si nechal uložit výsledek ve formě rastrového obrázku (PNG). Musíme však znát i některá zásadní omezení Bokehu, především fakt, že není jednoduché interaktivně měnit zobrazená data. Nejedná se tedy (a ani to není plánováno) o náhradu interaktivních ovládacích prvků použitých například v Jupyter Notebooku.
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