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)
설명