Dash3. Callbacks – Basic

Published by onesixx on

Dash Tutorial : https://dash.plotly.com/basic-callbacks

callback functions이란

“Reactive Programming”. 엑셀처럼 한Cell이 변함에 따라, 다른 Cell이 자동으로 변화하는 것

  • input 컴포넌트의 속성이 변할때, “자동으로” Dash에 의해 call되는 function
  • 어떤 event에 의해 호출되는 함수.
  • 다른 function의 인수로서 넘겨주는(호출되는) 실행 가능한 코드(함수)

참고> UI와 Chart로딩성능향상을 위해서, Dash Enterprise의 요소들 (Job QueueHPCDatashader, and horizontal scaling)들을 고려해 볼만하다.

예제 1 :: simple

update_div()함수에 decorator – @app.callback를 붙여서 callback을 완성한다.

input은 myInput의 value속성
Output은 id가 myOutput인 컴포넌트의 children 속성이다.
(Output의 children속성이 현재 없지만, 있다해도 input의 초기값으로 자동으로 입력된다.)

input이 변경될때마다, callback 데코레이터로 래핑된 함수가 자동으로 호출되고,
Dash는 해당 function(update_div)의 input 인수(val)에 새롭게 변경된 input의 값을 넘겨준다.
Dash는 function의 return값을 output의 속성에 업데이트한다. (style, option등도 가능)

import dash
from dash import dcc, html
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div(children=[
    html.Div(children=[
        "Input: ", 
        dcc.Input(id='myInput', value="", type='text', placeholder="initial value")
    ]),
    html.Div(id='myOutput')
])

@app.callback(
    Output('myOutput', 'children'),
    [Input('myInput', 'value')])
def update_div(val):
    return f'Output: {val}'

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

dash.dependencies의 Input 객체는 callback 정의할때 활용되고,
dcc.Input은 실제 컴포넌트이다.

예제2 :: 각종 input

import dash
import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)

ALLOWED_TYPES = ("text", "number", "password", "email", 
                 "search","tel", "url", "range", "hidden",)

app.layout = html.Div( children=[ 
    html.Div(children=[
        dcc.Input(
            id=f"input_{inputType}", 
            type=inputType, 
            placeholder=f"input type {inputType}"
        ) for inputType in ALLOWED_TYPES
    ]),
    html.Div(id="out-all-types")
])

@app.callback(
    Output("out-all-types", "children"),
    [Input(f"input_{_}", "value") for _ in ALLOWED_TYPES],
)
def cb_render(*vals):
    return " | ".join([str(val) for val in vals if val])

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

예시3 :: Figure & Slider

dcc.Slider 를 통해  dcc.Graph 를 업데이트

import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State

import plotly.express as px
import pandas as pd

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')

app = dash.Dash(__name__)

app.layout = html.Div(children=[
    dcc.Graph(id='mygraph'),
    dcc.Slider(id='slider_year',
        value=df['year'].min(), min=df['year'].min(), max=df['year'].max(), 
        marks={str(year): str(year) for year in df['year'].unique()},
        step=None
    )
])

@app.callback(
    Output('mygraph', 'figure'),
    Input('slider_year', 'value'))
def update_figure(selected_year):
    filtered_df = df[df.year == selected_year]
    fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",
                     size="pop", color="continent", hover_name="country",
                     log_x=True, size_max=55)
    fig.update_layout(transition_duration=500)
    return fig

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

예제 :: Multiple Inputs

어떤 “Output“은 여러개의 “Input” components를 가질수 있다.

5 Inputs

  • the value property of 2 dcc.Dropdown components
  • dcc.RadioItems components
  • dcc.Slider component

1 Output

  • figure property of the Graph component
import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc

import plotly.express as px
import pandas as pd

df = pd.read_csv('https://plotly.github.io/datasets/country_indicators.csv')
available_indicators = df['Indicator Name'].unique()

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(
    __name__, 
    external_stylesheets=[
        dbc.themes.BOOTSTRAP,
        external_stylesheets
    ]
)

app.layout = dbc.Container(
    html.Div([
        dbc.Row(
            dbc.Col( dcc.Graph(id='indicator-graphic'), width="auto" ),
        ),
        dbc.Row([
            dbc.Col( dcc.Slider(id='year--slider',
                        value=df['Year'].max(), min=df['Year'].min(), max=df['Year'].max(),
                        marks={str(year): str(year) for year in df['Year'].unique()},
                        step=None,) )
        ],style={'margin-bottom': '2em'}),
        dbc.Row([
            dbc.Col([
                dbc.Row([
                    dbc.Col(html.Label(['x axis:'], style={'font-weight':'bold', "text-align":"right"}), width="auto"),
                    dbc.Col(dcc.Dropdown(id='xaxis-column',
                                options=[{'label': i, 'value': i} for i in available_indicators],
                                value='Fertility rate, total (births per woman)',
                                searchable=False,
                                clearable=False,
                            ))
                ]),
                dbc.Row(
                    dbc.Col(dcc.RadioItems(id='xaxis-type',
                        options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                        value='Linear',
                        labelStyle={'display':'inline-block', 'padding':"6px 16px 6px 6px"}
                    ), width={"size":"auto", "offset":3})
                )
            ],width=5),
            dbc.Col([
                dbc.Row([
                    dbc.Col(html.Label(['y axis:'], style={'font-weight':'bold', "text-align":"right"}), width="auto"),
                    dbc.Col(dcc.Dropdown(id='yaxis-column',
                                options=[{'label': i, 'value': i} for i in available_indicators],
                                value='Life expectancy at birth, total (years)',
                                searchable=False,
                                clearable=False
                            ))
                ]),
                dbc.Row(
                    dbc.Col(dcc.RadioItems(id='yaxis-type',
                                options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],
                                value='Linear',
                                labelStyle={'display':'inline-block', 'padding':"6px 16px 6px 6px"}
                            ), width={"size":"auto", "offset":3})
                )
            ],width={"size":5, "offset":1}),
        ]),
    ])
)

