(数据科学学习手札115)Python+Dash快速web应用开发——交互表格篇(上)
本文示例代码已上传至我的
Github仓库https://github.com/CNFeffery/DataScienceStudyNotes
1 简介
这是我的系列教程Python+Dash快速web应用开发的第十二期,在以前撰写过的静态部件篇(中)那期教程中,我们介绍过在Dash中创建静态表格的方法。
而在实际的使用中,我们很多时候在网页中渲染的表格不仅仅是为了对数据进行展示,还需要更多交互能力,譬如按列排序、动态修改表中数值等特性,以及对大型数据表的快速渲染查看能力,诸如此类众多的交互功能在Dash自带的dash_table中已经实现。
而接下来的几期,我们就将针对如何利用dash_table创建具有丰富交互功能的表格进行介绍,今天介绍的是dash_table的基础使用方法。
图1
2 dash_table基础使用
作为Dash自带的拓展库,我们通过下列语句导入dash_table:
import dash_table
接着像之前使用其他的Dash部件一样,在定义layout时将dash_table.DataTable()对象置于我们定义的合适位置即可,可参考下面的例子配合pandas的DataFrame来完成最简单的表格的渲染。
其中参数columns用于设置每一列对应的名称与id属性,data接受由数据框转化而成的特殊格式数据,virtualization设置为True代表使用了虚拟化技术来加速网页中大量表格行数据的渲染:
app1.py
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_table
import seaborn as sns
app = dash.Dash(__name__)
# 载入演示数据集
df = sns.load_dataset('iris')
# 创建行下标列
df.insert(loc=0, column='#', value=df.index)
app.layout = html.Div(
dbc.Container(
dash_table.DataTable(
columns=[{'name': column, 'id': column} for column in df.columns],
data=df.to_dict('records'),
virtualization=True
),
style={
'margin-top': '100px'
}
)
)
if __name__ == '__main__':
app.run_server(debug=True)
如果你对数据的展示完全没要求,看个数就行,那上述的这套基础的参数设置你就可以当成万金油来使用,而如果你觉得dash_table.DataTable默认太丑了(大实话),那么请继续阅读今天的教程。
图2
2.1 自定义表格基础样式
针对DataTable所渲染出的表格的几个基础构成部分,我们可以使用到的用于修改表格样式的参数有style_table、style_cell、style_header、style_data等:
- 使用style_table来自定义表格外层容器样式
参数style_table用于对整个表格最外层的容器样式传入css键值对进行修改,一般用来设定表格的高度、宽度、周围留白或对齐等属性:
app2.py
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_table
import seaborn as sns
app = dash.Dash(__name__)
# 载入演示数据集
df = sns.load_dataset('iris')
# 创建行下标列
df.insert(loc=0, column='#', value=df.index)
app.layout = html.Div(
dbc.Container(
[
dash_table.DataTable(
columns=[{'name': column, 'id': column} for column in df.columns],
data=df.to_dict('records'),
virtualization=True,
style_table={
'height': '200px',
'margin-top': '100px'
}
),
html.Hr(),
dash_table.DataTable(
columns=[{'name': column, 'id': column} for column in df.columns],
data=df.to_dict('records'),
virtualization=True,
style_table={
'height': '200px',
'margin-left': '80px',
'width': '300px'
}
),
html.Hr(),
dash_table.DataTable(
columns=[{'name': column, 'id': column} for column in df.columns],
data=df.to_dict('records'),
virtualization=True,
style_table={
'height': '150px',
'width': '50%',
'margin-left': '50%'
}
)
],
style={
'background-color': '#bbdefb'
}
)
)
if __name__ == '__main__':
app.run_server(debug=True)
图3
- 使用style_cell、style_header与style_data定义单元格样式
不同于style_table,使用style_cell可以传入css将样式应用到所有单元格,而style_header与style_data则更加有针对性,可分别对标题单元格、数据单元格进行设置:
app3.py
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_table
import seaborn as sns
app = dash.Dash(__name__)
# 载入演示数据集
df = sns.load_dataset('iris')
# 创建行下标列
df.insert(loc=0, column='#', value=df.index)
app.layout = html.Div(
dbc.Container(
[
dash_table.DataTable(
columns=[{'name': column, 'id': column} for column in df.columns],
data=df.to_dict('records'),
virtualization=True,
style_table={
'height': '300px'
},
style_cell={
'background-color': '#fff9c4',
'font-family': 'Times New Romer',
'text-align': 'center'
}
),
html.Hr(),
dash_table.DataTable(
columns=[{'name': column, 'id': column} for column in df.columns],
data=df.to_dict('records'),
virtualization=True,
style_table={
'height': '300px'
},
style_header={
'background-color': '#b3e5fc',
'font-family': 'Times New Romer',
'font-weight': 'bold',
'font-size': '17px',
'text-align': 'left'
},
style_data={
'font-family': 'Times New Romer',
'text-align': 'left'
}
)
],
style={
'margin-top': '100px'
}
)
)
if __name__ == '__main__':
app.run_server(debug=True)
图4
- 条件样式设置
除了像上文所演示的那样针对某一类表格构成元素进行整体样式设置外,DataTable还为我们提供了条件样式设置,比如我们想为特殊的几列单独设置样式,或者为奇数下标与偶数下标行设置不同的样式,就可以使用到这一特性。
这在DataTable中我们可以利用style_header_conditional与style_data_conditional来传入列表,列表中每个元素都可看做是带有额外if键值对的css参数字典,而这个if键值对的值亦为一个字典,其接受的键值对种类丰富,我们今天先来介绍column_id与row_index,它们分别用来指定对应id的header与整行单元格。
参考下面这个例子,我们分别特殊设置#列的表头与奇数行的样式:
app4.py
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_table
import seaborn as sns
app = dash.Dash(__name__)
# 载入演示数据集
df = sns.load_dataset('iris')
# 创建行下标列
df.insert(loc=0, column='#', value=df.index)
app.layout = html.Div(
dbc.Container(
[
dash_table.DataTable(
columns=[{'name': column, 'id': column} for column in df.columns],
data=df.to_dict('records'),
virtualization=True,
style_table={
'height': '500px'
},
style_cell={
'font-family': 'Times New Romer',
'text-align': 'center'
},
style_header_conditional=[
{
'if': {
# 选定列id为#的列
'column_id': '#'
},
'font-weight': 'bold',
'font-size': '24px'
}
],
style_data_conditional=[
{
'if': {
# 选中行下标为奇数的行
'row_index': 'odd'
},
'background-color': '#cfd8dc'
}
]
)
],
style={
'margin-top': '100px'
}
)
)
if __name__ == '__main__':
app.run_server(debug=True)
图5
- 隐藏所有竖直框线
设置参数style_as_list_view为True可以隐藏所有竖向的框线,app4设置之后的效果如下:
图6
3 动手制作一个数据入库应用
学习完今天的内容之后,我们来动手写一个简单的数据入库应用,通过拖入本地csv文件以及填写入库表名,来实现对上传数据的预览与数据库导入,后端会自动检查用户输入的数据表名称是否合法,并自动检测上传csv文件的文件编码。
下面就是该应用工作时的情景,其中因为test表在库中已存在,所以会被检测出不合法:
图7
而当上传的数据表行数较多时,右下角会自动出现分页部件,我们将在下一期中进行讨论,完整代码如下:
app5.py
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
import dash_table
import dash_uploader as du
import re
import os
import pandas as pd
from sqlalchemy import create_engine
import cchardet as chardet # 用于自动识别文件编码
postgres_url = 'postgresql://postgres:CUDLCUDL@localhost:5432/Dash'
engine = create_engine(postgres_url)
app = dash.Dash(__name__)
du.configure_upload(app, 'upload')
app.layout = html.Div(
dbc.Container(
[
du.Upload(
id='upload',
filetypes=['csv'],
text='点击或拖动文件到此进行上传!',
text_completed='已完成上传文件:',
cancel_button=True,
pause_button=True),
html.Hr(),
dbc.Form(
[
dbc.FormGroup(
[
dbc.Label("设置入库表名", html_for="table-name"),
dbc.Input(
id='table-name',
autoComplete='off'
),
dbc.FormText(
"表名只允许包含大小写字母、下划线或数字,且不能以数字开头,同时请注意表名是否与库中现有表重复!", color="secondary"
),
dbc.FormFeedback(
"表名合法!", valid=True
),
dbc.FormFeedback(
"表名不合法!",
valid=False,
),
]
),
dbc.FormGroup(
[
dbc.Button('提交入库', id='commit', outline=True)
]
)
],
style={
'background-color': 'rgba(224, 242, 241, 0.4)'
}
),
dbc.Spinner(
[
html.P(id='commit-status-message', style={'color': 'red'}),
dbc.Label('预览至多前10000行', html_for='uploaded-table'),
dash_table.DataTable(
id='uploaded-table',
style_table={
'height': '400px'
},
virtualization=True,
style_as_list_view=True,
style_cell={
'font-family': 'Times New Romer',
'text-align': 'center'
},
style_header={
'font-weight': 'bold'
},
style_data_conditional=[
{
'if': {
# 选中行下标为奇数的行
'row_index': 'odd'
},
'background-color': '#cfd8dc'
}
]
)
]
)
],
style={
'margin-top': '30px'
}
)
)
@app.callback(
[Output('table-name', 'invalid'),
Output('table-name', 'valid')],
Input('table-name', 'value')
)
def check_table_name(value):
''''
检查表名是否合法
'''
if value:
# 查询库中已存在非系统表名
exists_table_names = (
pd
.read_sql('''SELECT tablename FROM pg_tables''', con=engine)
.query('~(tablename.str.startswith("pg") or tablename.str.startswith("sql_"))')
)
if (re.findall('^[A-Za-z0-9_]+$', value)[0].__len__() == value.__len__()) \
and not re.findall('^\d', value) \
and value not in exists_table_names['tablename'].tolist():
return False, True
return True, False
return dash.no_update
@app.callback(
Output('commit-status-message', 'children'),
Input('commit', 'n_clicks'),
[State('table-name', 'valid'),
State('table-name', 'value'),
State('upload', 'isCompleted'),
State('upload', 'fileNames'),
State('upload', 'upload_id')]
)
def control_table_commit(n_clicks,
table_name_valid,
table_name,
isCompleted,
fileNames,
upload_id):
'''
控制已上传表格的入库
'''
if all([n_clicks, table_name_valid, table_name, isCompleted, fileNames, upload_id]):
uploaded_df = pd.read_csv(os.path.join('upload', upload_id, fileNames[0]),
encoding=chardet.detect(open(os.path.join('upload', upload_id, fileNames[0]),
'rb').read())['encoding'])
uploaded_df.to_sql(table_name, con=engine)
return '入库成功!'
return dash.no_update
@app.callback(
[Output('uploaded-table', 'data'),
Output('uploaded-table', 'columns')],
Input('upload', 'isCompleted'),
[State('upload', 'fileNames'),
State('upload', 'upload_id')]
)
def render_table(isCompleted, fileNames, upload_id):
'''
控制预览表格的渲染
'''
if isCompleted:
uploaded_df = pd.read_csv(os.path.join('upload', upload_id, fileNames[0]),
encoding=chardet.detect(open(os.path.join('upload', upload_id, fileNames[0]),
'rb').read())['encoding']).head(10000)
uploaded_df.insert(0, '#', range(uploaded_df.shape[0]))
return uploaded_df.to_dict('record'), [{'name': column, 'id': column} for column in uploaded_df.columns]
return dash.no_update
if __name__ == '__main__':
app.run_server(debug=True)
以上就是本文的全部内容,欢迎在评论区与我进行讨论~
(数据科学学习手札115)Python+Dash快速web应用开发——交互表格篇(上)的更多相关文章
- (数据科学学习手札116)Python+Dash快速web应用开发——交互表格篇(中)
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...
- (数据科学学习手札117)Python+Dash快速web应用开发——交互表格篇(下)
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...
- (数据科学学习手札118)Python+Dash快速web应用开发——特殊部件篇
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...
- (数据科学学习手札102)Python+Dash快速web应用开发——基础概念篇
本文示例代码与数据已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的新系列教程Python+Dash快 ...
- (数据科学学习手札108)Python+Dash快速web应用开发——静态部件篇(上)
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...
- (数据科学学习手札109)Python+Dash快速web应用开发——静态部件篇(中)
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...
- (数据科学学习手札103)Python+Dash快速web应用开发——页面布局篇
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...
- (数据科学学习手札110)Python+Dash快速web应用开发——静态部件篇(下)
本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 这是我的系列教程Python+Dash快速web ...
- (数据科学学习手札123)Python+Dash快速web应用开发——部署发布篇
1 简介 这是我的系列教程Python+Dash快速web应用开发的第二十期,在上一期中我介绍了利用内网穿透的方式,将任何可以联网的电脑作为"服务器"向外临时发布你的Dash应用. ...
随机推荐
- DOM & Node.contains
DOM & Node.contains Node.contains() https://developer.mozilla.org/en-US/docs/Web/API/Node/contai ...
- Frameworkless Movement
Frameworkless Movement 无框架运动 https://www.frameworklessmovement.org/ vanilla javascript https://githu ...
- http cache & 浏览器缓存,存储位置的优先级,条件?
http cache & 浏览器缓存,存储位置的优先级,条件? memory cache disk cache 浏览器缓存,存储位置的优先级,条件, 机制,原理是什么? from memory ...
- 旅游玩乐 业务组件 UI 交互
旅游玩乐 业务组件 UI 交互 旅游业务组件 ctrip PC web https://piao.ctrip.com/dest/t4651499.html M web https://m.ctrip. ...
- js showOpenFilePicker showSaveFilePicker showDirectoryPicker API
选择文件,获取文件句柄 btn.addEventListener("click", async (e) => { try { const hFiles = await win ...
- GoEasy使用阿里云OSS出现的问题
前言:本人使用goeasy来实现微信小程序里面和其他人的im临时对话窗口,想要实现可以同时发送语音和视频.图片.表情包的话,就要通过goeasy关联到阿里云的存储对象. 报错:The OSS Acce ...
- NGK数字增益平台中如何分配代币产出
最近很多朋友听说NGK公链的主网和数字增益平台即将上线以后都纷纷表示非常感兴趣,已经基本了解了NGK代币的产出方式,但还是对代币产出分配的问题不是很明确.今天小编就给大家科普一下,NGK代币在NGK数 ...
- NGK DeFi项目即将上线,打造去中心化闭环金融生态!
据最新官方消息称:NGK已于近日宣布将进军DeFi领域,NGK此次的DeFi的项目将会是一个去中心的交易平台,其最大的功能是进行数字货币的交换.在用户选择了需要支付的数字货币和想购买的数字货币后,系统 ...
- 教你玩转CSS border(边框)
边框样式 边框样式属性指定要显示什么样的边界. border-style属性用来定义边框的样式 border-style的值 代码演示: <!DOCTYPE html> <html ...
- C++算法代码——Sumsets[uva10125]
题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=1278 题目描述 给你一个整数的集合S(里面所有的整数均不相同),请你找出最大的 d, ...