Plotly Dashのcallbackを理解する


# Plotly Dashとは
インストールや概念的な解説はこちらのリンクが詳しいです。

* Plotly Dashで簡単に可視化ができるWebアプリを作る
https://qiita.com/driller/items/49e3b6084ec034353dc1

* はんなりPythonの会＃10でDashに関して発表してきた。
https://www.mazarimono.net/entry/2018/10/21/dash

# Callback
Plotly Dashで入力を含むコンポーネントを作る際、少し戸惑うのがcallbackの部分だと思います。

しかし慣れてしまえば、すぐに使いこなすことができると思います。

ここでは、公式のサンプル https://dash.plot.ly/getting-started-part-2 をもとに、Input要素での入力内容を別の要素で表示させたいケースを考えます。


# ソース
```python:app.py
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)

app.layout = html.Div([
    dcc.Input(id='input_id', value=None, type='text'),
    html.Div(id='output_div')
])


@app.callback(
    Output(component_id='output_div', component_property='children'),
    [Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
    if input_value:
        return html.Div(className='output-area', children=[
            html.Span(input_value)
        ])
    else:
        html.Div()


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

## 説明
```>python app.py```
で実行し、デフォルトのhttp://127.0.0.1:8050/ にアクセスすると下記の様な画面が表示されます。
![aasdasfewq.png](https://qiita-image-store.s3.amazonaws.com/0/318314/04343a2f-ba2d-46d6-2e22-f3b57c89c774.png)

まずapp.callbackデコレータ内の
```[Input(component_id='input_id', component_property='value')]```
に注目します。

これは、input_idというIDがある要素のvalue属性をみています。つまり
```dcc.Input(id='input_id', value=None, type='text'),```
のことで、ブラウザでは矢印先のinput要素をさしています。このvalueが変化した際に、Outputが発生します。

```Output(component_id='output_div', component_property='children'),```
アウトプットが描画される場所は、output_divというIDがある要素の子要素です。

つまり、
```html.Div(id='output_div')```
のことです。

```python
    if input_value:
        return html.Div(className='output-area', children=[
            html.Span(input_value)
        ])
```
なので、出力先のHTML要素は 
div#output_div > div.output-area > span
という構造になっていると思います。

下記のスクリーンショットでわかるように、数字100がdiv#output_divの要素下のdiv.output-areaのspanに表示されていることがわかります。

![adasfsf.png](https://qiita-image-store.s3.amazonaws.com/0/318314/9cd45e8c-8ef1-8cf6-194d-9128fb78399c.png)

## component_idとcomponent_property
outputもinputもcomponent_idとcomponent_propertyが肝です。

どちらも必須で、これらがないと、どの要素を関連づけるのかわからないからです。
runする際にこの様なエラーが出て、IDが欠けていることを教えてくれます。
```
Attempting to assign a callback to the
component with the id "output_div" but no
components with id "output_div" exist in the
app's layout.
```


# 応用
ソースを変更して入力値が80以上であれば、```合格```を出す様にしてみました。

```python:app_v2.py
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)

app.layout = html.Div([
    dcc.Input(id='input_id', value=None, debounce=True, type='text'),
    html.Div(id='output_div')
])


@app.callback(
    Output(component_id='output_div', component_property='children'),
    [Input(component_id='input_id', component_property='value')]
)
def update_output_div(input_value):
    if input_value:
        message = '不合格'
        if input_value.isdigit() and int(input_value) >= 80:
            message = '合格'

        return html.Div(className='output-area', children=[
                html.Span(message)
            ])

    else:
        html.Div()


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



# 参考サイト
https://dash.plot.ly/getting-started-part-2