Skip to content

Instantly share code, notes, and snippets.

@ryxcommar
Last active August 15, 2020 21:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ryxcommar/f6d2ab7a8dc86cc0c346b3b9c9ea3967 to your computer and use it in GitHub Desktop.
Save ryxcommar/f6d2ab7a8dc86cc0c346b3b9c9ea3967 to your computer and use it in GitHub Desktop.
How to render static Matplotlib in Flask
"""Static Matplotlib Figures in Flask - Tutorial
To run:
1. Copy this file.
2. Make sure the dependencies are installed (see after the docstring)
3. Run in the command line: >>> python mpl_in_flask.py
4. Fire up your browser and view the site (default: http://127.0.0.1:5000/)
If you want to implement this solution:
1. Copy+paste ``matplotlib.use('agg')`` into whatever file your ``app`` or
``create_app`` is defined in. Note that this MUST be above any imports of
``pyplot``. Also, you only need to do this once.
If you ever experience any weird ``UserWarning``'s coming from Matplotlib,
your first troubleshooting step should be to make sure that
``matplotlib.use('agg')`` is being run prior to ``import matplotlib.pyplot``.
2. Copy+paste ``mpl_to_html``, ``_mpl_to_png_bytestring``, and ``render_mpl``
into your app/blueprint file (for smaller websites) or a utils file or folder
(for larger websites).
3. Copy+paste the ``close_mpl_plot`` function plus its decorator into the file
or ``create_app`` function in which you define the Flask ``app``.
4. (Optional) Add ``render_mpl`` to the Jinja environment. This allows for
Matplotlib figures and subplots to be passed into ``flask.render_template``
calls and then rendered using ``render_mpl``.
The functions are self-contained and do not use any module-level imports, so you
only need to copy paste the functions and not any other stuff.
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Please note: This is not an amazing solution to the problem of rendering
Matplotlib figures in Flask, but that's because getting an amazing solution
requires lots of idiosyncratic setup depending on the use case and your app's
code structure.
This solution is designed to be a generalizable, portable, and lightweight
approach. It's more complete than anything you'll find on StackOverflow, but
that doesn't mean it's truly complete.
I don't foresee any problems with this code, but caveat emptor regardless.
More advanced solutions will depend on your Flask app's setup. Please read this
blog post for more information and for better solutions if you want to put in
some elbow grease:
https://ryxcommar.com/<<FORTHCOMING>>
"""
# !pip install numpy
# !pip install matplotlib
# !pip install seaborn
# !pip install flask
from flask import Flask
app = Flask(__name__)
################################################################################
## Below lies all the stuff you'll need to copy and paste.
################################################################################
# (1)
import matplotlib
matplotlib.use('agg') # This needs to happen before any pyplot import!!!
# (2)
def _mpl_to_png_bytestring(fig):
"""This function uses Matplotlib's FigureCanvasAgg backend to convert a MPL
figure into a PNG bytestring. The bytestring is not encoded in this step.
"""
import io
import matplotlib.pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg
if isinstance(fig, plt.Axes):
fig = fig.figure
output = io.BytesIO()
FigureCanvasAgg(fig).print_png(output)
return output.getvalue()
def mpl_to_html(fig, **kwargs):
"""Take a figure and render it directly to HTML. A PNG is created, and
then encoded into base64 and decoded back to UTF-8 so that it can be stored
inside a <img> HTML tag.
``kwargs`` supports arbitrary HTML attributes. An example usage of kwargs:
>>> render_mpl(fig, style='width:480px; height:auto;')
"""
from flask import Markup
import base64
bstring = _mpl_to_png_bytestring(fig)
png = base64.b64encode(bstring).decode('utf8')
options = ' '.join([f'{key}="{val}"' for key, val in kwargs.items()])
return Markup(f'<img src="data:image/png;base64,{png}" {options}>')
def render_mpl(fig):
"""This function returns a png file from a Matplotlib figure or subplots
object. It is designed to be at the bottom of an endpoint function; instead
of returning HTML or ``render_template()``, you return this instead.
"""
from flask import Response
return Response(_mpl_to_png_bytestring(fig), mimetype='image/png')
# (3)
# This should go inside the file or function you declare your app = Flask(...).
@app.after_request
def close_mpl_plot(response):
"""This prevents memory leakage; Matplotlib's pyplot API is stateful, which
can be a burden for a website that runs for a while.
"""
import matplotlib.pyplot as plt
plt.close('all')
return response
# (4)
# Not used in this example, but it's a nice-to-have:
app.jinja_env.globals.update(mpl_to_html=mpl_to_html)
################################################################################
## ... And that's it! No more copy+pasting required. :)
##
## Everything below is just an example of how to use the above functions.
################################################################################
@app.route('/')
def index():
# Source:
# https://matplotlib.org/gallery/lines_bars_and_markers/simple_plot.html
import numpy as np
import matplotlib.pyplot as plt
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
fig, ax = plt.subplots()
ax.plot(t, s)
ax.set(
xlabel='time (s)',
ylabel='voltage (mV)',
title='About as simple as it gets, folks'
)
# Now time for the HTML. Note how the ``fig`` generated above is wrapped
# with the ``render_mpl`` function.
from flask import url_for
return '\n'.join([
'<!DOCTYPE html>',
'<head><title>Hello World MPL in Flask</title></head>',
'<body>'
'<p>Hello, world!</p>',
'<p>This is regular old HTML. This MPL figure is also dumped directly '
"into the HTML, meaning there's no URL for it:",
'<br />',
mpl_to_html(fig),
'<p>See how easy that was?</p>',
'<p>Here is another example, but done a bit differently: this one has '
'an endpoint associated with it, and it was made using Seaborn '
'instead of base Matplotlib:</p>',
'<img src="{}">'.format(url_for('seaborn_example')),
"<p>Don't be shy: You can inspect the source code of this page!</p>",
'</body>'
])
@app.route('/seaborn.png')
def seaborn_example():
# Source:
# https://seaborn.pydata.org/examples/simple_violinplots.html
import numpy as np
import seaborn as sns
sns.set()
# Create a random dataset across several variables
rs = np.random.RandomState(0)
n, p = 40, 8
d = rs.normal(0, 2, (n, p))
d += np.log(np.arange(1, p + 1)) * -5 + 10
# Use cubehelix to get a custom sequential palette
pal = sns.cubehelix_palette(p, rot=-.5, dark=.3)
# Show each distribution with both violins and points
graph = sns.violinplot(data=d, palette=pal, inner='points')
return render_mpl(graph)
if __name__ == '__main__':
app.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment