Matplotlib描画画像をFlaskによるWebアプリにてBytesIO を介して直接表示する方法

# 本日は

Matplotlibで作成したグラフをFlaskで作ったWebアプリに埋め込みたい．
タイトルが長々しい．日本語難しい．

## 調べると多くのReferenceが存在する．

- [web アプリケーションで matplotlib の図を表示する](https://qiita.com/5t111111/items/3d9efdbcc630daf0e48f)
- [Flaskを使ってMatplotlibグラフを表示する](http://hope-is-dream.hatenablog.com/entry/2016/12/06/_Flask%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6Matplotlib%E3%82%B0%E3%83%A9%E3%83%95%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B)
- [matplotlib を Flask から使う方法](http://arataka.github.io/2011/10/19/mpl-on-flask.html)

などがあります．　
savefigを使って一度画像として保存してそれを読み込む例が多くみられます．
保存せずに直接行ける方法はないかと探してみたらGitHubで公開している例がありました．

- [Example of rendering a matplotlib image directly to Flask view](https://gist.github.com/wilsaj/862153)

元コードはPython2系のものですが，　 tacaswell 氏による Python3 パッチを当てていました．

## 問題は

サーバーサイドのコードだけあってもどう使うか，どうやってWeb側に渡すんだべ？と悶々としてました．　どうやって調べたかは定かではないですが，

- [Python3でPillowを使ってURLから画像を読み込む](https://qiita.com/ru_pe129/items/b355ebfc82d38bc49778)
- [画像をBase64でHTMLファイルに直接埋め込む方法](https://edge.sincar.jp/web/base64-inline-image/)

をなどを参考にしながら画像データを渡すことにしました．

## 実装例

一番感心があるところだと思うのでコードを貼り付けましょう．

### Python側

```py3
#app.py
from flask import Flask, render_template
from io import BytesIO
import urllib
from matplotlib.backends.backend_agg import FigureCanvasAgg
from matplotlib.figure import Figure
import numpy as np

app = Flask(__name__)


@app.route("/plot/<func>")
def plot_graph(func='sin'):

    fig = Figure()
    ax = fig.add_subplot(111)
    xs = np.linspace(-np.pi, np.pi, 100)
    if func == 'sin':
        ys = np.sin(xs)
    elif func == 'cos':
        ys = np.cos(xs)
    elif func == 'tan':
        ys = np.tan(xs)
    else:
        ys = xs
    ax.plot(xs, ys)
    canvas = FigureCanvasAgg(fig)
    png_output = BytesIO()
    canvas.print_png(png_output)
    img_data = urllib.parse.quote(png_output.getvalue())
    return img_data


@app.route("/")
def index():
    return render_template("index.html", img_data=None)

if __name__ == "__main__":
    app.run(debug=True, port=9999)
```

### HTML側

```html
<!--index.html-->
<!DOCTYPE html>
<html>

<head>
    <title>Flask with matplotlib</title>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
    <script type="text/javascript">
    function drawGraph(obj) {
        var idx = obj.selectedIndex;
        var value = obj.options[idx].value;
        var plotdata = document.getElementById('plotimg');
        $.get("/plot/" + value, function(data) {
            plotdata.src = "data:image/png:base64," + data;
        });
    };
    $(document).ready(function() {
        //initialize components
        var target = document.getElementById('selector');
        drawGraph(target);
    });
    </script>
</head>

<body>
    <h1>Plot Graph</h1>
    <div>
        <p>select a function</p>
        <select id=selector onchange="drawGraph(this)">
            <option value="sin">sin</option>
            <option value="cos" selected>cos</option>
            <option value="tan">tan</option>
        </select>
    </div>
    <br/>
    <img id=plotimg></img>
</body>

</html>
```

HTML内で閉じるように書いています．もちろんJSとファイルを別に分離しても良いです．私はWeb屋ではないのでここら辺は雑になりがちです．　編集リクエストがありましたら順次対応してまいります．
### Remark
Flaskをお使いの方はご存知だと思いますが，　
ディレクトリの構造は次のようになります：

```
.
├── app.py
└── templates
    └── index.html
```

すなわち， `app.py` と同階層に`templates`　というフォルダを作成してその直下に `index.html` を配置するというものになります．


### 振る舞いについて
このコードは外部ライブラリとしてjQueryを用いています．

ブラウザを立ち上げた時に

```js
$(document).ready(function()){}
```
で定義した挙動が行われます．内部では`drawGraph `が呼び出されます．これはさらに

```js
$.get("/plot/" + value, function(data)
```
を呼び出し

サーバー側であるPythonのコードを呼び出します：

```py3
@app.route("/plot/<func>")
def plot_graph(func='sin'):
   #do something
```

## 実行例

```shell-session
$ python app.py
```
この後ブラウザを立ち上げて `127.0.0.1:9999` にアクセス

<img width="647" alt="Screen Shot 2017-09-26 at 18.37.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/135978/50c1d11e-8906-c9cd-de8c-4757e3e810a4.png">

selectタグ　`selector`　の初期値は`value=cos`となっているため，　`plot_graph` に渡される引数の値は　`cos` です．

画面左上のセレクタは `sin` `cos` `tan` と選べます．選択するごとに `drawGraph` がイベントハンドラとして呼び出されるので，対応する関数が描画されます．

## 最後に．．．

- CSS でもっと修飾させればかっこよくなるかも．
- Webアプリ前提としてグラフを描画する機能を提供するライブラリは
[Bokeh](https://bokeh.pydata.org/en/latest/) だったり [Dash](https://plot.ly/products/dash/) (というかplotly) があります．
Matplotlibだけが全てじゃないので個々の開発者にとって使いやすいものを適宜チョイスしていきませう．