Passing a matplotlib figure to HTML (flask)

40,542

Solution 1

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>

Solution 2

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>

Solution 3

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....

Solution 4

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')

Solution 5

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.

Share:
40,542
Alex Chumbley
Author by

Alex Chumbley

I am currently a computer science student. I just like learning new stuff, this seems like the right place to be. Thanks to everyone that makes this such a good community.

Updated on July 09, 2022

Comments

  • Alex Chumbley
    Alex Chumbley almost 2 years

    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)

  • Alex Chumbley
    Alex Chumbley over 10 years
    Miguel, first of all, just wanna say that your flask tutorial is utterly amazing. +1 just for that. But, I'm still getting the placeholder text when I navigate to that page. Is it perhaps a problem that has nothing to do with flask (i.e. the format that is returned by draw_polygons(cropzonekey)?
  • Miguel Grinberg
    Miguel Grinberg over 10 years
    After installing my changes navigate to http://localhost:5000/fig/cropzonekey in your browser. Do you see the image then?
  • Alex Chumbley
    Alex Chumbley over 10 years
    Wow, yes it worked. Just didn't re-route to the right place, but I can fix that. Thanks so much, perfect answer!
  • bmu
    bmu over 10 years
    @Miguel Do you see any change to integrate the matplolib web_agg backend with flask? Here is an example that integrates it in Tornado, however I can't figure out how to integrate it in flask.
  • Miguel Grinberg
    Miguel Grinberg over 10 years
    @bmu: You need web sockets for that, so the port effort is non-trivial.
  • bmu
    bmu over 10 years
    @Miguel Thanks for the quick response. Maybe one solution (or more a workaround?) would be to use a tornado.web.FallbackHandler (see this question). I think I will try this, or do you see any simpler way?
  • Miguel Grinberg
    Miguel Grinberg over 10 years
    That's probably the easiest, keep the web sockets specific parts working in Tornado, but add the traditional portion in Flask. I have never tried to combine Tornado with Flask myself so I can't comment from experience, but Tornado has a WSGI compatibility layer so it seems this would work just fine.
  • flash
    flash over 9 years
    @MIguel, I am trying to do this but it's not working. I have a form with the action that leads to images.html. in images.html, I have my img src pointing to the "fig". But I get the error "The method is not allowed for the requested URL." I know it's because my request is "GET" and not "POST". Any advice on this?
  • Miguel Grinberg
    Miguel Grinberg over 9 years
    @flash, I don't really understand the problem, but if you have a form, then why don't you make it submit with a POST request?
  • flash
    flash over 9 years
    @Miguel my form does submit with a post request. I think Alex is getting the same issue. He can see the image when he navigates to localhost:5000/fig/cropzonekey, because that calls the route that generates the image. But when you navigate to localhost:5000/images/cropzonekey the image isn't displayed because in the img tag source (url_for('.fig')) that route is never called for some reason. At least that's whats happening with me. I tried both url_for('fig') and url_for('.fig')
  • Miguel Grinberg
    Miguel Grinberg over 9 years
    @flash: are you passing the cropzonekey argument to url_for? In any case, if you inspect the <img> tag in generated HTML you can probably figure out what the problem is.
  • flash
    flash over 9 years
    @Miguel I did that, and it says the method that is being used to when the '/fig' page is being called is 'GET'. I need to make a request within the img tag to use the method 'POST'. Because my error is '405 Method not allowed". But I haven't been able to do that.
  • Admin
    Admin almost 7 years
    Worked for me when I switched img = StringIO() to img = BytesIO()
  • KcH
    KcH over 4 years
    Can't we approach without saving as an image?