将Matplotlib图表传递给HTML(flask)

40

我正在使用matplotlib在一个Web应用程序中呈现一些图形。之前我在运行脚本时使用过fig.savefig()。然而,现在我需要一个函数来返回实际的".png"图像,以便我可以在HTML中调用它。

更多信息(可能是不必要的):我正在使用Python Flask。我想我可以使用fig.savefig(),把图形放在我的静态文件夹中,然后从HTML中调用它,但我不想每次都这样做。最理想的情况是,我可以创建图形,将其制作成图像,返回该图像,并从HTML中调用它,然后它就消失了。

创建图形的代码已经可以工作。然而,它返回的是一张图片,似乎无法在HTML中使用。

这是我在路由中调用draw_polygon的地方,draw_polygon是返回该图形的方法:

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

这里是我尝试生成图片的HTML代码。

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

而且,您可能已经猜到了,当我加载页面时,我只会得到图像占位符。因此,他们不喜欢我用的图形格式。

有人知道matplotlib的方法/解决方案可以将图形转换为实际图像吗?我已经查看了所有文档,但找不到任何信息。谢谢!

顺便说一下:我认为没有必要包括创建图形的Python代码,但如果您需要查看它(我只是不想使问题混乱),我可以包括它。


最近有一些工作是为了让mpl与Google AppEngine兼容,相关讨论包括如何做这样的事情的示例。另一个选项是像IPython笔记本那样将PNG转换为字符串,然后直接嵌入它。 - tacaswell
6个回答

37

你需要将HTML和图像分为两个不同的路由。

你的/images/<cropzonekey>路由只会提供页面,而在该页面的HTML内容中,将会有一个指向第二个路由的引用,即提供图像的路由。

图像将从一个内存文件中的自己的路由提供,该文件是使用savefig()生成的。

显然我没有测试过这个,但我相信以下示例将原样工作,或者将使你接近一个可行的解决方案:

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

您的images.html模板变成了:

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

4
首先,Miguel,我想说你的Flask教程真是太棒了,非常赞!但是,当我导航到那个页面时,我仍然看到了占位文本。这可能与Flask无关吗(即draw_polygons(cropzonekey)返回的格式有问题吗)? - user2497586
安装完我的更改后,请在浏览器中导航到 http://localhost:5000/fig/cropzonekey。你能看到图片吗? - Miguel Grinberg
哇,是的,它起作用了。只是没有重定向到正确的位置,但我可以修复它。非常感谢,完美的答案! - user2497586
@Miguel 感谢您的快速回复。也许一个解决方案(或更多的变通方法?)是使用tornado.web.FallbackHandler(参见此问题)。我想我会尝试这个,或者您有更简单的方法吗? - bmu
1
当我将img = StringIO()更改为img = BytesIO()时,它对我起作用了。 - user5012123
显示剩余9条评论

8

Python 3

在使用matplotlib与flask并在python3中的html页面上渲染图形时,我经常会遇到像这样的错误:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'NSWindow drag regions should only be invalidated on the Main Thread!

对于所有想要在python 3中使用matplotlib与flask并在html页面上呈现图形的人,下面是解决方法 -

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

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


7

对于Python3 ....

我有一个DataFrame,我想在Flask中展示这个图表....

因此要创建一个Base64图像。

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

我现在使用base64编码的数据来调用我的模板,如下所示...。 return render_template('where.html', form=form, sunalt=sunalt2) 模板的相关部分(即图片部分)如下...。
 {% if sunalt != None %}

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

希望这能够帮助到某些人...

3
我正在使用Python 3.x进行工作,我更改了一些代码行并使其正常运行。我收到了以下错误信息:".....对象没有'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')

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

其他答案是正确的,我只想展示必须包含的头文件。 该程序创建一个简单的图表并将其发送到HTML页面。

1
这对我来说很有效,您也可以查看此URL medium博客
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)
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接