(資料科學學習手札117)Python+Dash快速web應用開發——互動表格篇(下)

費弗裡發表於2021-04-18

本文示例程式碼已上傳至我的Github倉庫https://github.com/CNFeffery/DataScienceStudyNotes

1 簡介

   這是我的系列教程Python+Dash快速web應用開發的第十四期,在前兩期中,我們針對dash_table的自定義樣式、前後端分頁、單元格內容編輯等特點展開了介紹。

  而在dash_table中還有很多高階特性,可以極大程度上豐富DataTable()所渲染網頁表格的互動能力,今天的文章作為互動表格篇的下篇,我們就來一起學習其中比較實用的一些特性。

(資料科學學習手札117)Python+Dash快速web應用開發——互動表格篇(下)
圖1

2 dash_table的更多實用功能

2.1 更多表格互動特性

  上一期文章最後我們學習了通過設定引數editable=True,使得渲染出的表格可以通過滑鼠雙擊進行編輯,而dash_table除此之外,還有更多實用的互動能力:

2.1.1 按列排序

  • 普通單列排序

  在DataTable()中,我們只需要設定引數sort_action='native',即可開啟列排序功能,此時每一列列名單元格內都會出現部件供我們點選切換排序方式:

app1.py

import dash
import dash_table
import dash_bootstrap_components as dbc

import seaborn as sns

df = sns.load_dataset('iris')

app = dash.Dash(__name__)

app.layout = dbc.Container(
    [
        dash_table.DataTable(
            data=df.to_dict('records'),
            columns=[
                {'name': column, 'id': column}
                for column in df.columns
            ],
            style_table={
                'height': '500px',
                'overflow-y': 'auto'
            },
            sort_action='native'
        )
    ],
    style={
        'margin-top': '50px'
    }
)

if __name__ == '__main__':
    app.run_server(debug=True)
(資料科學學習手札117)Python+Dash快速web應用開發——互動表格篇(下)
圖2
  • 基於後端排序的多列排序

  在DataTable()中設定sort_action='native'時,對應的是按列排序的前端模式,也即是資料一次性灌注到瀏覽器的前提下進行排序,這種方式不僅不適合大型資料集,而且只支援單列排序

  而當資料渲染方式為後端模式時,我們通過設定引數sort_action='custom'以及sort_mode='multi',配合在回撥中獲取屬性sort_by中記錄的參與排序的列名及升序降序方式,就可以實現多列排序。

  我們在上一期的app2.py的基礎上修改得到下面的例子:

app2.py

import dash
import dash_bootstrap_components as dbc
import dash_table
from dash.dependencies import Input, Output

import seaborn as sns

df = sns.load_dataset('iris')
df.insert(0, '#', df.index)

app = dash.Dash(__name__)

app.layout = dbc.Container(
    [
        dbc.Spinner(
            dash_table.DataTable(
                id='dash-table',
                columns=[
                    {'name': column, 'id': column}
                    for column in df.columns
                ],
                page_size=15,  # 設定單頁顯示15行記錄行數
                page_action='custom',
                page_current=0,
                style_header={
                    'font-family': 'Times New Romer',
                    'font-weight': 'bold',
                    'text-align': 'center'
                },
                style_data={
                    'font-family': 'Times New Romer',
                    'text-align': 'center'
                },
                sort_action='custom',
                sort_mode='multi'
            )
        )
    ],
    style={
        'margin-top': '50px'
    }
)


@app.callback(
    [Output('dash-table', 'data'),
     Output('dash-table', 'page_count')],
    [Input('dash-table', 'page_current'),
     Input('dash-table', 'page_size'),
     Input('dash-table', 'sort_by')]
)
def refresh_page_data(page_current, page_size, sort_by):

    if sort_by:
        return (
            df
            .sort_values(
                [col['column_id'] for col in sort_by],
                ascending=[
                    col['direction'] == 'asc'
                    for col in sort_by
                ]
            )
            .iloc[page_current * page_size:(page_current + 1) * page_size]
            .to_dict('records'),
            1 + df.shape[0] // page_size
        )

    return (
        df.iloc[page_current * page_size:(page_current + 1) * page_size].to_dict('records'),
        1 + df.shape[0] // page_size
    )


if __name__ == '__main__':
    app.run_server(debug=True)
(資料科學學習手札117)Python+Dash快速web應用開發——互動表格篇(下)
圖3

2.1.2 按列條件篩選

  除了基於指定欄位進行排序之外,dash_table還支援列的條件篩選,設定filter_action="native",就可以開啟基礎的按列條件篩選功能,此時每一列表頭下都會多出供使用者輸入篩選條件的單元格:

app3.py

import dash
import dash_table
import dash_bootstrap_components as dbc

import seaborn as sns

df = sns.load_dataset('iris')

app = dash.Dash(__name__)

