3
1
記事投稿キャンペーン 「2024年!初アウトプットをしよう」

Plotly DashとBootstrapでマルチページ

Last updated at Posted at 2023-12-31

1. はじめに

StreamlitだとPlotlyのマウスホバー・クリック情報を活用したインタラクティブ可視化が難しそうだったので(詳しい方は情報いただけるとありがたいです)、Plotly Dashを勉強中です。

Dashでマルチページを作成したくて、色々調べたので備忘録です。

見栄えのよいナビゲーションバーをつけるために、Bootstrapを利用しました。

2. 参考にしたページ

3. 開発環境

  • macOS Ventura
  • Miniconda
  • Python==3.11.6
  • Dash==2.14.2
  • Plotly==5.9.0
  • dash-bootstrap-components==1.5.0

4. ライブラリインストール

% conda install -c conda-forge plotly
% conda install -c conda-forge dash
% conda install -c conda-forge dash-bootstrap-components

5. ディレクトリ構成

dash_multipage
├── app.py
└── pages
    ├── home.py
    ├── page1.py
    ├── page2.py
    └── page3.py

6. 動作イメージ(非公式)

dash_multipage_image.png

使用に際してのイメージです。正確ではない可能性があります。

7. .pyファイルの内容

データにはPlotlyで取得できるデータセットを使用しました。

使用したデータセットはirisgapminderwindです。

app.py
import dash
from dash import Dash, html, dcc
import dash_bootstrap_components as dbc

app = Dash(__name__,
           external_stylesheets=[dbc.themes.SANDSTONE],
           use_pages=True)

navbar = dbc.Nav(
                children=[dbc.NavItem(dbc.NavLink(page["name"], 
                                            href=page["relative_path"],
                                            class_name="nav-item")) for page in dash.page_registry.values()],
                id = "navibar",
                class_name = "navbar navbar-expand-lg bg-dark"
            )

app.layout = dbc.Container([
                        dcc.Location(id='url', refresh=False),
                        navbar,
                        html.Br(),
                        dash.page_container
                    ])

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

BootstrapテンプレートにはSandStoneを利用しました。

クラス名は上のリンク先を参照しました。

dash.register_page(__name__)を宣言すると、layout変数に代入したレイアウトに従って、app.pydash.page_containerの場所に配置されるようです。どの.pyファイルを参照するかは、dcc.Locationで定義してるっぽいです(id='url')が、まだちゃんと理解しきれていないので、引き続き調べる予定です。

home.py
import dash
from dash import html

dash.register_page(__name__, path='/')

layout = html.Div([
    html.H1('Dash Multipage Test'),
    html.Br(),
    html.Div([html.H3("Page1: Iris Data"),
              html.H3("Page2: Gapminder Data"),
              html.H3("Page3: Wind Data"),
              ])
])
page1.py

import dash
from dash import html, dcc, callback, Input, Output
import plotly.express as px
from plotly.data import iris
import json

page_number = "page1"
data = iris()

dash.register_page(__name__)

layout = html.Div([
        html.H2("Scatter Plot of Iris data"),
        html.Div([dcc.Graph(id=f"{page_number}_scatter",
                            figure=px.scatter(data,
                                              x='sepal_length',
                                              y='sepal_width',
                                              color='species'))]),
        html.Br(),
        html.Div([html.P(id=f"{page_number}_json")])])


@callback(Output(f"{page_number}_json","children"),
              [Input(f"{page_number}_scatter","hoverData")])
def scatter_plot_hover_data(hover_data):
    if hover_data is None:
        return ""
    
    point_data = hover_data['points'][0]
    point_data_json = json.dumps(point_data)
    return point_data_json

ページの内容としては、Irisデータのsepalの長さと幅をそれぞれxyとして散布図を描き、マウスホバーした点の情報をjson形式で記述するものとなっています。

シングルページの場合との違いは、callbackappオブジェクトのメソッドとして記述するのではなく、callback関数をdashからインポートしている点です。

参考ページにある通り、callbackで使う各ページファイル内のidが被っているとエラーで動かないので、ページごとにidを変える必要があります。

page2.pypage3.pyの内容はpage1.pyとほとんど同じなので割愛します。

8. 結果

最終的に、こんな感じになりました。

2023-12-31.gif

なかなか悪くない感じです。

3
1
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
3
1