【Python】Dashアプリのテーブルにダウンロードボタンをつける

## 表示しているテーブルデータをDownloadしたい

dash communityの[download csvに関する話題](https://community.plot.ly/t/is-it-possible-to-upload-edit-then-download-csv-files-with-dash-python/28303)のなかで、以下のようなアナウンスがありました.
>A recent version of the dash_table.DataTable has a built-in export table feature as well, so if you display your modified data as a DataTable component, you could get the export functionality “for free”.

`dash_table`にExport機能が追加されたみたいなので、試してみます.

## 目標

- 表示しているテーブルのデータをDownloadできるアプリケーションを作ります.
- まずシンプルなアプリを作って、最終的に少し見た目を整えたものを作ってみます.

成果物イメージ
![export_table_test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143058/4b490717-21a5-ac27-f014-ef5e0f0b4f8a.gif)

## 環境

- OS : macOS Mojave Version 10.14.6
- Python : Python 3.6.1
- dash == 1.3.1
- dash-core-components == 1.2.1
- dash-html-components == 1.0.1
- dash-table == 4.3.0

## 方法

- `dash_table`の引数に`export`を追加します.

- 最終的にフォルダ構成は↓のようになります.

```
dash_export_table/.
├── app.py
└── data
    └── iris.csv
```

### データ準備

何でも良いですが、irisデータセットを用意します.

```terminal
mkdir data
curl https://gist.githubusercontent.com/curran/a08a1080b88344b0c8a7/raw/639388c2cbc2120a14dcf466e85730eb8be498bb/iris.csv > data/iris.csv
```
### tableを表示するアプリを用意

- まずは簡単な、dropdownで表示するテーブルの行を制限するアプリを用意します.

```python:app.py
import dash
import dash_table
import pandas as pd
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

df = pd.read_csv('data/iris.csv')

app = dash.Dash(__name__)

app.layout = html.Div(
    children=[
        html.H1("Dash app with export dataset"),
        dcc.Dropdown(
            id='my-dropdown',
            options=[
                {'label': 'setosa',
                 'value': 'setosa'},
                {'label': 'versicolor',
                 'value': 'versicolor'},
                {'label': 'virginica',
                 'value': 'virginica'}
            ], value='setosa'
        ),
        html.Div(id='output-container', style={"margin": "5%"})
    ]
)


@app.callback(
    Output('output-container', 'children'),
    [Input('my-dropdown', 'value')])
def input_triggers_spinner(value):
    df_filtered = df[df["species"] == value]
    output_table = dash_table.DataTable(
        id='table',
        columns=[{"name": i, "id": i} for i in df.columns],
        data=df_filtered.to_dict('records'),
    )
    return output_table


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

```

現時点では↓のようなシンプルなアプリです.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143058/72f8fe83-2447-c019-348c-3ed00c7a5f03.png)


### `export_format='csv',`を追加する

- `DataTable()`の引数に追加します.

```python:app.py
import dash
import dash_table
import pandas as pd
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

df = pd.read_csv('data/iris.csv')

app = dash.Dash(__name__)

app.layout = html.Div(
    children=[
        html.H1("Dash app with export dataset"),
        dcc.Dropdown(
            id='my-dropdown',
            options=[
                {'label': 'setosa',
                 'value': 'setosa'},
                {'label': 'versicolor',
                 'value': 'versicolor'},
                {'label': 'virginica',
                 'value': 'virginica'}
            ], value='setosa'
        ),
        html.Div(id='output-container', style={"margin": "5%"})
    ]
)


@app.callback(
    Output('output-container', 'children'),
    [Input('my-dropdown', 'value')])
def input_triggers_spinner(value):
    df_filtered = df[df["species"] == value]
    output_table = dash_table.DataTable(
        id='table',
        columns=[{"name": i, "id": i} for i in df.columns],
        data=df_filtered.to_dict('records'),
        export_format='csv',
    )
    return output_table


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

```

- Exportボタンが登場します. ボタンをクリックすると、現在表示されているテーブルのデータをDownloadすることができます.
<img width="950" alt="Screen Shot 2019-09-29 at 12.53.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143058/d290c4e2-20e4-6210-5933-6fdf6d13a25d.png">

- species = "setosa"の行のデータがDLされます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143058/afc893fb-6d3f-36f0-9e29-122769a8d6f6.png)


## 少し整える

- 少しだけ見た目整えてみます.
    - テーブルをリスト表示に変更 `style_as_list_view=True,`[Styling the DataTable](https://dash.plot.ly/datatable/style)
    - headerを固定 `fixed_rows={ 'headers': True, 'data': 0 },` [DataTable Sizing](https://dash.plot.ly/datatable/sizing)
    - loadingアニメーションを追加 outputをLoading Componentで囲む 
        - [Loading Component](https://dash.plot.ly/dash-core-components/loading_component)
        - [【Python】Dashのcallbackにアニメーションをつける](https://qiita.com/sasaki_K_sasaki/items/0fb3e996d24300c8c659)
    
- 見た目ではないですが、Exportの形式をxlsxに変えてみます.

```python:app.py
import dash
import dash_table
import pandas as pd
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

df = pd.read_csv('data/iris.csv')

app = dash.Dash(__name__)
common_style = {'position': 'relative', 'width': '100%',
                'font-family': 'Dosis', 'text-align': 'center'}

app.layout = html.Div(
    children=[
        html.H1("Dash app with export csv", style={'margin-bottom': '3%'}),
        dcc.Dropdown(
            id='my-dropdown',
            options=[
                {'label': 'setosa',
                 'value': 'setosa'},
                {'label': 'versicolor',
                 'value': 'versicolor'},
                {'label': 'virginica',
                 'value': 'virginica'}
            ],
            value='setosa',
            style={'margin-bottom': '8%'}
        ),
        dcc.Loading(id="loading-1",
                    children=[
                        html.Div(id='output-container',
                                 )
                    ],
                    style={"margin": "10%"},
                    type="default"),
    ],
    style=common_style
)

@app.callback(
    Output('output-container', 'children'),
    [Input('my-dropdown', 'value')])
def input_triggers_spinner(value):
    df_filtered = df[df["species"]==value]
    output_table = dash_table.DataTable(
            id='table',
            columns=[{"name": i, "id": i} for i in df.columns],
            data=df_filtered.to_dict('records'),
            # リスト表示にします
            style_as_list_view=True,
            # headerを固定してスクロールできるようにします
            fixed_rows={ 'headers': True, 'data': 0 },
            # exportするフォーマットを指定(csv or xlsx)
            export_format='xlsx',
        )
    return output_table

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

```

これで最初のgifが再現できます.
![export_table_test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143058/4b490717-21a5-ac27-f014-ef5e0f0b4f8a.gif)

#### おわりに

以前からデータExport機能についてはCommunityで議論されていましたが、ついに公式でサポートしてくれたみたいです.
Dash communityに感謝しつつ、ご紹介させていただきました.

皆さんも是非お試しください.


## 参考

[Is it possible to upload, edit then download csv files with dash? (python)](https://community.plot.ly/t/is-it-possible-to-upload-edit-then-download-csv-files-with-dash-python/28303)