Files with plots are easy to deal with as long as they are in the
static
subdirectory of the Flask application directory. However,
as already pointed out, the previous vib1
app, which writes plot
files, is not suited for multiple simultaneous users since every
user will destroy all existing plot files before making a new
one. Therefore, we need a robust solution for multiple users of
the app.
The idea is to not write plot files, but instead return the plot
as a string and embed that string directly in the HTML code.
This is relatively straightforward with Matplotlib
and Python. The relevant code is found in the compute.py
file
of the vib2
app.
Python has the io.StringIO
object for writing text to a
string buffer with the same syntax as used for writing text to
files. For binary streams, such as PNG files, one can use
a similar object, io.BytesIO
, to hold the stream (i.e., the plot file)
in memory. The idea is to let Matplotlib write to
a io.BytesIO
object and afterwards extract the
series of bytes in the plot file from this object and embed it in
the HTML file directly. This approach avoids storing plots in separate
files, at the cost of bigger HTML files.
The first step is to let Matplotlib write the PNG data to
the BytesIO
buffer:
import matplotlib.pyplot as plot
from io import BytesIO
# run plt.plot, plt.title, etc.
figfile = BytesIO()
plt.savefig(figfile, format='png')
figfile.seek(0) # rewind to beginning of file
figdata_png = figfile.getvalue() # extract string (stream of bytes)
Before the PNG data can be embedded in HTML we need to convert the data to base64 format:
import base64
figdata_png = base64.b64encode(figdata_png)
Now we can embed the PNG data in HTML by
<img src="data:image/png;base64,PLOTSTR" width="500">
where PLOTSTR
is the content of the string figdata_png
.
The complete compute.py
function takes the form
def compute(A, b, w, T, resolution=500):
"""Return filename of plot of the damped_vibration function."""
t = linspace(0, T, resolution+1)
u = damped_vibrations(t, A, b, w)
plt.figure() # needed to avoid adding curves in plot
plt.plot(t, u)
plt.title('A=%g, b=%g, w=%g' % (A, b, w))
# Make Matplotlib write to BytesIO file object and grab
# return the object's string
from io import BytesIO
figfile = BytesIO()
plt.savefig(figfile, format='png')
figfile.seek(0) # rewind to beginning of file
import base64
figdata_png = base64.b64encode(figfile.getvalue())
return figdata_png
The relevant syntax in an HTML template is
<img src="data:image/png;base64,{{ results }}" width="500">
if results
holds the returned object from the compute
function above.
Inline figures in HTML, instead of using files, are most often realized by XML code with the figure data in SVG format. Plot strings in the SVG format are created very similarly to the PNG example:
figfile = BytesIO()
plt.savefig(figfile, format='svg')
figdata_svg = figfile.getvalue()
The figdata_svg
string contains XML code text can almost
be directly embedded in
HTML5. However, the beginning of the text contains information before
the svg
tag that we want to remove:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Created with matplotlib (http://matplotlib.sourceforge.net/) -->
<svg height="441pt" version="1.1" viewBox="0 0 585 441" ...
The removal is done with a little string manipulation:
figdata_svg = '<svg' + figfile.getvalue().split('<svg')[1]
Now, figdata_svg
can be directly inserted in HTML code without
any surrounding tags (because it is perfectly valid HTML code in itself).
The SVG code generated by Matplotlib may contain UTF-8 characters
so it is necessary to make a unicode string out of the text:
unicode(figdata_svg, 'utf-8')
, otherwise the HTML template will
lead to an encoding exception.
We have made an alternative compute function compute_png_svg
that returns both a PNG and an SVG plot:
def compute_png_svg(A, b, w, T, resolution=500):
...
figfile = BytesIO()
plt.savefig(figfile, format='svg')
figfile.seek(0)
figdata_svg = '<svg' + figfile.getvalue().split('<svg')[1]
figdata_svg = unicode(figdata_svg, 'utf-8')
return figdata_png, figdata_svg
The relevant syntax for inserting an SVG plot in the HTML template is now
{{ result[1]|safe }}
The use of safe
is essential here.
safe
for verbatim HTML code:
Special HTML characters like <
, >
,
&
, "
, and '
are escaped in a template string like {{ str }}
(i.e., &
is replaced by &
`<` is replaced by <
, etc.).
We need to avoid this manipulation of the string content
because result[1]
contains
XML code where the mentioned characters are essential part of the syntax.
Writing {{str|safe}}
ensures that the contents of the string str
are not altered before being embedded in the HTML text.
An alternative template, view_svg.html
applies the SVG plot instead
of the PNG plot. We use the command-line argument svg
for indicating
that we want an SVG instead of a PNG plot:
# SVG or PNG plot?
svg = False
try:
if sys.argv[1] == 'svg':
svg = True
except IndexError:
pass
if svg:
from compute import compute_png_svg as compute
template = 'view_svg.html'
else:
from compute import compute
template = 'view.html'
The mpld3 library can convert Matplotlib plots to HTML code that can be directly embedded in a web page. Here is a basic example:
# Plot array y vs x
import matplotlib.pyplot as plt, mpld3
fig, ax = plt.subplots()
ax.plot(x, y)
html_text = mpld3.fig_to_html(fig)
The string html_text
contains all the HTML code that is needed to
display the plot.
The great advantage of the mpld3
library is that it contains
capabilities for creating custom interactive plots through combining
Matplotlib with JavaScript, see the mpld3 Example Gallery.
As an alternative to using Matplotlib for plotting, we can utilize
the Bokeh tool, which is
particularly developed for graphics in web browsers.
The vib3 app is similar to the
previously described vib1
and vib2
app, except that we make one plot with
Bokeh. Only the compute.py
and view.html
files
are different. Obviously, we need to run Bokeh in the compute function.
Normally, Bokeh stores the HTML code for the plot in a file
with a specified name. We can load the text in this file and
extract the relevant HTML code for a plot. However, it is easier to
use Bokeh tools for returning the HTML code elements directly.
The steps are exemplified in the compute.py
file:
from numpy import exp, cos, linspace
import bokeh.plotting as plt
import os, re
def damped_vibrations(t, A, b, w):
return A*exp(-b*t)*cos(w*t)
def compute(A, b, w, T, resolution=500):
"""Return filename of plot of the damped_vibration function."""
t = linspace(0, T, resolution+1)
u = damped_vibrations(t, A, b, w)
# create a new plot with a title and axis labels
TOOLS = "pan,wheel_zoom,box_zoom,reset,save,box_select,lasso_select"
p = plt.figure(title="simple line example", tools=TOOLS,
x_axis_label='t', y_axis_label='y')
# add a line renderer with legend and line thickness
p.line(t, u, legend="u(t)", line_width=2)
from bokeh.resources import CDN
from bokeh.embed import components
script, div = components(p)
head = """
<link rel="stylesheet"
href="http://cdn.pydata.org/bokeh/release/bokeh-0.9.0.min.css"
type="text/css" />
<script type="text/javascript"
src="http://cdn.pydata.org/bokeh/release/bokeh-0.9.0.min.js">
</script>
<script type="text/javascript">
Bokeh.set_log_level("info");
</script>
"""
return head, script, div
The key data returned from compute
consists of a text for loading Bokeh
tools in the head
part of the HTML document (common for all plots in
the file) and for the plot itself there is a script
tag and a div
tag. The
script
tag can be placed anywhere, while the div
tag must be placed
exactly where we want to have the plot. In case of multiple plots,
there will be a common script
tag and one div
tag for each plot.
We need to insert the three elements return from compute
,
available in the tuple result
, into the view.html
file.
The link and scripts for Bokeh tools in result[0]
is inserted
in the head
part, while the script and div tags for the plot
is inserted where we want to have to plot.
The complete view.html
file looks like this:
<!DOCTYPE html>
<html lang="en">
<head>
{{ result[0]|safe }}
</head>
<body>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
TeX: {
equationNumbers: { autoNumber: "AMS" },
extensions: ["AMSmath.js", "AMSsymbols.js", "autobold.js"]
}
});
</script>
<script type="text/javascript" src=
"http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>
This web page visualizes the function \(
u(t) = Ae^{-bt}\sin (w t), \hbox{ for } t\in [0,T]
\).
<form method=post action="">
<table>
{% for field in form %}
<tr>
<td class="name">\( {{ field.name }} \) </td>
<td>{{ field(size=12) }}</td>
<td>{{ field.label }}</td>
{% if field.errors %}
<td><ul class=errors>
{% for error in field.errors %}
<li><font color="red">{{ error }}</font></li>
{% endfor %}</ul></td>
{% endif %}
</tr>
{% endfor %}
</table>
<p><input type="submit" value="Compute"></form></p>
<p>
{% if result != None %}
<!-- script and div elements for Bokeh plot -->
{{ result[1]|safe }}
{{ result[2]|safe }}
{% endif %}
</p>
</body>
</html>
A feature of Bokeh plots is that one can zoom, pan, and save to PNG file, among other things. There is a toolbar at the top for such actions.
The controller.py
file is basically the same as before (but simpler
than in the vib2
app since we do not deal with PNG and/or SVG plots):
from model import InputForm
from flask import Flask, render_template, request
from compute import compute
app = Flask(__name__)
@app.route('/vib3', methods=['GET', 'POST'])
def index():
form = InputForm(request.form)
if request.method == 'POST' and form.validate():
for field in form:
# Make local variable (name field.name)
exec('%s = %s' % (field.name, field.data))
result = compute(A, b, w, T)
else:
result = None
return render_template('view.html', form=form,
result=result)
if __name__ == '__main__':
app.run(debug=True)
Finally, we remark that Bokeh plays very well with Flask. Project 9: Interactive function exploration suggests a web app that combines Bokeh with Flask in a very interactive way.
The pandas-highcharts package is another strong alternative to Bokeh for interative plotting in web pages. It is a stable and widely used code.