web アプリケーションで matplotlib の図を表示する


今は多分 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」とかが相応しいでしょう。

```python
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)
```

こんな感じで。

![iminashi_graph.png](https://qiita-image-store.s3.amazonaws.com/0/29846/d2b9bde8-e578-943e-c6cb-04936edb14f7.png "iminashi_graph.png")

web アプリで表示する
----------
処理の流れはこんな感じです。

1. 図表の png データをつくる
2. データを cStringIO.StringIO のバッファに書き込む
3. 画像コンテンツとしてデータをレスポンスで返す

たぶん、コードを見たほうが早いでしょう。

```python
@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 ブラウザからアクセスすれば、画像が表示されます。

![iminashi_graph_on_brw.png](https://qiita-image-store.s3.amazonaws.com/0/29846/e27d73cd-b9d8-7666-92f2-b9b44e2a63cd.png "iminashi_graph_on_brw.png")

画像ファイルを一時ファイルとして作る
========
もう1この方のやつです。

処理はこう。

1. 図表を png ファイルとして書き出す
2. png ファイルをレスポンスで返す
3. png ファイルを消す

おもしろいのは 3. でしょう。
複数からの同時アクセスなどを考えると、ファイル名はランダム文字列などにするのがいいような気がします。
ただその場合、どんどんファイルが増えていっちゃうので、ファイルを表示した後にちゃんと消さないといけません。

さっきのと同じグラフを使います。
グラフのタイトルは「IMINASHI GRAPH 2」とかが相応しいでしょう。

```python
@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っくなイメージなの。

![iminashi_graph_on_brw_2.png](https://qiita-image-store.s3.amazonaws.com/0/29846/d3df2c72-cd8c-7bef-15de-2f91a412fe79.png "iminashi_graph_on_brw_2.png")

表示されました。
ファイルにしてしまった方が使いやすいかも。