LoginSignup
137
160

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

Last updated at Posted at 2021-11-09

Dashの使い方完全保存版

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

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

基本

Dashとは

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

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

Dash Hello World

不便な点

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

関連ライブラリ

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

import dash

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

import dash_bootstrap_components

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

Dash bootstrap components

import dash_core_components

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

Dash Core Components (公式ページ)

import dash_html_components

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

Dash HTML Components (公式ページ)

import dash_auth

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

import dash_daq

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

Dash DAQ (公式ページ)

アプリ基本設定編

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

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

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アプリを自動再読み込みしてくれます。

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

マルチスレッド対応(Flask Optionの利用)

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

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

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

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()

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

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

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

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

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

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

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
test.js
alert('hello world');

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

metatagを利用する(レスポンシブル対応など)

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

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

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

レイアウト関連

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

dash_bootstrap_componentsのThemeを利用する

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解説ページ

基本コンポーネント

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

Label

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

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

非表示Div

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

A

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

Button

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

import dash_bootstrap_components as dbc
dbc.Button("Primary", color="primary"), # primaryカラーはThemeに応じて変わる

Bootstrap Component - Button

InputBox

0〜100が入力できるInputBox

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

Tooltip

dbc.Tooltip(
    "Tooltip Message",
    target="element-target",
    placement='top',
),

Bootstrap Component - Tooltip

Image (html.Img利用)

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

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

Image (dcc.Graph利用)

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

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が重くなる可能性があるので、
たくさんのタイマーを配置するのは控えた方がいいです。

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

Table

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

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

その他

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

グラフ設定関連

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

config -> displayModeBar をFalseに変更する

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

凡例の非表示

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

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

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

グラフのMargin, Padding調整

update_layout()で変更が可能

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

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

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

image.png

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

グラフの文字フォントと色を変更する

# figは事前に用意
fig.update_layout(
    font=dict(
        family="Courier New, monospace, Open Sans",
        color='#BBE1FA',#"#415675"
    ),
)
# dcc.Graph()に入れて使う

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

image.png

# figは事前に用意
fig.update_layout(
    hovermode="x unified"
)
# dcc.Graph()に入れて使う

Hover Text and Formatting in Python

コールバック関連

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

「prevent_initial_call」をFalseに設定する

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にする。

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パラメータに何も返さないという方法が便利

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にする必要があります。)

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の値を全て取得したいときなどに利用。


# 必要な部分のみ抜粋してます
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 と同じ動きです。

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

基本認証実装

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

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 初版
137
160
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
137
160