====== 8. Vizualizace dat - grafy ====== ===== Cíle cvičení ===== * Vytvoření obrázku (grafu) pomocí knihovny Matplotlib * Kreslení dat v reálném čase * Kontrola průběžné domácí práce * Definitivní stanovení témat semestrálních prací ===== Podklady ===== * [[https://matplotlib.org/stable/|Matplotlib]] * [[https://zdrojak.cz/clanky/co-je-data-uri/|Co je to Data URI]] * [[https://www.chartjs.org/|chart.js]] ===== Kreslení grafů pomocí Matplotlib ===== Matplotlib renderuje data na displej nebo do souboru prostřednictvím [[https://matplotlib.org/stable/users/explain/figure/backends.html|backendu]], což je vykreslovací zařízení poskytované operačním systémem prostřednictvím vhodné knihovny (např. Qt). Pro naše účely použijeme neinteraktivní statické backendy (tj. nebude se vytvářet vykreslovací okno) a graf budeme transformovanat na proud binárních dat. Tato data pak pošleme společně s vhodným ''mime-type'' jako odpověď na dotaz ''GET'' z prohlížeče, který renderuje HTML kód obsahující tag ''''. ==== Rastrový formát PNG ==== import io import random from matplotlib.figure import Figure from matplotlib.backends.backend_agg import FigureCanvasAgg from matplotlib.backends.backend_svg import FigureCanvasSVG @app.route("/matplot-as-image-.png") def plot_png(num_x_points=50): fig = Figure() axis = fig.add_subplot(1, 1, 1) x_points = range(num_x_points) axis.plot(x_points, [random.randint(1, 30) for x in x_points]) output = io.BytesIO() FigureCanvasAgg(fig).print_png(output) return Response(output.getvalue(), mimetype="image/png") HTML kód, který požádá server o graf 200 náhodných bodů ve formátu PNG pak vypadá následovně. random points as png ==== Vektorový formát SVG ==== @app.route("/matplot-as-image-.svg") def plot_svg(num_x_points=50): fig = Figure() axis = fig.add_subplot(1, 1, 1) x_points = range(num_x_points) axis.plot(x_points, [random.randint(1, 30) for x in x_points]) output = io.BytesIO() FigureCanvasSVG(fig).print_svg(output) return Response(output.getvalue(), mimetype="image/svg+xml") ==== Využití data URI ==== Data URI je způsob, jak obsah externího souboru zapsat přímo do HTML/CSS. Pokud není použit protokol HTTP/2, režie navázání spojení pro získáním jednotlivého souboru může být mnohem větší než samotné stažení dat. Při velkém počtu souborů hraje navíc roli maximální počet souběžných spojení, kvůli kterému musí soubory čekat ve frontě. Následující příklad produkuje datový proud v kódování ''base64'', navíc se využívá toho, že od verze 3.1 knihovny Matplotlib není třeba instacionovat ''CanvasAgg''. Celý příklad je [[https://matplotlib.org/stable/gallery/user_interfaces/web_application_server_sgskip.html|zde]]. fig = Figure() # kod pro vytvoreni grafu output = io.BytesIO() fig.savefig(output, format="png") data = base64.b64encode(buf.getbuffer()).decode("ascii") return f"" ===== Aktualizace grafu v reálném čase ===== Pro kreslení grafů lze samozřejmě využít render na straně klienta, např. pomocí vhodné knihovny v JavaScriptu. Takovou knihovnou je např. [[https://www.chartjs.org/|chart.js]]. Knihovna umožňuje vykreslovat datové řady do HTML elementu [[https://www.w3schools.com/html/html5_canvas.asp|canvas]]. Nejprve je třeba vytvořit instanci třídy Chart, které se předá handler na element. var ctx = document.getElementById('tempChart').getContext('2d'); var tempChart = new Chart(ctx, { type: 'line', data: { labels: [], datasets: [{ label: 'temperature', data: [] },{ label: 'humidity', data: [] }] } }); Předpokládejme, že data přichází do prohlížeče asynchronně např. prostřednictvím ''SSE'' a jsou tvořena třemi hodnotami (čas, teplota, vlhkost) oddělenými čárkou. Funkce pro zpracování takových dat může vypadat třeba takto: var eventSource = new EventSource('/stats'); eventSource.onmessage = function (event) { var data = event.data.split(','); console.log(data); var currentTime = data[0]; var temp = parseInt(data[1]); var humi = parseInt(data[2]); tempChart.data.labels.push(currentTime); tempChart.data.datasets[0].data.push(temp); tempChart.data.datasets[1].data.push(humi); if (tempChart.data.labels.length > 30) { tempChart.data.labels.shift(); tempChart.data.datasets[0].data.shift(); tempChart.data.datasets[1].data.shift(); } tempChart.update(); }; Element pro vykreslování pak může vypadat třeba takto: Celý příklad ke stažení {{ :courses:b0b37nsi:tutorials:app08.zip |zde}}