【Python】FlaskでWebアプリケーションを作る⑥ -matplotlibのグラフをWeb画面に描画する

前回はFlaskでPandasデータフレームを表示する方法を紹介しました。

今回は、Flaskでmatplotlibのグラフを表示してみます。前回と同じ流れでPandasデータフレームを取得し、そのデータを使ってグラフを描画します。

前提

Pythonパッケージ

今回使用するPythonパッケージは以下の通りです。

  • Flask
  • Pandas
  • psycopg2(PostgreSQL操作用)
  • matplotlib

DB

今回はPostgreSQLを用います。”dvdrental”というデータベースを用意し、支払情報テーブルからデータを取得します。

"dvdrental"のpaymentテーブル

実装

 まずは今回のコード全体です。pythonはmain.py、結果を表示する画面はresult.htmlに記述しています。

from flask import Flask, render_template, request
import psycopg2
import pandas as pd

import base64
from io import BytesIO

from flask import Flask
from matplotlib.figure import Figure

app = Flask(__name__)

# PostgreSQLへの接続
conn = psycopg2.connect(
    user="postgres",        #ユーザ
    password="postgresql",  #パスワード
    host="localhost",       #ホスト名
    port="5432",            #ポート
    dbname="dvdrental")    #データベース名
 
#PostgreSQLのバージョンを取得し返す   
@app.route('/')
def get_version():
    cur = conn.cursor()
    cur.execute('SELECT version()')
    version = cur.fetchone()[0]
    return f'PostgreSQL version: {version}'

@app.route('/result', methods=["GET"])
def result():
# GET送信の処理
    user_id = request.args.get("user_id","")
    print(user_id)

    sql = "select * from payment where customer_id='{}'".format(user_id)
    
    df = pd.read_sql(sql, conn) 
    
    x = df["payment_date"]
    y = df["amount"]
    
    #グラフ描画
    fig = Figure()
    ax = fig.subplots()
    ax.plot(x, y)
    
    #画像をバッファに保存
    buf = BytesIO()
    fig.savefig(buf, format="png")
    
    #画像データをBase64にエンコード
    data = base64.b64encode(buf.getbuffer()).decode("ascii")
    
  #エンコードしたデータをHTMLに渡す
    return render_template('result.html', table=(df.to_html(classes="mystyle")), data=data)

if __name__ == '__main__':
    app.run(host='localhost')
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>mystyle</title>
    <style type="text/css">
        body {
                font-family: Arial, Verdana, sans-serif;
                font-size: 90%;
                color: #666;
                background-color: #f8f8f8;}
        </style>
</head>
<body>
        <h1>支払情報</h1>    
        {{table | safe}}
        
        <img src="data:image/png;base64,{{data}}"/>
</body>
</html>

冒頭で紹介したFlaskでPandasデータフレームを表示する方法のページと同じ部分は説明を割愛します。

グラフの作成

main.pyのdef result()では、DBからデータを取得しPnadasデータフレームに格納した後、matplotlibでグラフを描画し、そのグラフを返り値としています。

    x = df["payment_date"]
    y = df["amount"]
    
    #グラフ描画
    fig = Figure()
    ax = fig.subplots()
    ax.plot(x, y)
    
    #画像をバッファに保存
    buf = BytesIO()
    fig.savefig(buf, format="png")
    
    #画像データをBase64にエンコード
    data = base64.b64encode(buf.getbuffer()).decode("ascii")
    
  #エンコードしたデータをHTMLに渡す
    return render_template('result.html', table=(df.to_html(classes="mystyle")), data=data)

まずはグラフ描画します。fig.subplots()で描画領域を作成した後、ax.plot()でグラフを描画します。

続いて、今回は画像をバッファ(メモリ)に一時保存します。BytesIOを使えば、画像データをバイト列としてメモリに記憶させることができます。これによって、いちいち画像ファイルとして出力しなくてもよくなります。

続いて、バッファに記憶させた画像データをBase64にエンコードします。画像データをBase64にエンコードすると、テキストデータとして画像をHTMLに埋め込むことができます。そのため画像ファイルの通信負荷が軽減され表示が速くなる等のメリットがあります。

ただし、サイズの大きすぎる画像はエンコードすると長大な文字列となりかえって重くなる可能性があるので、注意が必要です。

Base64にエンコードした画像データは変数dataに格納され、HTMLに渡されます。

<body>
        <h1>支払情報</h1>    
        {{table | safe}}
        
        <img src="data:image/png;base64,{{data}}"/>
</body>

エンコードされた画像も、HTMLではimgタグで表示します。

実行した結果を見てみます。

http://localhost:5000/result?user_id=341

サイトの画面にグラフが埋め込まれていることを確認できました。

まとめ

Flaskでmatplotlibのグラフを表示する方法を紹介しました。matplotlibで作成した画像はバッファに保存して、無駄なファイル生成を抑えることができます。画像のサイズ等考慮して、HTMLとのやり取りの最適化を行ってください。

ではでは👋