Bokeh Charts in Flask-Apps einbetten
Die Python Bibliothek Bokeh ist eine hervorragende Lösung, interaktive Diagramme zu erzeugen. Sie besitzt die wesentlichen Komponenten der weitaus populäreren API „matplotlib“, birgt jedoch den Vorteil der dynamischen Diagramme. Demnach kann der User etwa Charts verschieben, zoomen, Bereiche in ihnen auswählen oder sich durch Überfahren zusätzliche Informationen anzeigen.
Doch muss einem Chart auch Plattform für Präsentation geboten werden. So bspw. auf einer Website. Im Artikel „Flask –Webapps mit Python erstellen“ wurde das Python Modul „Flask“ bereits ausführlich eingeleitet. Darauf aufbauend soll hier gezeigt werden, wie sich ein Bokeh Plot generieren – und in eine Flask Webanwendung integrieren lässt.
Daten erzeugen |
def parabel(x): """ Generiere Parabelfunktion (y=x^2) aus Definitionsmenge. Params: x (list(floats)): Definitionsmenge ("x-Werte") Returns: y (list(floats)): Wertemenge ("y-Werte") """ return [i**2 for i in x] x = [i/(10) for i in range(-100, 100)] y = parabel(x)
Um ein Diagramm darzustellen, müssen zunächst Daten als Grundlage vorhanden sein. Mit dem oben dargestellten Quelltext werden die Listen x
und y
generiert. Dabei repräsentiert die Liste x
die Grundmenge, also den „Input“ einer mathematischen Funktion. Hierbei wird mittels einer „List Comprehension“ über die Werte der Python-range()
-Funktion, des Intervalls von einschließlich -100
bis 100
iteriert, jeweils durch 10
geteilt und nach und nach der Liste x
angehängt. Das Resultat sieht wie folgend aus:
[-10.0, -9.9, -9.8, … 9.7, 9.8, 9.9]
Da im Diagramm exemplarisch der Verlauf einer Normalparabel geplottet werden soll, quadriert die Funktion –parabel()
alle Elemente der Definitionsmenge x
und gibt sie als Wertemenge y
wieder aus. Das Resultat sieht wie folgend aus:
[[100.0, 98.01, 96.04000000000002, … 94.08999999999999, 96.04000000000002, 98.01]]
(Nebenbei werden hier die (Un-)Genauigkeiten der Fließkomma-Arithmetik ersichtlich, die im Folgenden jedoch nicht weiter beachtet werden.)
Chart-Funktion |
from bokeh.plotting import figure def chart(x, y): """ Erstelle interaktiven "line plot" Params: x (list): x-Werte von Punkten, zwischen denen eine Linie gezeichnet wird y (lsit): y-Werte von Punkten, zwischen denen eine Linie gezeichnet wird Returns: plot (bokeh.plotting.figure.Figure): plot object """ plot = figure(plot_width=500, plot_height=300) plot.line(x=x, y=y, line_width=3) plot.sizing_mode = "scale_both" return plot
In der Funktion chart()
wird ein plot
-Objekt der Bokeh-Bibliothek generiert. Dazu wird aus dem bokeh.plotting
-Modul die figure
-Klasse importiert. Sie bildet eine Art „Container“, in dem sich das zukünftige Diagramm befindet. Dazu wird die Klasse als „plot
“ zunächst mit den Parametern plot_width=500
und plot_height=300
instanziiert, welche die (anfängliche) Größe des Containers definieren. Danach wird dem plot
-Objekt ein Graph via .line()
ergänzt. Als Parameter werden, die zuvor kreierten Listen x
und y
, sowie die Linienstärke „3“ übergeben. Zuletzt definiert das Attribut .sizing_mode = "scale_both"
ein Verhalten, dass den Plot auf der Internetseite responsive machen wird, also sich bei Skalierung der Internetseite durch Vergrößerung/Verkleinerung des Browserfensters ebenfalls verändert, beibehaltend der Proportionen.
Flask App |
from flask import Flask, render_template from bokeh.embed import components app = Flask(__name__) @app.route("/") def index(): x = [i/(10) for i in range(-100, 100)] y = parabel(x) plot = chart(x, y) script, div = components(plot) return render_template("index.html", script=script, div=div) app.run()
Da im Artikel „Flask –Webapps mit Python erstellen“ Webanwendungen mit Flask Apps bereits ausführlich erläutert wurden, wird sich auf diesen Teil des Codes nicht weiter bezogen. Innerhalb der Viewfunction index()
wird die Definitionsmenge x
, wie oben beschrieben, angelegt und mit der Funktion parabel()
die Wertemenge errechnet. Mit die Funktion chart()
wird das plot
-Objekt instanziiert und durch die importierte Methode components()
, daraus ein Javascript in die script
-Variable erzeugt, sowie ein div
-Element. Diese beiden Variablen werden an das Folgende Template index.html
übergeben, welches sich, relativ von app.py
in dem Ordner templates befindet.
HTML-Template |
│ app.py
├───templates
│ index.html
<!doctype html> <html lang="de"> <head> <meta charset="utf-8"> <!-- Bokeh imports - info: https://docs.bokeh.org/en/latest/docs/user_guide/embed.html#components --> <script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.0.2.min.js"></script> <script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.0.2.min.js"></script> <script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.0.2.min.js"></script> </head> <body> <!-- Bokeh Plot --> {{ script | safe }} {{ div | safe }} </body> </html>
Im head
-Bereich des HTML-Templates müssen Javascripte importiert werden. Dabei ist auf die Versionsnummerierung zu achten. Siehe auch: Dokumentation
Der Plot wird über die Jinja2 Variablen {{ script | safe }}
und {{ div | safe }}
eingebettet. Zusätzlich werden dabei die Filter safe
verwendet. Er ermöglicht das Transferieren von HTML-Code bei deaktiviertem Auto-escaping.
Resultat |
Der Code in seiner vollen Gänze |
from bokeh.plotting import figure from bokeh.embed import components from flask import Flask, render_template def parabel(x): """ Generiere Parabelfunktion (y=x^2) aus Definitionsmenge. Params: x (list(floats)): Definitionsmenge ("x-Werte") Returns: y (list(floats)): Wertemenge ("y-Werte") """ return [i**2 for i in x] def chart(x, y): """ Erstelle interaktiven "line plot" Params: x (list): x-Werte von Punkten, zwischen denen eine Linie gezeichnet wird y (lsit): y-Werte von Punkten, zwischen denen eine Linie gezeichnet wird Returns: plot (bokeh.plotting.figure.Figure): plot object """ plot = figure(plot_width=500, plot_height=300) plot.line(x=x, y=y, line_width=3) plot.sizing_mode = "scale_both" return plot app = Flask(__name__) @app.route("/") def index(): x = [i/(10) for i in range(-100, 100)] y = parabel(x) plot = chart(x, y) script, div = components(plot) return render_template("index.html", script=script, div=div) app.run()
<!doctype html> <html lang="de"> <head> <meta charset="utf-8"> <!-- Bokeh imports - more info: https://docs.bokeh.org/en/latest/docs/user_guide/embed.html#components --> <script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.0.2.min.js"></script> <script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.0.2.min.js"></script> <script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.0.2.min.js"></script> </head> <body> <!-- Bokeh Plot --> {{ script | safe }} {{ div | safe }} </body> </html>