医療通訳で汗だくになる医療情報技師のブログ

田舎の病院でも外国人患者急増中

医療通訳支援Webアプリ(The record system)にPlotlyから最近出たDashを使ってインタラクティブなグラフを追加した

以下のエントリーの続編です。

healthcareit-interpreter.hatenablog.com

herokuにデプロイしたページは以下になります。

https://englishtranslationrecord.herokuapp.com/f:id:HealthcareIT_interpreter:20170804005557p:plain

コードはGitHubにあげてあります。
①Dashのページのコード

github.com

医療通訳支援Webアプリ(以下、Webアプリ)のコード

github.com

作成手順(概要)

1)Dashのページを作ってherokuにデプロイ
2)WebアプリにDashのページを埋め込む
という2段階になります。今回も前回に引き続き以下のページをベースに作業を進めました。

Making a Flask app using a PostgreSQL database and deploying to Heroku

それからDash用に以下も参照:

Dash User Guide and Documentation - Dash by Plotly

herokuに置いたDB(PostgreSQL)をDashのページとWebアプリとで共有する

参考にしたページ:

devcenter.heroku.com

$ git init
$ git add .
$ git commit -m "initial commit"
$ heroku create etr-dashapp
$ git push heroku master

とした後に、データベースの手続きをします。ちなみにetr-dashappというのが今回新規作成するDashのページになります。今回はすでに展開してあったWebアプリ用のDBを使い回したいので、

$ heroku addons:attach englishtranslationrecord::DATABASE --app etr-dashapp

$ heroku pg:promote postgresql-hexagonal-12345 --app etr-dashapp

とやってあげます。englishtranslationrecordが既存のWebアプリの名称ということになります。
postgresql-hexagonal-12345はダミーの名称です。herokuのページ行くと確認できる、すでに展開してあるDBの名称になります。
すでに作成済みのDBを使い回すので以下の処理(herokuにDBを新規作成)は不要です。

$ heroku run python
>>> from app import db
>>> db.create_all()
>>> exit()

Procfileの中身

さて上記で、

$ git push heroku master

とやった時点でうまくいけば

https://etr-dashapp.herokuapp.com/

にDashのページが表示されるのですが、Procfileの書き方を間違えていたせいでHeroku deployment error H10 (App crashed)というエラーが出てしまいました。

stackoverflow.com

↑ここにあるように

$ heroku restart

とか色々と試してみたのですがダメでした。頭を抱えた末にたどり着いたページに助けられました:

github.com

このページでは、Procfileの中身は以下のようになっていました。

web gunicorn run:server

うん? runというのはrun.pyのことか。serverというのはrun.py中に記載されたDashのインスタンスのことか。なら自分はdashapp.pyという名称にしていたので

web gunicorn dashapp:server

にしてみるか。動いた!jimmybowに感謝です。 でもよく見たら公式ページにもちゃんと書いてありました。

Procfile
web: gunicorn app:server
(Note that app refers to the filename app.py. server refers to the variable server inside that file).

Webアプリのデプロイのときは純flask製だったのでserverインスタンスではなくappインスタンスだった上、ファイル名称はapp.pyだったので

web: gunicorn app:app

だったとしてもよく分かっていなかったということが露呈した形になります。
app:appはapp.py:appインスタンスということだとようやく分かりました。

WebアプリにDashのページを埋め込む

さて話を本題に戻しましょう。Dashのページはflask本体のようにrender_templateメソッドを(現在のところ)使えないらしいので、
(参考:Dash itself won’t provide compatibility with Jinja templates.)

community.plot.ly

Webアプリ(flaskapp)がrender_templateするindex.html (ここではhome.html)などにiframeを組み込んで、herokuにデプロイしたDashのページのURLをsrcに指定してやる方法が一番簡単そうです。

ということで以下の修正をWebアプリ側に施しました。

<html>

<head>
</head>

<body>
 
<iframe frameborder='0' noresize='noresize' style='position: absolute; background: transparent; width: 100%; height:100%;' src="{{ iframe }}" frameborder="0" id="dash"></iframe>

<script type="text/javascript">
    window.onload = setInterval(function(){
        document.getElementById("dash").src = document.getElementById("dash").src
    }, 60*1000);
</script>

</body>

</html>
@app.route('/')
def home():
    iframe = 'https://etr-dashapp.herokuapp.com/'
    return render_template('home.html', iframe=iframe)

htmlファイルにはJavaScriptで60秒ごとにiframe部分限定のリロードをかけるように仕込んであります。 Dashのページはリロードする度にDBから最新データを取得するように関数化してあります。

def serve_layout():
    getdata()
    return html.Div([
        dcc.Dropdown(
            id='select-person',
            options=[{'label': i, 'value': i} for i in ffc.columns],
            multi=True,
            value=ffc.columns
        ),
        dcc.Graph(
            id='graph-with-range',
            animate=False
        ),
        dcc.Graph(
            id='my-table'
        ),
        dcc.RangeSlider(
            id='month-range',
            marks={str(i): i for i in ffc.index},
            min=ffc.index.min(),
            max=ffc.index.max(),
            value=[ffc.index.min(), ffc.index.max()],
            step=None
        )
    ])

app.layout = serve_layout

しかしDashのコードというのはPythonなのかJavaScriptなのか分からなくなりますね。(実際、HTMLとJavaScriptだけでインタラクティブなページを作ろうとするとこれほど短いコードでは済みませんが。)