@app.callback(
    Output('indicator-graphic', 'figure'),
    [Input('xaxis-column', 'value'),
     Input('yaxis-column', 'value'),
     Input('xaxis-type',   'value'),
     Input('yaxis-type',   'value'),
     Input('year--slider', 'value')]
)
def update_graph(xaxis_column_name, yaxis_column_name,
                 xaxis_type, yaxis_type, year_value):
    dff = df[df['Year'] == year_value]

    fig = px.scatter(x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],
                     y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],
                     hover_name=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])

    fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')
    fig.update_xaxes(title=xaxis_column_name,
                     type='linear' if xaxis_type == 'Linear' else 'log')
    fig.update_yaxes(title=yaxis_column_name,
                     type='linear' if yaxis_type == 'Linear' else 'log')
    return fig

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

예제 :: Multiple Outputs

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

app = dash.Dash( __name__)

app.layout = html.Div([
    dcc.Input(id='num-multi',
        type='number', value=5
    ),
    html.Table([
        html.Tr([html.Td(['x', html.Sup(2)]),   html.Td(id='square')]),
        html.Tr([html.Td(['x', html.Sup(3)]),   html.Td(id='cube')  ]),
        html.Tr([html.Td([  2, html.Sup('x')]), html.Td(id='twos')  ]),
        html.Tr([html.Td([  3, html.Sup('x')]), html.Td(id='threes')]),
        html.Tr([html.Td(['x', html.Sup('x')]), html.Td(id='x^x')   ]),
    ]),
])

@app.callback(
    [Output('square', 'children'),
     Output('cube',   'children'),
     Output('twos',   'children'),
     Output('threes', 'children'),
     Output('x^x',    'children')],
    Input('num-multi', 'value'))
def callback_a(x):
    return x**2, x**3, 2**x, 3**x, x**x

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

ex> Chained Callbacks

outputs 과 inputs 함께 Chain할수 있다. : dynamic UIs

즉, 하나의 callback 함수의 output은 다른 callback함수의 input이 될수 있다는 뜻이다.
어떤 input이 업데이트되면 관련 callback의 output이 반응하고 이에 따라 chain된 input이 활성화된다.

# -*- coding: utf-8 -*-
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

all_options = {
    'America': ['New York City', 'San Francisco', 'Cincinnati'],
    'Canada': [u'Montréal', 'Toronto', 'Ottawa']
}
app.layout = html.Div([
    dcc.RadioItems(id='rdo_countries',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='America'
    ),
    html.Hr(),
    dcc.RadioItems(id='rdo_cities'),
    html.Hr(),
    html.Div(id='display_result')
])

@app.callback(
    Output('rdo_cities', 'options'),
    Input('rdo_countries', 'value') )
def set_cities_options(selected_country):
    #print(selected_country)
    return [{'label': i, 'value': i} for i in all_options[selected_country]]

@app.callback(
    Output('rdo_cities', 'value'),
    Input('rdo_cities', 'options') )
def set_cities_value(available_options):
    #print(available_options[0]['value'])
    return available_options[0]['value']

@app.callback(
    Output('display_result', 'children'),
    Input('rdo_countries', 'value'),
    Input('rdo_cities', 'value'))
def set_display_children(selected_country, selected_city):
    return u'{} is a city in {}'.format(selected_city, selected_country)

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

ex> With State

Input 이 변경될때마다 callback함수가 fire되는게 아니라,
form 형식으로 입력이 다끝나고 반영하는 경우

# -*- coding: utf-8 -*-
import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)

app.layout = html.Div([
    dcc.Input(id="input-1", 
        type="text", value="Montréal"),
    dcc.Input(id="input-2", 
        type="text", value="Canada"),
    html.Button(id='submit-button-state', 
        n_clicks=0, children='Submit'),    
    html.Div(id="output"),
])

@app.callback(
    Output("output", "children"),
    Input('submit-button-state', 'n_clicks'),
    State("input-1", "value"),
    State("input-2", "value"),
)
def update_output(n_clicks, input1, input2):
    return u''' 
        Input 1 is "{}" and Input 2 is "{}"
        the button has been pressed {} times
    '''.format(input1, input2, n_clicks)

if __name__ == "__main__":
    app.run_server(debug=True)
# -*- coding: utf-8 -*-
import dash
from dash import dcc, html
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div([
    dcc.Input(id="input-1", 
        type="text", value="Montréal"),
    dcc.Input(id="input-2", 
        type="text", value="Canada"),
    
    
    html.Div(id="number-output"),
])

@app.callback(
    Output("number-output", "children"),
    
    Input("input-1", "value"),
    Input("input-2", "value"),
)
def update_output(input1, input2):
    return u'Input 1 is "{}" and Input 2 is "{}"'.format(input1, input2)




if __name__ == "__main__":
    app.run_server(debug=True)
Categories: dash

onesixx

Blog Owner

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x