62

I'm very new to Flask and Matplotlib. I'd like to be able to show a simple chart I generated in some html, but I'm having a very hard time figuring out how. Here is my Python code:

from flask import Flask, render_template
import numpy as np
import pandas
import matplotlib.pyplot as plt

app = Flask(__name__)
variables = pandas.read_csv('C:\\path\\to\\variable.csv')
price =variables['price']


@app.route('/test')
def chartTest():
    lnprice=np.log(price)
    plt.plot(lnprice)
    return render_template('untitled1.html', name = plt.show())

if __name__ == '__main__':
   app.run(debug = True)

And here is my HTML:

<!doctype html>
<html>
   <body>

      <h1>Price Chart</h1>

      <p>{{ name }}</p>

      <img src={{ name }} alt="Chart" height="42" width="42">

   </body>
</html>
SVill
  • 331
  • 5
  • 22
  • 55
  • 2
    Unfortunately it does not simply work that way, you will need to save the plot as an image/vector first (either to disk or store in memory) so it can be loaded in the browser. This involves a few more things, but you can get started looking at how to save it first here https://stackoverflow.com/a/29931148/9802392 and then check how to serve it from Flask here https://stackoverflow.com/questions/20646822/how-to-serve-static-files-in-flask – d parolin Jun 06 '18 at 19:48
  • Thank you, I'll get right to reading through those. – SVill Jun 06 '18 at 19:49
  • 2
    Comment here if you get stuck, ideally showing some progress of what you have tried in the question, serving a file from Flask has several solutions and is prerequisite that your html can show/reach that file. Your html is not necessarily wrong completely, you cannot just pass a Python object to the view, so what you pass as {{ name }} should be a URL. And you want that to be in "" too. Hope you succeed. – d parolin Jun 06 '18 at 19:56

2 Answers2

98

You can generate the image on-the-fly in Flask URL route handler:

import io
import random
from flask import Response
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure

@app.route('/plot.png')
def plot_png():
    fig = create_figure()
    output = io.BytesIO()
    FigureCanvas(fig).print_png(output)
    return Response(output.getvalue(), mimetype='image/png')

def create_figure():
    fig = Figure()
    axis = fig.add_subplot(1, 1, 1)
    xs = range(100)
    ys = [random.randint(1, 50) for x in xs]
    axis.plot(xs, ys)
    return fig

Then you need to include the image in your HTML template:

<img src="/plot.png" alt="my plot">
Messa
  • 24,321
  • 6
  • 68
  • 92
  • 5
    This won't show changing dynamic plot content reliably due to browser caching. For static plots that won't change on reload, this is fine – PathToLife Jun 24 '19 at 17:39
  • 4
    You can (and should, if the data are dynamic) add appropriate http headers to disable caching. Ultimately, if you need the data to be “realtime”, you should choose different approach – feed the data to the browser via websocket and plot them using JavaScript (d3.js or similar). – Messa Jun 24 '19 at 18:38
  • 1
    I am trying to adapt `plot_png()` to receive arguments *xs, ys* but there is a `TypeError: plot_png() missing 2 required positional arguments: 'xs' and 'ys'`. I just pasted the same random generated samples in the `main()` instead of inside the `create_figure()`. What's wrong? – aerijman Nov 07 '19 at 16:16
  • A flask app might be called from uwsgi using multiple threads. Is the above usage of matplotlib thread-safe? – sauerburger Nov 13 '19 at 21:41
  • is there a way to change png with a gif using a similar method? – Saba Jan 07 '20 at 15:16
  • Doesn't work for me as of Flask 1.1.2 – Kris Stern Nov 27 '20 at 11:38
  • 2
    @KrisStern This didn't work for me initially either, using flask 2.0.1. But I changed line 5 from `from matplotlib.figure import Figure` to `from matplotlib import pyplot as plt`. After that I removed the first line of `plot_png` and changed the line before the return to `FigureCanvas(plt.gcf()).print_png(output)`. Worked like a dream. – Eric Ed Lohmar Jul 16 '21 at 20:20
10

As @d parolin pointed out, the figure generated by matplotlib will need to be saved before being rendered by the HTML. In order to serve images in flask by HTML, you will need to store the image in your flask file directory:

static/
  images/
    plot.png --> store plots here
templates/

Therefore, in your application, use plt.savefig:

@app.route('/test')
def chartTest():
  lnprice=np.log(price)
  plt.plot(lnprice)   
  plt.savefig('/static/images/new_plot.png')
  return render_template('untitled1.html', name = 'new_plot', url ='/static/images/new_plot.png')

Then in untitled1.html:

  <p>{{ name }}</p>

  <img src={{ url}} alt="Chart" height="42" width="42">
Neel Sandell
  • 429
  • 5
  • 12
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
  • 3
    There is no need to save the image. In order to display it in HTML you can put the data directly into src argument of img tag. – MaciekS Jun 16 '19 at 09:40
  • @MaciekS This sounds like a good idea. Please show a code example. – mac13k Dec 17 '19 at 20:13
  • 5
    @mac13k It is difficult to put the code here, so I shared it as a [GitLab snippet](https://gitlab.com/snippets/1924163). – MaciekS Dec 20 '19 at 14:27
  • 1
    If anyone reading this gets `NSInternalInconsistencyException`, avoid using Matplotlib GUI. This is done by importing the `Agg` - see this answer: https://stackoverflow.com/a/15713545/13055097 – Saurabh Nov 30 '20 at 05:52