【保存版】Python Dash パーフェクトガイド

Dashの使い方完全保存版

基本的な使い方は他の方もたくさん投稿していると思うので、実務で使う便利なテクニックの紹介が多いです。
辞書的に使ってください。

随時更新していくので是非ストック or LGTMしてください！

## 基本

### Dashとは

超簡単におしゃれなWEBアプリが開発できるライブラリ（Flaskベースのライブラリ）
グラフはPlotlyというライブラリを使用しています。
（PlotlyはPython以外にもR、Javascriptなどにも対応しています）

Webアプリのプロトタイプを開発する際や、社内利用のアプリならこれで十分だと思います。

> [Dash Hello World](https://dash.plotly.com/layout)

### 不便な点

* 大規模アプリには向かないかも（DjangoやFlaskの利用を検討）
* Flask render_template が使用できない（Flaskとレンダリングの仕組みが異なるため）
* コードが少し冗長となり、メンテナンス性はあまり良くない(templateが使えないから)
* ドキュメントのほとんどが英語

## 関連ライブラリ

```python
import plotly.express as px
import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html
import dash_auth
import dash_daq as daq
```

### import plotly.express

Plotlyのグラフを利用するために使用

> [Plotly Graph Gallery](https://plotly.com/python/)

### import dash

Dashの基本となるライブラリ

### import dash_bootstrap_components

DashでBootstrapに対応したコントロールを配置する際に必要
正直ボタンなどの各要素は、こっちで作成する方が見栄えが良い

> [Dash bootstrap components](https://dash-bootstrap-components.opensource.faculty.ai/docs/components/)

### import dash_core_components 

グラフ要素やタイマーを配置するときに使用

> [Dash Core Components (公式ページ)](https://dash.plotly.com/dash-core-components)

### import dash_html_components

Div要素、A要素などを配置するときに使用

> [Dash HTML Components (公式ページ)](https://dash.plotly.com/dash-html-components)

### import dash_auth

dashで基本認証を利用する
（Webページにアクセスした際、ユーザー名・パスワードを入力するポップアップ画面を表示）

### import dash_daq

トグルスイッチやスライダーを配置可能

> [Dash DAQ (公式ページ)](https://dash.plotly.com/dash-daq)

## アプリ基本設定編

### 全てのユーザーのアクセスを許可(PortNo = 5000)

起動時hostパラメータを0.0.0.0に設定。

```python
import dash
app = dash.Dash(__name__)
app.run_server(host='0.0.0.0', port=5000) # bindingを0.0.0.0にすることで全てのユーザーからのアクセスを許可
#app.run_server(host='192.168.1.10') # これだと192.168.1.10 からのアクセスのみ許可
```

### Debug時リローダーを有効にする

リローダーを有効にすることで、Pythonファイルを更新するたびにWebアプリを自動再読み込みしてくれます。

```python
app.run_server(port=5000, debug=True, host="0.0.0.0", use_reloader=True)
```

### マルチスレッド対応（Flask Optionの利用）

Dashアプリ起動時のパラメータにはFlask起動パラメータを指定することができる。

```python
app.run_server(threaded=True) # threadedはFlask起動パラメータ
```

参考）Dashで指定可能な起動パラメータ

```python:dash.py
def run_server(
    self,
    host=os.getenv("HOST", "127.0.0.1"),
    port=os.getenv("PORT", "8050"),
    proxy=os.getenv("DASH_PROXY", None),
    debug=False,
    dev_tools_ui=None,
    dev_tools_props_check=None,
    dev_tools_serve_dev_bundles=None,
    dev_tools_hot_reload=None,
    dev_tools_hot_reload_interval=None,
    dev_tools_hot_reload_watch_interval=None,
    dev_tools_hot_reload_max_retry=None,
    dev_tools_silence_routes_logging=None,
    dev_tools_prune_errors=None,
    **flask_run_options # ここでFlask Optionの指定が可能になっている
):
```
> [マルチスレッドのサンプルコード Dash - dcc.Store()](https://dash.plotly.com/dash-core-components/store)

### app.layout ページロード時再描画

Dashのアプリは一度app.layoutを読み込むと、ページをロードしても初回の情報を覚えています。
ロードするたびにapp.layoutを更新するには、「app.layout = 関数名」にします。（関数の括弧は不要）

下記の例ではページをロードするたびに、app.layoutを更新し現在時間をページに再描画してくれます。


```python
import datetime

import dash
import dash_html_components as html

def serve_layout():
    return html.H1('The time is: ' + str(datetime.datetime.now()))

app.layout = serve_layout # app.layout = server_layout() はNG

if __name__ == '__main__':
    app.run_server(debug=True)
```

> [Dash Live Update](https://dash.plotly.com/live-updates)

### 右下の青いデバッグマークを非表示にする

起動時debugパラメータをFalseにする

```python
import dash
app = dash.Dash(__name__)
app.run_server(host='0.0.0.0', debug=False)
```

### Favicon変更

assetsフォルダに```favicon.ico```を配置する

```
Root Dir
 |_ app.py
 |_ assets
     |_ favicon.ico
```

### cssファイル配置＆反映

assetsフォルダにcssファイルを配置する
宣言は不要で配置するだけで反映される

```
Root Dir
 |_ app.py
 |_ assets
     |_ main.css
     |_ sub.css
     |_ ***
```

### JavaScriptを配置＆利用

css同様にassetsフォルダにJavaScriptファイルを配置する
宣言は不要で配置するだけで反映される

```
Root Dir
 |_ app.py
 |_ assets
     |_ test.js
```

```javascript:test.js
alert('hello world');
```

上記のスクリプトを配置した場合、Webアプリを起動するとアラートが表示される。

### metatagを利用する（レスポンシブル対応など）

```python
metatags = [{'name': 'viewport',}, {'content': 'width=device-width,initial-scale=1'}]
app = dash.Dash(__name__, meta_tags=metatags)
# app.run_server()
```

### タイトルバーの「Updating...」消す & 表示を任意の文字に変更

```python
import dash
app = dash.Dash(__name__, update_title=None)
app.title = "Hello Title"
```

## レイアウト関連

### 画面のデザインを一括でBootStrap風に変更する

dash_bootstrap_componentsのThemeを利用する

```python
import dash
import dash_bootstrap_components as dbc
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.SIMPLEX])
```

使用可能なテーマパラメータ
上記コードの「SIMPLEX」の部分をお気に入りのテーマに変えてみてください。

```
CERULEAN, COSMO, CYBORG, DARKLY, FLATLY, JOURNAL, LITERA, LUMEN, LUX, MATERIA,
MINTY, MORPH, PULSE, QUARTZ, SANDSTONE, SIMPLEX, SKETCHY, SLATE, SOLAR, SPACELAB, 
SUPERHERO, UNITED, VAPOR, YETI, ZEPHYR
```

> [Theme解説ページ](https://dash-bootstrap-components.opensource.faculty.ai/docs/themes/)

## 基本コンポーネント

比較的よく利用するコンポーネントの一部を紹介します。

### Label

Label使わなくても、html.div()でChildren指定でも問題ないです。

```python
import dash_bootstrap_components as dbc
dbc.Label(id='lbl1,children="Hello, world"),
```

### 非表示Div

```python
import dash_html_components as html
html.Div(id='div-hide',style={'display':'none'}),
```

### A

```python
import dash_html_components as html
html.A("Go to page2",　href='/page2', target='_blank') # _blank指定で新規タブ
```


### Button

dccよりdbcを使う方がいいです。

```python
import dash_bootstrap_components as dbc
dbc.Button("Primary", color="primary"), # primaryカラーはThemeに応じて変わる
```
> [Bootstrap Component - Button](https://dash-bootstrap-components.opensource.faculty.ai/docs/components/button/)

### InputBox

0〜100が入力できるInputBox

```python
import dash_bootstrap_components as dbc
dbc.Input(id='input_1',type="number",min=1,max=100,step=1,value=0),
```

### Tooltip

```python
dbc.Tooltip(
    "Tooltip Message",
    target="element-target",
    placement='top',
),
```
> [Bootstrap Component - Tooltip](https://dash-bootstrap-components.opensource.faculty.ai/docs/components/tooltip/)

### Image (html.Img利用)

画像を配置するだけならこれでOK
拡大やホバーに対応したい場合は次の項目を参照
画像は必ず「static」フォルダに配置する

```python
import dash_html_components as html
html.Td(html.Img(src='./static/img.png')
```

### Image (dcc.Graph利用)

画像を拡大、ホバー可能（この方法がおすすめです）
画像をfigure形式に変換する

```python
import cv2
import dash_core_components as dcc
import plotly.express as px

img = cv2.imread("./static/img.png")
fig = px.imshow(img)
dcc.Graph(
    id='graph-image',
    figure = fig,
),
```
### Timer

タイマーつけすぎるとクライアント側PCが重くなる可能性があるので、
たくさんのタイマーを配置するのは控えた方がいいです。

```python
import dash_core_components as dcc
dcc.Interval(
    id='interval-main',
    interval=1000, # 1sec
    n_intervals=0
),
```
### Table

テーブルもボタン同様dbcでの実装お勧めします。
cssでのレイアウト調整がやりやすいです。

```python
import dash_bootstrap_components as dbc
import pandas as pd

df = pd.DataFrame(
    {
        "First Name": ["Arthur", "Ford", "Zaphod", "Trillian"],
        "Last Name": ["Dent", "Prefect", "Beeblebrox", "Astra"],
    }
)

table = dbc.Table.from_dataframe(df, striped=True, bordered=True, hover=True)
```

> [Bootstrap Components - Table](https://dash-bootstrap-components.opensource.faculty.ai/docs/components/table/)

## その他

上記を使いこなせたら大体のことができます。
適宜公式リファレンス見ながらやってみてください。
グラフは次の章で詳細を解説します。

## グラフ設定関連

### 右上に出るアイコン表示を全て消す

config -> displayModeBar をFalseに変更する

```python
import dash_core_components as dcc
dcc.Graph(
    id='graph',
    config={
        'displayModeBar':False,
    },
    figure = figure, // 各自で用意
),
```

### 凡例の非表示

```python
# figは事前に用意
fig.update_layout(
    showlegend=False,
)
# dcc.Graph()に入れて使う
```

### 散布図のカラーテーマを変更する

```python
import dash_core_components as dcc
import plotly.express as px

fig = px.scatter(
    df, x="sepal_width", y="sepal_length", 
    color_discrete_sequence=px.colors.sequential.Viridis, // ここでテーマ設定
)
dcc.Graph(
    id='graph',
    config={
        'displayModeBar':False,
    },
    figure = fig,
),

#使用できるテーマは下記で確認可能
#colorscales = px.colors.named_colorscales()
#print(colorscales)
```

下記は Viridis の例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586845/67e77290-01df-762e-8f2a-e0ff40ec0d4c.png)


### グラフのMargin, Padding調整

update_layout()で変更が可能

```python
# figは事前に用意
fig.update_layout(
    margin = dict(l=0, r=0, b=0, t=0, pad=0),
)
# dcc.Graph()に入れて使う
```

###グラフ周りのカラーを変更

外周エリアの白色部分とセンターのグレーカラーを透過させる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586845/8e890c7e-7671-7414-4158-eb4a15b95f80.png)


```python
fig.update_layout(
    paper_bgcolor='rgba(0,0,0,0)', # legend and axis transparent
    plot_bgcolor='rgba(0,0,0,0)', # graph area transparent
)
```

### グラフの文字フォントと色を変更する
```python
# figは事前に用意
fig.update_layout(
    font=dict(
        family="Courier New, monospace, Open Sans",
        color='#BBE1FA',#"#415675"
    ),
)
# dcc.Graph()に入れて使う
```

### グラフのホバー表示を変更する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586845/2e347785-6fe6-89a9-e265-3cd3f53fb8e0.png)

```python
# figは事前に用意
fig.update_layout(
    hovermode="x unified"
)
# dcc.Graph()に入れて使う
```
> [Hover Text and Formatting in Python](https://plotly.com/python/hover-text-and-formatting/)


## コールバック関連

### アプリ起動時の初回コールバック防止

「prevent_initial_call」をFalseに設定する

```python
import dash
from dash.dependencies import Input, Output

@app.callback(Output('div-output','children'),
              Input('div-input', 'children'),
              prevent_initial_call=True,)
def func(children):
    pass
```

### 他のPythonファイルにCallbackを定義する場合

アプリケーション生成時に「suppress_callback_exceptions」をTrueにする。

```python
import dash
app = dash.Dash(__name__, suppress_callback_exceptions=True)
```

### タイマー処理で前回と同じ情報をOutputする

dash.no_updateを指定すると前回から要素を更新しない

```
import dash
@app.callback(Output('div-output','figure'),
              Input('div-input', 'children'))
def func(children):
    return dash.no_update
```

### Outputつけたくない時

Outputで何も返さずにメンバー変数のフラグを変更したい時など
Inputと同じ要素のstyleパラメータに何も返さないという方法が便利

```python
import dash

_flag = False

@app.callback(Output('btn-input','style'),
              Input('btn-input', 'n_clicks'))
def func(children):
    global _flag
    _flag = True
    #return不要
```

### 複数のボタンで同じコールバックを利用

C#で複数ボタンある際に、tagで区別するイメージです。
CallbackのInputとOutputにMATCHを指定します。
（InputのみMATCH指定するなどは不可。両方MATCHにする必要があります。）

```python
import dash
from dash.dependencies import Input, Output, MATCH
import ast

dbc.Button("button1",
    id = {
        "type" : "btn-main",
        "index" : 1,
    },
    className="cls-btn",
),
dbc.Button("button2",
    id = {
        "type" : "btn-main",
        "index" : 2,
    },
    className="cls-btn",
),
dbc.Button("button3",
    id = {
        "type" : "btn-main",
        "index" : 3,
    },
    className="cls-btn",
),

@app.callback(Output({"type": "btn-main", "index" : MATCH}, 'style'),
              Input({"type" : "btn-main", "index" : MATCH}, 'n_clicks'),
              prevent_initial_call=True)
def btnsClick(n):
    selected_id = dash.callback_context.triggered[0]["prop_id"].split(".")[0]
    dicID = ast.literal_eval(selected_id)
    index = int(dicID['index'])

    if index == 1:
        print("button1 clicked")
    elif index == 2:
        print("button2 clicked")
    elif index == 3:
        print("button3 clicked")
```

### ALL 使って対象の要素をStateで全て取得

ボタンを押した際に対象のdivの値を全て取得したいときなどに利用。

```python

# 必要な部分のみ抜粋してます
html.Button(id='submit-button', n_clicks=0, children='Submit'),

# ALLを使用するためidは{type, index}形式で定義
html.Div(id={'type':'labels', 'index':0}, children=["Zero"]),
html.Div(id={'type':'labels', 'index':1}, children=["One"]),
html.Div(id={'type':'labels', 'index':2}, children=["Two"]),
html.Div(id={'type':'labels', 'index':3}, children=["Three"]),
html.Div(id={'type':'labels', 'index':4}, children=["Four"]),

@app.callback(Output('submit-button', 'style'),
              Input('submit-button', 'n_clicks'),
              State({'type': 'labels', 'index': ALL}, 'children'),
              prevent_initial_call=True) # 初回イベント発生防止
def button_click(n_clicks, values):
    # id (type=labels) で設定している全ての要素を取得可能
    for text in values:
        print(text)

    #Outputは何も返さない

# 上記を使わない場合下記のように格好悪いコードになります。
'''
html.Div(id='labels-0', children=["Zero"]),
html.Div(id='labels-1', children=["One"]),
html.Div(id='labels-2', children=["Two"]),
html.Div(id='labels-3', children=["Three"]),
html.Div(id='labels-4', children=["Four"]),

@app.callback(Output('submit-button', 'style'),
              Input('submit-button', 'n_clicks'),
              State('labels-0', , 'children'),
              State('labels-1', , 'children'),
              State('labels-2', , 'children'),
              State('labels-3', , 'children'),
              State('labels-4', , 'children'),
              prevent_initial_call=True) # 初回イベント発生防止
def button_click(n_clicks, lbl0, lbl1, lbl2, lbl3, lbl4):
    print(lbl0)
    print(lbl4)
'''
```


## その他

### WARNING: This is a development server. Do not use it in a production deployment.

基本的に実際に公開するアプリケーションを利用する場合は、
Pythonの内部Webサーバーは非推奨です。

WSGIサーバーの利用を検討してください。

デバッグ時はこの赤字が出ても無視しても問題ありません。

### ルーティングコールバック

Flaskの app.route と同じ動きです。

```python
@app.server.route("/page")
def routeFunc():
    # /page移動時のルーティング時の処理
    pass
```

### 基本認証実装

ポップアップでユーザー認証が完了すると app.layoutが読み込まれます。
パスワード丸わかりなので、暗号化はした方がいいです。

```python
import dash
import dash_auth

app = dash.Dash(__name__)
passPair = {"User1" : "AAA", "User2" : "BBB"}
auth = dash_auth.BasicAuth(app, passPair)
```

### ページのリロード

Flaskの機能を使用してページの遷移をすることで実装します。
例）ボタンを押したらページのリロード

```
from flask.helpers import url_for
from flask import redirect

# btn-Aを定義

@app.callback(Output('btn-A','style'),
              Input('btn-A', 'n_clicks'))
def reload(n_clicks):
    redirect(url_for('/')) # / に移動.　現在「/」に居るならリロードと同じ意味
```

---
## 最後に
弊社ではエンジニアを絶賛募集中です！
AI、IoT、Microsoft技術の道を進みたい人は是非連絡ください。

## 更新履歴

* 2021/11/09 初版