Passing a matplotlib figure to HTML (flask)

Question:

I am using matplotlib to render some figure in a web app. I’ve used fig.savefig() before when I’m just running scripts. However, I need a function to return an actual “.png” image so that I can call it with my HTML.

Some more (possibly unnecessary) info: I am using Python Flask. I figure I could use fig.savefig() and just stick the figure in my static folder and then call it from my HTML, but I’d rather not do that every time. It would be optimal if I could just create the figure, make an image out of it, return that image, and call it from my HTML, then it goes away.

The code that creates the figure works. However, it returns a figure, which doesn’t work with HTML I guess.

Here’s where I call the draw_polygon in the routing, draw_polygon is the method that returns the figure:

@app.route('/images/<cropzonekey>')
def images(cropzonekey):
    fig = draw_polygons(cropzonekey)
    return render_template("images.html", title=cropzonekey, figure = fig)

And here is the HTML where I am trying to generate the image.

<html>
  <head>
    <title>{{ title }} - image</title>
  </head>
  <body>
    <img src={{ figure }} alt="Image Placeholder" height="100">
  </body>
</html>

And, as you can probably guess, when I load the page, all I get is Image Placeholder. So, they didn’t like the format I fed the figure in with.

Anyone know what matplotlib methods/work-arounds turn a figure into an actual image? I am all over these docs but I can’t find anything. Thanks!

BTW: didn’t think it was necessary to include the python code that makes the figure, but I can include it if You guys need to see it (just didn’t want to clutter the question)

Asked By: Alex Chumbley

||

Answers:

You have to separate the HTML and the image into two different routes.

Your /images/<cropzonekey> route will just serve the page, and in the HTML content of that page there will be a reference to the second route, the one that serves the image.

The image is served in its own route from a memory file that you generate with savefig().

I obviously didn’t test this, but I believe the following example will work as is or will get you pretty close to a working solution:

@app.route('/images/<cropzonekey>')
def images(cropzonekey):
    return render_template("images.html", title=cropzonekey)

@app.route('/fig/<cropzonekey>')
def fig(cropzonekey):
    fig = draw_polygons(cropzonekey)
    img = StringIO()
    fig.savefig(img)
    img.seek(0)
    return send_file(img, mimetype='image/png')

Your images.html template the becomes:

<html>
  <head>
    <title>{{ title }} - image</title>
  </head>
  <body>
    <img src="{{ url_for('fig', cropzonekey = title) }}" alt="Image Placeholder" height="100">
  </body>
</html>
Answered By: Miguel Grinberg

I am working with Python 3.x, I have changed some lines of the code and it worked for me. I had the following error message: “…..object has no attribute ‘savefig'”

@app.route('/fig/<cropzonekey>')

def fig(cropzonekey):
    #fig = draw_polygons(cropzonekey)
    fig = plt.plot([1,2,3,4], [1,2,3,4])
    #img = StringIO()
    img = BytesIO()
    #fig.savefig(img)
    plt.savefig(img)
    img.seek(0)
    return send_file(img, mimetype='image/png')
Answered By: Rodolfo Alvarez
from flask import Flask, send_file
from io import StringIO
import matplotlib.pyplot as plt
from StringIO import StringIO
@app.route('/fig/')
def fig():
      plt.plot([1,2,3,4], [1,2,3,4])
      img = StringIO()
      plt.savefig(img)
      img.seek(0)
      return send_file(img, mimetype='image/png')

The other answers are correct ,I just wanted to show the header files that has to be included.
This program creates a simple graph and sends it over to the html page.

Answered By: Sneha Ravichandran

For Python3 ….

I have a DataFrame, I want to show this plot in Flask ….

So Create a Base64 Image of the plot.

    df_week_min_az = pd.DataFrame.from_dict(week_max_az.to_dict(),
                                            orient='index', columns=['min_az'])



    sunalt = df_week_max_angle.plot().get_figure()
    buf = io.BytesIO()
    sunalt.savefig(buf, format='png')
    buf.seek(0)
    buffer = b''.join(buf)
    b2 = base64.b64encode(buffer)
    sunalt2=b2.decode('utf-8')

I now call my template using the base64 encoded data like this….

return render_template('where.html', form=form, sunalt=sunalt2)

The relevant part of the template (i.e. the picture bit) looks like this….

 {% if sunalt != None %}

      <h2>Sun Altitude during the year</h2>
    <img src="data:image/png;base64,{{ sunalt }}">
{% endif %}

Hope that helps someone….

Answered By: Tim Seed

Python 3

I went through a lot of trouble with errors like –
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSWindow drag regions should only be invalidated on the Main Thread!

For all those who want to use matplotlib with flask and render the graph on an html page in python 3, here you go –

In the __init__.py

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from flask import Flask, render_template
from io import BytesIO
import base64

    @app.route('/plot')
    def plot():
        img = BytesIO()
        y = [1,2,3,4,5]
        x = [0,2,1,3,4]

        plt.plot(x,y)

        plt.savefig(img, format='png')
        plt.close()
        img.seek(0)
        plot_url = base64.b64encode(img.getvalue()).decode('utf8')

        return render_template('plot.html', plot_url=plot_url)

In flaskr/templates/plot.html

<!doctype html>
<title>heatmap - </title>
<section>
  <h2>Heatmap</h2>
  <img src="data:image/png;base64, {{ plot_url }}">
</section>

Answered By: akkhil

This works pretty well for me, you can also check this URL medium blog

from flask import Flask, render_template
from PIL import Image
import base64
import io

app = Flask(__name__)

@app.route('/')
def show_image():


    # Your plt plots instructions here 
    # plt.save('generated_plot.png') 

    im = Image.open("generated_plot.png") #Open the generated image
    data = io.BytesIO() 
    im.save(data, "png")
    encoded_img_data = base64.b64encode(data.getvalue())

    return render_template("show_image.html", img=encoded_img_data.decode('utf-8'))


if __name__ == '__main__':


   app.run(debug=True)
    
Answered By: Mohamed Berrimi
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.