在先前的介绍过快速数据可视化界面工具Streamlit,Dash是一个与之非常类似的工具,个人在使用Streamlit加载地图呈现时遇到响应非常慢的的问题,于是使用了Dash整理使用起来也非常的简单,这里做下简要的分享。
DASH简介
Dash是一个开源的Python框架,用于创建基于Web的应用程序。它由Plotly公司开发,专为数据科学家和分析家设计,以便他们可以构建自定义的数据可视化Web应用程序,而无需知道太多的前端开发知识。
以下是Dash的一些主要特性:
- 交互性:Dash提供了丰富的交互式组件库,如图表、表格、滑块等,可以根据用户的操作动态更新。
- Pure Python:Dash应用程序完全使用Python编写,无需JavaScript、HTML或CSS的知识。虽然对前端知识的了解可能有助于微调应用程序,但并非必需。
- 可扩展性:Dash使用js进行图形渲染,支持高度定制化和交互式图表。还可以扩展现有的React.js库以创建自定义组件。
- 部署简单:Dash应用程序可以轻松部署到服务器或云平台,方便与他人共享和协作。
- 企业级应用:Plotly提供了一个名为Dash Enterprise的商业产品,它增加了许多功能,如数据缓存、分析、身份验证和更多。
DASH的使用
Dash的基本结构
Dash应用程序的基本结构主要由两个部分组成:布局(Layout)和交互(Callbacks)。
布局(Layout)
布局定义了应用程序的基本结构,可以看作是一个HTML模板。Dash使用Python类表示HTML标签。例如,html.Div对应HTML的<div>标签,html.H1对应HTML的<h1>标签等等。这些类的实例通过嵌套的Python列表来组织,形成树形的结构。
Dash提供了两种类型的组件:
- dash_html_components:这个库提供了一套纯粹基于HTML的组件集,如Div,Label,P(段落)等等,用于创建HTML内容。
- dash_core_components:这个库提供了一套基于js的丰富的交互式组件,如Dropdown,Slider,Graph等等,用于创建具有交互性的应用程序。
一个简单的布局示例如下:
app.layout = html.Div([ html.H1('Hello Dash'), html.Div([ html.P('Dash converts Python classes into HTML'), html.P('This conversion happens behind the scenes by Dashs JavaScript front-end') ]) ])
以下是一个使用dash_core_components的布局示例:
app.layout = html.Div([ dcc.Graph(id='my-graph'), dcc.Slider(id='my-slider', min=0, max=10, step=0.5, value=5) ])
这将生成一个包含图形和滑块的<div>标签。
总的来说,理解布局和Dash组件是创建Dash应用程序的关键。通过嵌套和组合这些组件,我们可以创建出各种复杂的应用程序界面。
交互(Callbacks)
在Dash中,交互是通过“回调”机制实现的。回调函数定义了应用程序如何响应用户的操作,如点击按钮,拖动滑块等。
每个回调函数主要由三部分组成:输入(Input)、输出(Output)和状态(State)。
- 输入(Input):这是触发回调函数的事件源,如按钮的点击、滑块的拖动等。一个回调函数可以有多个输入。
- 输出(Output):这是回调函数的结果要显示的地方,通常是某个组件的属性。一个回调函数可以有多个输出。
- 状态(State):这是回调函数的额外输入,但它的变化不会触发回调函数。只有当其他输入触发回调函数时,状态才会被读取。
这些输入、输出和状态都是通过dash.dependencies.Input,dash.dependencies.Output和dash.dependencies.State类来定义的。
以下是一个简单的回调函数示例:
@app.callback( Output('my-graph', 'figure'), [Input('my-slider', 'value')] ) def update_graph(slider_value): return {'data': [{'y': [i*slider_value for i in range(10)]}]}
这个回调函数将’my-slider’的’value’属性作为输入,将’my-graph’的’figure’属性作为输出。每当滑块的值改变时,都会触发这个回调函数,生成一个新的图形数据,并更新到图形组件中。
需要注意的是,Dash强制回调函数具有唯一的输出。这是为了避免多个回调函数更新同一个组件属性带来的冲突。如果需要更新多个输出,可以使用dash.callback_context来检查是哪个输入触发了回调函数。
由于回调函数是在服务器端执行的,因此在设计回调函数时需要注意性能问题。避免在回调函数中执行时间消耗大的操作,如复杂的计算或网络请求。如果必须这样做,可以考虑将结果缓存起来,以提高响应速度。
完整示例
下面是一个简单但完整的Dash应用程序示例。这个应用程序包含一个滑块和一个图形。当您移动滑块时,图形上的数据将会改变。
# 导入所需的库 import dash import dash_core_components as dcc import dash_html_components as html from dash.dependencies import Input, Output # 创建Dash应用实例 app = dash.Dash(__name__) # 定义布局 app.layout = html.Div([ dcc.Slider( id='my-slider', min=0, max=10, step=0.5, value=5, ), dcc.Graph(id='my-graph'), ]) # 定义回调 @app.callback( Output('my-graph', 'figure'), [Input('my-slider', 'value')] ) def update_graph(slider_value): return { 'data': [{ 'x': list(range(10)), 'y': [i*slider_value for i in range(10)], 'type': 'bar' }] } # 运行应用 if __name__ == '__main__': app.run_server(debug=True)
你可以将这段代码保存为Python文件,然后运行它。你将看到一个网页应用程序,其中包含一个滑块和一个图形。当你移动滑块时,图形会动态更新。
这个示例非常简单,但它展示了Dash应用程序的基本结构和工作原理:定义布局,添加交互性,然后运行应用程序。你可以根据自己的需求,添加更多的组件和回调,创建更复杂的应用程序。
Dash复杂布局
Dash的布局方案主要有以下几种:
- 单列布局:这是最基本的布局形式,所有的内容都按照顺序排列在一列中。
- 多列布局:你可以使用dash_html_components的Div组件和CSS来创建多列布局。
- 选项卡布局:你可以使用dash_core_components的Tabs和Tab组件来创建选项卡布局。
- 侧边栏布局:你可以使用dash_bootstrap_components的Sidebar组件来创建侧边栏布局。
- 网格布局:你可以使用dash_bootstrap_components的Container, Row 和 Col 组件来创建基于网格系统的响应式布局。
注意,为了创建复杂的布局,你可能需要学习一些CSS知识。你可以使用内联样式,也可以创建一个外部的CSS文件,并通过app = dash.Dash(__name__, external_stylesheets=[…])来引用它。
你也可以使用预构建的CSS框架,如Bootstrap,来简化布局和样式的编写。
Dash可以很方便地与Bootstrap一起使用,以便利用Bootstrap的响应式网格系统和预定义样式。这可以通过一个第三方库dash-bootstrap-components来实现。
首先,你需要安装dash-bootstrap-components库,运行如下命令:
pip install dash-bootstrap-components
然后,你需要在你的Dash应用中引入Bootstrap的CSS。你可以使用Bootstrap的CDN,也可以下载Bootstrap的CSS文件并放在你的应用目录中。以下是一个使用CDN的例子:
import dash import dash_bootstrap_components as dbc app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
现在,你可以在你的应用中使用dash-bootstrap-components提供的组件了。这些组件大部分与Bootstrap的类名相同,比如dbc.Row、dbc.Col、dbc.Button等等。
以下是一个使用Bootstrap网格系统的例子:
import dash import dash_bootstrap_components as dbc import dash_html_components as html app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP]) app.layout = dbc.Container([ dbc.Row([ dbc.Col(html.Div("This is column 1"), width=4), dbc.Col(html.Div("This is column 2"), width=8), ]) ]) if __name__ == '__main__': app.run_server(debug=True)
在这个例子中,我们创建了一个包含两列的行,第一列占据了总宽度的4份,第二列占据了总宽度的8份。Bootstrap的网格系统总宽度为12,你可以根据需要分配每列的宽度。
更多关于dash-bootstrap-components的信息,你可以查阅其官方文档。
Dash的数据表格组件
Dash的数据表格组件(dash_table.DataTable)是一个灵活且强大的工具,能够呈现、编辑和操作数据。以下是如何创建和使用DataTable的基本步骤:
首先,你需要在代码中导入dash_table模块,然后创建一个DataTable实例,并将其添加到你的布局中。例如:
import dash import dash_table import pandas as pd # 创建一个简单的DataFrame df = pd.DataFrame({ 'Column 1': ['Value 1', 'Value 2', 'Value 3'], 'Column 2': ['Value 4', 'Value 5', 'Value 6'] }) app = dash.Dash(__name__) app.layout = html.Div([ dash_table.DataTable( id='table', columns=[{"name": i, "id": i} for i in df.columns], data=df.to_dict('records'), ) ]) if __name__ == '__main__': app.run_server(debug=True)
在这个例子中,DataTable的columns属性接受一个字典列表,其中每个字典表示一列,name键是列的标题,id键是列的标识符。data属性接受一个字典列表,其中每个字典代表一行数据。
此外,DataTable还提供了许多其他属性,可以用来自定义表格的外观和行为,如:
- filter_action:设置为’native’,可以启用内建的过滤功能。
- sort_action:设置为’native’,可以启用内建的排序功能。
- page_action:设置为’native’,可以启用内建的分页功能。
- style_data_conditional:这是一个字典列表,可以包含一些条件和样式,用来根据单元格的值改变其样式。
- editable:设置为True,可以让用户直接在表格中编辑数据。
这只是DataTable的一部分功能,更多高级功能和详细用法可以参考Dash的官方文档。
Dash AG Grid
Dash AG Grid是一个基于AG Grid的Dash组件。AG Grid是一个功能强大的JavaScript数据网格,支持多种框架。它提供了丰富的功能,如排序、过滤、分组、树形视图、行拖拽、列调整等。
Dash AG Grid组件使得AG Grid能够集成到Dash应用中,从而提供更高级的数据表格功能。
以下是一个Dash AG Grid的基本示例:
import dash import dash_ag_grid import pandas as pd # 创建一个简单的DataFrame df = pd.DataFrame({ 'Column 1': ['Value 1', 'Value 2', 'Value 3'], 'Column 2': ['Value 4', 'Value 5', 'Value 6'] }) app = dash.Dash(__name__) app.layout = dash_ag_grid.AgGrid( data=df.to_dict('records'), columnDefs=[{'headerName': i, 'field': i} for i in df.columns], enableColResize=True, enableSorting=True, enableFilter=True, ) if __name__ == '__main__': app.run_server(debug=True)
在这个例子中,AgGrid组件的data属性接收一个字典列表,每个字典代表一行数据;columnDefs属性接收一个字典列表,每个字典代表一列,headerName是列的标题,field是列的标识符。enableColResize、enableSorting和enableFilter属性分别开启了列调整、排序和过滤功能。
此外,Dash AG Grid还支持更多AG Grid的高级功能,如:
- 复杂的单元格渲染:你可以为cellRenderer属性指定一个JavaScript函数,用于自定义单元格的渲染方式。
- 行和列的拖拽:你可以设置rowDragManaged、suppressMovableColumns等属性,以开启行拖拽和列调整功能。
- 分组和树形视图:你可以设置groupUseEntireRow、treeData等属性,以开启分组和树形视图功能。
更多用法和详细信息,请参考Dash AG Grid的官方文档。
Dash Leaflet
Dash Leaflet是一个为Dash提供的轻量级地图插件,它是基于JavaScript库Leaflet.js的Python封装。Leaflet是一个开源的JavaScript库,用于构建移动友好的交互式地图。类似Folium。
以下是一个基本的Dash Leaflet示例:
import dash import dash_html_components as html import dash_leaflet as dl app = dash.Dash(__name__) app.layout = html.Div([ dl.Map([ dl.TileLayer() ], style={'width': '1000px', 'height': '500px'}) ], style={'display': 'flex', 'justify-content': 'center', 'align-items': 'center', 'height': '100vh'}) if __name__ == '__main__': app.run_server(debug=True)
在这个例子中,dl.Map组件创建了一个地图,dl.TileLayer组件添加了一个基础的切片图层,图层的来源默认是OpenStreetMap。
Dash Leaflet还提供了许多其他组件,可以实现更多的地图功能,如:
- Marker:在地图上添加标记。
- Popup:添加一个可以显示额外信息的弹窗,通常与dl.Marker一起使用。
- Polyline、dl.Polygon、dl.Rectangle:在地图上绘制线、多边形和矩形。
- Circle、dl.CircleMarker:在地图上绘制圆和圆形标记。
- LayerGroup、dl.FeatureGroup:用于组合多个层。
此外,Dash Leaflet还支持GeoJSON格式的数据,并提供了dl.GeoJSON组件来显示这种数据。你可以使用dl.Choropleth组件来创建填充区域地图。
更多用法和详细信息,请参考Dash Leaflet的GitHub页面。
多页面应用
Dash 能够让你创建多页面应用。在Dash中,你可以使用dash_core_components.Location组件和dash_core_components.Link组件来创建多页面应用。
以下是一个简单的多页面应用示例:
import dash import dash_core_components as dcc import dash_html_components as html from dash.dependencies import Input, Output app = dash.Dash(__name__, suppress_callback_exceptions=True) app.layout = html.Div([ dcc.Location(id='url', refresh=False), html.Div(id='page-content') ]) index_page = html.Div([ dcc.Link('Go to Page 1', href='/page-1'), html.Br(), dcc.Link('Go to Page 2', href='/page-2'), ]) page_1_layout = html.Div([ html.H1('Page 1'), dcc.Link('Go back to home', href='/'), ]) page_2_layout = html.Div([ html.H1('Page 2'), dcc.Link('Go back to home', href='/'), ]) @app.callback(Output('page-content', 'children'), Input('url', 'pathname')) def display_page(pathname): if pathname == '/page-1': return page_1_layout elif pathname == '/page-2': return page_2_layout else: return index_page if __name__ == '__main__': app.run_server(debug=True)
在这个例子中,dcc.Location组件用于获取当前的URL,dcc.Link组件用于创建链接。通过监听dcc.Location组件的pathname属性,我们可以判断用户当前访问的是哪个页面,并返回相应的内容。注意,在URL中,根路径(/)对应的是首页。
此外,在构建多页面应用时,你可能需要将每个页面的布局和回调函数放在不同的模块中,以保持代码的清晰和可维护。为了使这些模块中的回调函数能够正常工作,你需要在创建dash.Dash实例时,将suppress_callback_exceptions参数设置为True,以允许Dash在启动时不检查回调函数的输入和输出是否在初始布局中。
在 Dash 中,可以利用dash_core_components.Location组件来获取 URL 中的参数,这个组件的 search 属性会返回 URL 中的查询参数字符串。
以下是一个使用 URL 参数的示例:
import dash import dash_core_components as dcc import dash_html_components as html from dash.dependencies import Input, Output from urllib.parse import parse_qs app = dash.Dash(__name__, suppress_callback_exceptions=True) app.layout = html.Div([ dcc.Location(id='url', refresh=False), html.Div(id='page-content') ]) @app.callback(Output('page-content', 'children'), Input('url', 'pathname'), Input('url', 'search')) def display_page(pathname, search): params = parse_qs(search[1:]) # 解析查询参数 page = html.Div([ html.H3('The pathname is "{}"'.format(pathname)), html.H3('The search is "{}"'.format(search)), html.H3('The parameters are "{}"'.format(params)), ]) return page if __name__ == '__main__': app.run_server(debug=True)
在这个例子中,如果你访问http://localhost:8050/page-1?param1=value1¶m2=value2,pathname将是/page-1,search将是?param1=value1¶m2=value2,params将是{‘param1’: [‘value1’], ‘param2’: [‘value2’]}。
这样,你就可以根据查询参数来改变页面的内容。比如,你可以根据参数来显示不同的数据、应用不同的筛选条件等等。
注意,查询参数字符串的开头是一个?,在解析它之前需要先去掉。另外,urllib.parse.parse_qs函数会将每个参数的值解析成列表,如果你知道参数只有一个值,可以使用列表的[0]索引来获取这个值。
Dash部署到JupyterLab
安装必要的包:
pip install dash pip install jupyter-dash
重建 jupyter lab环境:jupyter lab build
使用示例:
from dash import Dash, dcc, html import plotly.express as px import pandas as pd from jupyter_dash import JupyterDash #app = Dash(__name__) app = JupyterDash(__name__) df = pd.read_csv('gdp-life-exp-2007.csv') fig = px.scatter(df, x="gdp per capita", y="life expectancy", size="population", color="continent", hover_name="country", log_x=True, size_max=60) app.layout = html.Div([ dcc.Graph( id='life-exp-vs-gdp', figure=fig ) ]) if __name__ == '__main__': # app.run_server(mode='jupyterlab') app.run_server(mode='inline') # app.run_server(mode='external')
以下是一些实用的Dash学习资源和参考网站:
- Dash 官方文档: 这是最权威、最全面的Dash资源,包括了介绍、教程、示例和API文档。
- GitHub – plotly/dash: 基于React、Plotly.js和Flask的分析Web应用框架: 你可以在这里查看源代码,了解Dash的最新更新,以及一些示例代码。
- Medium 上的 Dash 教程: 这里有许多由社区成员编写的教程和案例分析。
- Awesome Dash: 这是一个收集了很多Dash资源的列表,包括了库、工具、教程和示例。