plotly dash

Dash router

wefree 2022. 5. 30. 21:57

문제

dash-2.5.0 이상 버전 부터 Dash Pages 기능을 제공하고 있다. - 참고

https://stackoverflow.com/a/62337544 에 언급된 것 처럼, 두개의 dcc.Location 를 이용해야 한다.

dash-2.5.0 이상 버전의 Dash Pages 를 사용하면, 한개의 dcc.Location 으로 구현할 수 있다.

 

  • fired when the url (dcc.Location) is changed and updates the selected tab: Dash Page 이용
  • fired when the selected tab changes (dcc.Tabs) and updates the displayed url: dcc.Location 이용

https://dash.plotly.com/urls 의 Example 을 바탕으로 

http://127.0.0.1:8050/analytics?city=Montreal 이 동작하는 application 을 개발해 보자

 

코드

소스 구조

├── app.py
└── pages
    ├── analytics.py
    ├── archive.py
    └── home.py

 

 

app.py

import dash
from dash import Dash, html, dcc  # type: ignore
import dash_bootstrap_components as dbc

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

app.layout = html.Div([
    html.H1('Multi-page app with Dash Pages'),

    html.Div(
        [
            html.Div(
                dcc.Link(
                    f"{page['name']} - {page['path']}", href=page["relative_path"]
                )
            )
            for page in dash.page_registry.values()
        ]
    ),

    dash.page_container
])

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

 

pages/analytics.py

from urllib import parse

import dash
from dash import html, dcc, callback, Input, Output

dash.register_page(__name__)


def layout(city=None):
    selected = 'Montreal' if city is None else city

    return html.Div([
        dcc.Location(id='url', refresh=True),
        html.H1("Analytics Page"),
        html.Div([
            "Select a city: ",
            dcc.RadioItems(['NewYork', 'Montreal'], selected, id='analytics-input')
        ]),
        html.Br(),
        html.Div(f'You selected: {selected}')
    ])


@callback(
    Output('url', 'search'),
    Input('analytics-input', 'value')
)
def update_city_selected(input):
    return f"?city={parse.quote(input, encoding='utf-8')}"

 

pages/archive.py

import dash
from dash import html

dash.register_page(__name__)

layout = html.Div(children=[
    html.H1(children='This is our Archive page'),

    html.Div(children='''
        This is our Archive page content.
    '''),

])

 

pages/home.py

import dash
from dash import html

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

layout = html.Div(children=[
    html.H1(children='This is our Home page'),

    html.Div(children='''
        This is our Home page content.
    '''),

])

 

---------------------------------------------------------------------------

 

 

dash-2.5.0 이전 버전 일때

문제1

아래와 같이 Router 를 구현해 보자. 코드2 에서 구현한 방법보다 코드1 방법이 더 나은 것 같다.

코드1

from typing import Optional
from urllib import parse

import dash_bootstrap_components as dbc
from dash import Dash, html, dcc, Input, Output, State

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    dbc.Form([
        dbc.RadioItems(id='country-radio',
                       name='country',
                       options=[{'label': 'Korea', 'value': 'ko'}, {'label': 'USA', 'value': 'usa'}]),
        dbc.Button(id='submit-button', n_clicks=0, children='Submit', type='submit', color='primary'),

    ], prevent_default_on_submit=False),
    html.Br(),
    html.Br(),
    html.Div(id='selected-div')
], style={'margin': '10px'})


@app.callback(
    Output('country-radio', 'value'),
    Output('submit-button', 'n_clicks'),  # submit 버튼 클릭 효과
    Input('url', 'href')
)
def apply_url_to_form(href: str) -> (Optional[str], int):
    parsed_url = parse.urlparse(href)
    queries = parse.parse_qs(parsed_url.query, encoding='utf-8')
    selected_country = queries.get('country')[0] if queries.get('country') else None
    return selected_country, 0


@app.callback(
    Output('selected-div', 'children'),
    Input('submit-button', 'n_clicks'),
    State('country-radio', 'value')
)
def show_result(clicked, country) -> str:
    return f'You selected country={country}'


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

 

 

문제2

ctx.triggered_id 를 이용해 Router 를 구현해 보자

 

예1: 입력된 URL 로 최종 결과까지 노출되게 한다. (마치 submit 버튼을 누른 것 처럼)

예2: Submit 버튼을 누른 후에야 선택한 country 가 URL 이나 country 결과에 반영되어야 한다.

코드2

from dash import Dash, html, dcc, Input, Output, State, ctx

app = Dash()

country_list = ['korea', 'usa']

app.layout = html.Div([
    dcc.Location(id='location', refresh=False),
    dcc.RadioItems(id='country-radio', options=country_list),
    html.Button(id='submit-button', n_clicks=0, children='Submit'),
    html.Br(),
    html.Br(),
    html.Div(id='selected-div')
])


@app.callback(
    Output('country-radio', 'value'),
    Input('location', 'pathname')
)
def apply_location(pathname: str):
    country = pathname.lstrip('/').lower()
    return country


@app.callback(
    Output('selected-div', 'children'),
    Output('location', 'pathname'),  # 브라우저의 URL 을 변경
    Input('submit-button', 'n_clicks'),
    Input('location', 'pathname'),
    State('country-radio', 'value')
)
def show_result(clicked, pathname: str, country):
    cntry = country if ctx.triggered_id == 'submit-button' else pathname.lstrip('/').lower()
    path = f'/{country}' if ctx.triggered_id == 'submit-button' else pathname
    return f'You selected country={cntry}', path


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

 

설명