今は多分 JavaScript のライブラリでかっこいいグラフとかをひょいひょい作ってくれたりすんじゃないかな、と思うのですが、いや、よく知らないですが、やはり使い慣れた matplotlib を web アプリでも使いたいです。
でも、いかんせん matplotlib は X11 を使って描画するものですので、web アプリで使うのは難しいのではないか、と思ったら、そんなことはありません。
バックエンドに AGG (Anti-Grain Geometry) を使い、画像データを作ります。
やること
matplotlib で作ったグラフを flask web アプリケーションで描画します。
flask を使う理由は、便利だから、ぐらいなので、使わなくても同じようにすれば同じようにできると思います。
また、以下の2パターンでやります。
- png データをレスポンスで返す
- 画像ファイルを一時ファイルとして作る
png データをレスポンスで返す
図表をつくる
とりあえず何か表示する用の図表が必要なので、
- x軸: 1から284までの数値
- y軸: xにランダムで436から875をかけた数値
というグラフを作ります。
これらの数字に深い意味は無いように見えますが、実際ありません。
グラフのタイトルは「IMINASHI GRAPH」とかが相応しいでしょう。
fig, ax = matplotlib.pyplot.subplots()
ax.set_title(u'IMINASHI GRAPH')
x_ax = range(1, 284)
y_ax = [x * random.randint(436, 875) for x in x_ax]
ax.plot(x_ax, y_ax)
こんな感じで。
web アプリで表示する
処理の流れはこんな感じです。
- 図表の png データをつくる
- データを cStringIO.StringIO のバッファに書き込む
- 画像コンテンツとしてデータをレスポンスで返す
たぶん、コードを見たほうが早いでしょう。
@app.route('/graph1')
def graph1():
import matplotlib.pyplot
from matplotlib.backends.backend_agg import FigureCanvasAgg
import cStringIO
import random
fig, ax = matplotlib.pyplot.subplots()
ax.set_title(u'IMINASHI GRAPH')
x_ax = range(1, 284)
y_ax = [x * random.randint(436, 875) for x in x_ax]
ax.plot(x_ax, y_ax)
canvas = FigureCanvasAgg(fig)
buf = cStringIO.StringIO()
canvas.print_png(buf)
data = buf.getvalue()
response = make_response(data)
response.headers['Content-Type'] = 'image/png'
response.headers['Content-Length'] = len(data)
return response
これで、web ブラウザからアクセスすれば、画像が表示されます。
画像ファイルを一時ファイルとして作る
もう1この方のやつです。
処理はこう。
- 図表を png ファイルとして書き出す
- png ファイルをレスポンスで返す
- png ファイルを消す
おもしろいのは 3. でしょう。
複数からの同時アクセスなどを考えると、ファイル名はランダム文字列などにするのがいいような気がします。
ただその場合、どんどんファイルが増えていっちゃうので、ファイルを表示した後にちゃんと消さないといけません。
さっきのと同じグラフを使います。
グラフのタイトルは「IMINASHI GRAPH 2」とかが相応しいでしょう。
@app.route('/graph2')
def graph2():
import matplotlib.pyplot
from matplotlib.backends.backend_agg import FigureCanvasAgg
import random
import string
import os
class TempImage(object):
def __init__(self, file_name):
self.file_name = file_name
def create_png(self):
fig, ax = matplotlib.pyplot.subplots()
ax.set_title(u'IMINASHI GRAPH 2')
x_ax = range(1, 284)
y_ax = [x * random.randint(436, 875) for x in x_ax]
ax.plot(x_ax, y_ax)
canvas = FigureCanvasAgg(fig)
canvas.print_figure(self.file_name)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
os.remove(self.file_name)
chars = string.digits + string.letters
img_name = ''.join(random.choice(chars) for i in xrange(64)) + '.png'
with TempImage(img_name) as img:
img.create_png()
return send_file(img_name, mimetype='image/png')
レスポンスを返した後に削除する方法はいくつか考えられますが、この例ではコンテキストマネージャを使って破棄しています。
なんかこうするのが一番Pythonっくなイメージなの。
表示されました。
ファイルにしてしまった方が使いやすいかも。