1简介这是我的《Python+Dash快速Web应用开发》系列教程的第十三期。交互式表格组件dash_table,学习了如何自定义表格不同部分的样式。在今天的教程中,我们将继续深入了解dash_table的交互功能,学习如何对渲染后的表格进行分页,添加动态修改内容等交互功能。图12dash_table的基本交互能力dash_table的核心功能是为用户提供与图表快速交互的能力。下面我们来学习一下它的一些基本和常用的交互功能:2.1翻页当我们要显示大量的数据行时,在网页中可以选择Pages进行渲染,在dash_table中实现起来比较方便。根据数据传输方式的不同,可以分为“前端分页”和“后端分页”:2.1.1前端分页前端分页,顾名思义就是当我们访问Dash的时候application,一次性加载表中所有页面的数据,适用于数据量不大,将数据存储压力转移给浏览器的情况。通过参数page_size设置每页显示的记录行数,Dash会自动帮我们分页,并添加翻页组件:app1.pyimportdashimportdash_bootstrap_componentsasdbcimportdash_tableimportseabornassnsdf=sns.load_dataset('tips')df.insert(0,'#',df.index)app=dash.Dash(__name__)app.layout=dbc.Container([dash_table.DataTable(id='dash-table',data=df.to_dict('records'),columns=[{'name':column,'id':column}forcolumnindf.columns],page_size=15,#设置单页显示15行记录style_header={'font-family':'TimesNewRomer','font-weight':'bold','text-align':'center'},style_data={'font-family':'TimesNewRomer','text-align':'center'})],style={'margin-top':'50px'})if__name__=='__main__':app.run_server(debug=True)图22.1.2后端分页虽然前端分页简单好用,但是当我们的数据量很大的时候,强行使用前端分页会导致“网络传输”和“浏览器端”带来大量的延迟和内存压力,严重影响用户体验,所以Dash贴心的为我们准备了“后台分页”的方式。这个时候首先我们要为DataTable设置参数page_action='custom',这是使用后端分页的前提。接下来,我们需要知道一些新的参数:page_current,int类型,对应当前页码;page_count,int类型,对应显示的总页数;我们在使用“后端分页”的时候,实际上是利用用户当前翻页的页码和设置的page_size,在翻页后动态加载对应的一批数据。并控制显示的总页数,参考下面的简单例子: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}forcolumnindf.columns],page_size=15,#设置单页显示15行记录page_action='custom',page_current=0,style_header={'font-family':'TimesNewRomer','font-weight':'bold','text-align':'center'},style_data={'font-family':'TimesNewRomer','text-align':'center'}))],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')])defrefresh_page_data(page_current,page_size):returndf.iloc[page_current*page_size:(page_current+1)*page_size].to_dict('records'),1+df.shape[0]//page_sizeif__name__=='__main__':app.run_server(debug=True)可以看到,即使我们完整的数据集是concat起来的到24万行,加载应用和翻页依然轻松无压力。在实际应用中,还可以将翻页部分改为由LIMIT和OFFSET控制的数据库查询过程,使应用程序运行更快、更高效。:图32.2单元格内容编辑说完翻页,我们来了解dash_table中更强大的功能——单元格内容编辑。当然,现代Web应用程序不能仅限于查看数据。Dash还给出了为了让我们能够双击数据表格的单元格来编辑数据,首先要设置参数editable=True,即打开表格编辑模式,然后我们就可以双击任意一个单元格了在数据区进行选择和编辑。但是Dash默认的单元格选中样式是丑陋的(你可以相信它是粉红色的),所以我们可以使用下面的参数设置方法来自定义美化:style_data_conditional=[{#CustomizethecellinselectedstateStyle"if":{"state":"selected"},"background-color":"#b3e5fc","border":"none"},]看图片的例子,我们用“前端分页”的方式修改随意渲染表格,并打印使用pandas比较的数据帧之间的差异,如下所示:容器([dash_table.DataTable(id='dash-table',data=df.to_dict('records'),columns=[{'name':column,'id':column}forcolumnindf.columns],fixed_rows={'headers':True},page_size=15,editable=True,style_header={'font-family':'TimesNewRomer','font-weight':'bold','text-align':'center'},style_data={'font-family':'TimesNewRomer','text-align':'center'},style_data_conditional=[{#自定义选中状态的单元格样式"if":{"state":"selected"},"background-color":"#b3e5fc","border":"none"},]),html.H4('与原表格内容对比:',style={'margin-top':'50px'}),dcc.Markdown('无差异',id='markdown',dangerously_allow_html=True)],style={'margin-top':'50px'})@app.callback(Output('markdown','children'),Input('dash-table','data'),prevent_initial_call=True)defcompare_difference(dash_table_data):print(pd.DataFrame(dash_table_data))returndf.compare(pd.DataFrame(dash_table_data)).to_html()if__name__=='__main__':app.run_server(debug=True)如你所见,我们成功了对指定的单元格元素进行了修改图43开发一个数据库内容在线更新工具学习完今天的内容,我们可以开发一个简单的工具,可以在线自由修改并同步到数据库。这里我们以MySQL数据库为例,对示例表进行修改和更新:首先我们使用如下代码新建一个表tips到示例数据库中:fromsqlalchemyimportcreate_engineimportseabornassnsdf=sns.load_dataset('tips')df.insert(0,'#',df.index)engine=create_engine('mysql+pymysql://root:mysql@localhost/DASH')df.to_sql('tips',con=engine,if_exists='replace',index=False)图5下面我们以创建的tips表为例,开发一个DashApply,修改数据更新到数据库:图6的效果很好。大家可以根据我这个简单的例子扩展更多的新功能,也可以使用后端分页+条件修改来处理大数据表的修改,全部代码如下:app4.pyimportdashimportdash_bootstrap_componentsasdbcimportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_tablefromdash.dependenciesimportimport_bootstrap_componentsasdbcimportdash_core_componentsasdccimportdash_html_componentsashtmlimportdash_tablefromdash.dependenciesimportimportimportinput,Output,StatefromsqlenginescreatedimportInput,Output,StatefromsqlenginescreatedimportInput,Output,Statefromsqlenginesalchemy'+pymysql://root:mysql@localhost/DASH')app=dash.Dash(__name__)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%'}),宽度=2)]),html.Hr(),dash_table.DataTable(id='dash-table',editable=True,page_size=15,style_header={'font-family':'TimesNewRomer','font-weight':'bold','text-align':'center'},style_data={'font-family':'TimesNewRomer','text-align':'center'},style_data_conditional=[{#对选中状态下的单位格式进行自定义样式"if":{"state":"selected"},"background-color":"#b3e5fc","border":"none"},]),dbc.Button('同步变量到数据库',id='update-tables',style={'display':'none'}),html.P(id='message')],style={'margin-top':'50px'})@app.callback(Output('table-select','options'),Input('refresh-tables','n_clicks'))defrefresh_tables(n_clicks):ifn_clicks:return[{'label':table,'value':table}fortableinpd.read_sql_query('SHOWTABLES',con=engine)['Tables_in_dash']]returndash.no_update@app.callback([Output('dash-table','data'),Output('dash-table','columns'),Output('update-tables','style')],Input('table-select','value'))defrender_dash_table(value):ifvalue:df=pd.read_sql_table(value,con=engine)returndf.to_dict('records'),[{'name':column,'id':column}forcolumnindf.columns],{'margin-top':'25px'}else:return[],[],{'display':'none'}@app.callback([Output('message','children'),Output('message','style')],Input('update-tables','n_clicks'),[State('dash-table','data'),State('table-select','value')])defupdate_to_database(n_clicks,data,value):ifn_clicks:try:pd.DataFrame(data).to_sql(value,con=engine,if_exists='replace',index=False)return'更新成功!',{'color':'green'}exceptExceptionase:returnf'更新失败!{e}',{'color':'red'}returndash.no_updateif__name__=='__main__':app.run_server(debug=True)
