(資料科學學習手札109)Python+Dash快速web應用開發——靜態部件篇(中)
阿新 • • 發佈:2021-02-27
> 本文示例程式碼已上傳至我的`Github`倉庫[https://github.com/CNFeffery/DataScienceStudyNotes](https://github.com/CNFeffery/DataScienceStudyNotes)
# 1 簡介
這是我的系列教程**Python+Dash快速web應用開發**的第七期,在上一期的文章中,我們對`Dash`生態裡常用的一些簡單**靜態部件**進行了介紹和功能展示,並且get到`dcc.Markdown()`這種非常方便的靜態部件。
而在今天的教程內容中,我將帶大家學習`Dash`中渲染網頁靜態表格的常用方法,並在最後的例子中教大家如何配合`Dash`,簡簡單單編寫一個數據庫查詢應用~
圖1
# 2 在Dash中渲染靜態表格
在`Dash`中渲染**靜態**表格,方法有很多,而我們今天要學習的方法,是配合之前文章介紹過的第三方拓展`dash_bootstrap_components`中的`Table()`部件,藉助`bootstrap`的特性來快速建立美觀的**靜態**表格:
圖2
## 2.1 靜態表格的構成
要學習如何基於`Dash`在前端中渲染出一張靜態表格,首先我們需要學習其元素構成,`Dash`延續`html`中`table`標籤相關概念,由`Table()`、`Thead()`、`Tbody()`、`Tr()`、`Th()`以及`Td()`等6個部件來構成一張完整的表,先從一個簡單的例子出發:
> app1.py
```Python
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
app = dash.Dash(__name__)
app.layout = html.Div(
dbc.Container(
dbc.Table(
[
html.Thead(
html.Tr(
[
html.Th('第一列'),
html.Th('第二列'),
]
)
),
html.Tbody(
[
html.Tr(
[
html.Td('一行一列'),
html.Td('一行二列'),
]
),
html.Tr(
[
html.Td('二行一列'),
html.Td('二行二列'),
]
)
]
)
]
),
style={
'margin-top': '50px' # 設定頂部留白區域高度
}
)
)
if __name__ == '__main__':
app.run_server(debug=True)
```
圖3
注意,我們這裡使用到的`Table()`部件來自`dash_bootstrap_components`,而表格其餘的構成部件均來自`Dash`原生的`dash_html_components`庫,這些部件分別的作用如下:
- **Table()**
`Table()`是一張靜態表格最外層的部件,而之所以選擇`dash_bootstrap_components`中的`Table()`,是因為其自帶了諸多實用引數,常用的如下:
> **bordered**:bool型,用於設定是否**保留**表格外邊框線
>
> **borderless**:bool型,用於設定是否**刪除**表格內部單元格框線
>
> **striped**:bool型,用於設定是否對數值行應用**斑馬著色**方案,即相鄰行背景色不同
>
> **dark**:bool型,用於設定是否應用**暗黑**主題
>
> **hover**:bool型,當設定為True時,滑鼠懸浮於某行會有對應的效果
通過上述引數,我們就可以改變靜態表格的整體效果,譬如設定`dark=True`之後的`app1.py`效果如下:
圖4
- **Thead()與Tbody()**
在部件`Table()`之下一級需要子元素`Thead()`與`Tbody()`,分別用於存放表頭資訊以及表數值內容資訊。
- **Tr()、Th()與Td()**
經過前面`Table()`巢狀`Thead()`與`Tbody()`的過程之後,我們就可以分別開始在**表頭區域**和**數值區域**正式組織資料內容。
既然是一張表格,那麼還是要按照先行後列的網格方式組織內容。而`Tr()`部件的作用就是作為行容器,其內部巢狀的子元素則是表格中每個單元格位置上的元素。
其中在`Thead()`巢狀的`Tr()`內部,需要使用`Th()`來設定每列的欄位名稱,而在`Tbody()`巢狀的`Tr()`內部,`Td()`與`Th()`都可以用來設定每個單元格的數值內容,只不過`Th()`在表現單元格數值時有加粗效果:
> app2.py
```Python
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
app = dash.Dash(__name__)
app.layout = html.Div(
dbc.Container(
dbc.Table(
[
html.Thead(
html.Tr(
[
html.Th('欄位1'),
html.Th('欄位2')
]
)
),
html.Tbody(
[
html.Tr(
[
html.Th('1'),
html.Td('test')
]
),
html.Tr(
[
html.Th('2'),
html.Td('test')
]
),
html.Tr(
[
html.Td('3'),
html.Td('test')
]
)
]
)
],
striped=True
),
style={
'margin-top': '50px' # 設定頂部留白區域高度
}
)
)
if __name__ == '__main__':
app.run_server(debug=True)
```
圖5
而`Th()`與`Td()`均有額外引數`colSpan`與`rowSpan`,可以傳入整數,來實現橫向或縱向**合併單元格**的效果,譬如下面的例子:
> app3.py
```Python
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
app = dash.Dash(__name__)
app.layout = html.Div(
dbc.Container(
dbc.Table(
[
html.Thead(
html.Tr(
[
html.Th('欄位1'),
html.Th('欄位2'),
html.Th('欄位3'),
html.Th('欄位4'),
]
)
),
html.Tbody(
[
html.Tr(
[
html.Th('1'),
# style設定水平居中
html.Td('colSpan=2', colSpan=2, style={'text-align': 'center'}),
html.Td('test'),
]
),
html.Tr(
[
html.Th('2'),
html.Td('test'),
# style設定垂直居中
html.Td('rowSpan=2', rowSpan=2, style={'vertical-align': 'middle'}),
html.Td('test')
]
),
html.Tr(
[
html.Th('3'),
html.Td('test'),
html.Td('test')
]
)
]
)
],
striped=True,
bordered=True
),
style={
'margin-top': '50px' # 設定頂部留白區域高度
}
)
)
if __name__ == '__main__':
app.run_server(debug=True)
```
圖6
## 2.2 快速表格渲染
### 2.2.1 利用列表推導快速渲染靜態表格
通過前面的內容,我們知曉了在`Dash`中如果渲染一張帶有樣式的靜態表格,而日常需求中,面對批量的資料,我們當然不可能手動編寫整張表對應的程式碼,對於數量較多的表格,我們可以配合`Python`中常用的列表推導來實現。
比如下面的例子:
> app4.py
```Python
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import pandas as pd
import numpy as np
fake_df = pd.DataFrame(np.random.rand(1000).reshape(200, 5))
fake_df.rename(lambda s: f'欄位{s}', axis=1, inplace=True) # 批量格式化列名
app = dash.Dash(__name__)
app.layout = html.Div(
dbc.Container(
dbc.Table(
[
html.Thead(
html.Tr(
[html.Th('行下標', style={'text-align': 'center'})] +
[
html.Th(column, style={'text-align': 'center'})
for column in fake_df.columns
]
)
),
html.Tbody(
[
html.Tr(
[html.Th(f'#{idx}', style={'text-align': 'center'})] +
[
html.Td(row[column], style={'text-align': 'center'})
for column in fake_df.columns
]
)
for idx, row in fake_df.iterrows()
]
)
],
striped=True,
bordered=True
),
style={
'margin-top': '50px' # 設定頂部留白區域高度
}
)
)
if __name__ == '__main__':
app.run_server(debug=True)
```
在生成表頭和每行內容時應用列表推導,使得我們的程式碼更加簡潔。
圖7
### 2.2.2 利用from_dataframe()快速渲染表格
上述的列表推導方式雖說已經簡潔了很多,但`dash_bootstrap_components`還提供了`Table.from_dataframe()`方法,可以直接傳入`pandas`資料框來快速製作簡易的靜態表格。
它的樣式相關引數與`dbc.Table()`一致,缺點是自定義表格內部元素樣式的自由度沒有前面列表推導高:
> app5.py
```Python
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import pandas as pd
import numpy as np
fake_df = pd.DataFrame(np.random.rand(1000).reshape(200, 5))
fake_df.rename(lambda s: f'欄位{s}', axis=1, inplace=True) # 批量格式化列名
app = dash.Dash(__name__)
app.layout = html.Div(
dbc.Container(
# 一行程式碼渲染靜態表格
dbc.Table.from_dataframe(fake_df, striped=True),
style={
'margin-top': '50px' # 設定頂部留白區域高度
}
)
)
if __name__ == '__main__':
app.run_server(debug=True)
```
圖8
# 3 自制簡易的資料庫查詢系統
在學習了今天的內容之後,我們就可以建立很多以表格為主體內容的web應用,典型如資料庫查詢系統,我們以`Postgresql`為例,配合`pandas`與`sqlalchemy`的相關功能,來快速打造一個簡單的資料庫查詢系統。
首先將本期附件中的所有資料表利用下面的程式碼匯入目標資料庫中:
圖9
圖10
接著只需要配合`Dash`,短短的幾十行程式碼就可以實現下面的效果:
圖11
對應程式碼如下:
> app6.py
```Python
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_core_components as dcc
from dash.dependencies import Input, Output, State
import pandas as pd
from sqlalchemy import create_engine
postgres_url = 'postgresql://postgres:填入你的密碼@localhost:5432/Dash'
engine = create_engine(postgres_url)
app = dash.Dash(__name__)
app.layout = html.Div(
dbc.Container(
[
dbc.Row(
[
dbc.Col(dbc.Button('更新資料庫資訊', id='refresh-db', style={'width': '100%'}), width=2),
dbc.Col(dcc.Dropdown(id='db-table-names', placeholder='選擇庫中資料表', style={'width': '100%'}), width=4),
dbc.Col(dbc.Button('查詢', id='query', style={'width': '100%'}), width=1)
]
),
html.Hr(),
dbc.Row(
[
dbc.Col(
id='query-result'
)
]
)
],
style={
'margin-top': '50px' # 設定頂部留白區域高度
}
)
)
@app.callback(
Output('db-table-names', 'options'),
Input('refresh-db', 'n_clicks'),
prevent_initial_call=True
)
def query_data_records(n_clicks):
# 提取目標表格並查詢其最多前500行記錄
table_names = pd.read_sql_query("select tablename from pg_tables where schemaname='public'", con=engine)
return [{'label': name, 'value': name} for name in table_names['tablename']]
@app.callback(
Output('query-result', 'children'),
Input('query', 'n_clicks'),
State('db-table-names', 'value'),
prevent_initial_call=True
)
def refresh_table_names(n_clicks, value):
if value:
query_result = pd.read_sql_query(f'select * from {value} limit 500', con=engine)
return html.Div(dbc.Table.from_dataframe(query_result, striped=True), style={'height': '600px', 'overflow': 'auto'})
else:
return dash.no_update
if __name__ == '__main__':
app.run_server(debug=True)
```
---
以上就是本文的全部內容,歡迎在評論區與我進