app.layout = dbc.Container(
    [
        dash_table.DataTable(
            data=df.to_dict('records'),
            columns=[
                {'name': column, 'id': column}
                for column in df.columns
            ],
            # 自定義條件篩選單元格樣式
            style_filter={
                'font-family': 'Times New Romer',
                'background-color': '#e3f2fd'
            },
            style_table={
                'height': '500px',
                'overflow-y': 'auto'
            },
            style_header={
                'font-family': 'Times New Romer',
                'font-weight': 'bold',
                'text-align': 'center'
            },
            style_data={
                'font-family': 'Times New Romer',
                'text-align': 'center'
            },
            filter_action="native"
        )
    ],
    style={
        'margin-top': '50px'
    }
)

if __name__ == '__main__':
    app.run_server(debug=True)
(資料科學學習手札117)Python+Dash快速web應用開發——互動表格篇(下)
圖4

  而dash_table中自帶的條件篩選語法很豐富,有條件的朋友可以前往https://dash.plotly.com/datatable/filtering瞭解更多。

  而dash_table同樣可以實現後端篩選,和前面的後端排序類似,主要利用filter_query屬性的回撥變化在後臺基於pandas等框架進行資料篩選,比較簡單,這裡就不再贅述。

2.2 自帶的資料表格下載功能

  dash_table還自帶了將當前所渲染的表格內容直接下載為csvxlsx格式檔案的簡易功能,通過引數export_format設定匯出的檔案格式,但自帶的下載按鈕樣式比較醜,如果你對此有比較高的要求,還是建議結合之前的上傳下載篇自己設計相關功能:

(資料科學學習手札117)Python+Dash快速web應用開發——互動表格篇(下)
圖5

2.3 凍結首行

  通過設定引數fixed_rows={'headers': True},我們可以實現下滑檢視錶格的過程中,始終保持表頭被凍結:

(資料科學學習手札117)Python+Dash快速web應用開發——互動表格篇(下)
圖6

3 開發一個線上取數工具

  在學習完今天的內容之後,我們來結合之前上傳下載篇中提到的下載功能,來製作一個簡單的對指定資料庫中的資料表進行快速條件篩選並下載的工具,其中DataTablederived_virtual_data屬性記錄了經過排序、條件篩選等操作後當前顯示的表格資料:

(資料科學學習手札117)Python+Dash快速web應用開發——互動表格篇(下)
圖7

app4.py

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

from flask import send_from_directory

import os
import uuid
from sqlalchemy import create_engine
import pandas as pd

try:
    os.mkdir("downloads")
except FileExistsError:
    pass

engine = create_engine('mysql+pymysql://root:mysql@localhost/DASH')

app = dash.Dash(__name__)


@app.server.route('/download/<file>')
def download(file):
    return send_from_directory('downloads', file)


app.layout = dbc.Container(
    [
        dbc.Row(
            [
                dbc.Col(dbc.Button('更新資料表', id='refresh-tables', style={'width': '100%'}), width=2),
                dbc.Col(dcc.Dropdown(id='table-select', style={'width': '100%'}), width=2)
            ]
        ),
        html.Hr(),
        dash_table.DataTable(
            id='dash-table',
            editable=True,
            page_size=15,
            style_header={
                'font-family': 'Times New Romer',
                'font-weight': 'bold',
                'text-align': 'center'
            },
            style_data={
                'font-family': 'Times New Romer',
                'text-align': 'center'
            },
            style_data_conditional=[
                {
                    # 對選中狀態下的單元格進行自定義樣式
                    "if": {"state": "selected"},
                    "background-color": "#b3e5fc",
                    "border": "none"
                },
            ],
            filter_action="native"
        ),
        html.Br(),
        html.A(id='download-url', target="_blank")
    ],
    style={
        'margin-top': '50px'
    }
)


@app.callback(
    Output('table-select', 'options'),
    Input('refresh-tables', 'n_clicks')
)
def refresh_tables(n_clicks):
    if n_clicks:
        return [
            {
                'label': table,
                'value': table
            }
            for table in pd.read_sql_query('SHOW TABLES', con=engine)['Tables_in_dash']
        ]

    return dash.no_update


@app.callback(
    [Output('dash-table', 'data'),
     Output('dash-table', 'columns')],
    Input('table-select', 'value')
)
def render_dash_table(value):
    if value:
        df = pd.read_sql_table(value, con=engine)

        return df.to_dict('records'), [
            {'name': column, 'id': column}
            for column in df.columns
        ]

    else:
        return [], []


@app.callback(
    [Output("download-url", "href"),
     Output("download-url", "children")],
    [Input("dash-table", "derived_virtual_data"),
     Input("dash-table", "filter_query")],
    prevent_initial_call=True
)
def download_table(derived_virtual_data, filter_query):
    if derived_virtual_data:
        print(derived_virtual_data)

        filename = f"output_{uuid.uuid1()}.xlsx"

        pd.DataFrame(derived_virtual_data).to_excel("downloads/" + filename, index=False)

        return "/download/" + filename, "下載當前狀態表格"

    return "", ""


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

  以上就是本文的全部內容,歡迎在評論區發表你的意見與觀點。

相關文章