Dash+Plotly | Web应用开发(1)

本文为https://github.com/CNFeffery/DataScienceStudyNotes的学习笔记,部分源码来源于此仓库。

本期内容主要为基础概念web布局方法交互回调

文章目录

Dash的主要模块

  • dash.dcc(Dash Core Components)和dash_bootstrap_components提供核心的components,如按钮、下拉菜单等。
  • dash.html是对html标记语言的Python封装。可以将dccdbc中的组件放入html容器中
  • dash.Inputdash.Output是进行callback的必用组件
  • plotly.expressplotly.graph_objects是plotly的绘图库。一般将plotly画好的图像对象传入dcc.Graph()figure参数中。dcc.Graph()是dash渲染可交互图像的方法。
  • dash.dash_tabel是展示表格的对象,具有类似excel的功能。
python 复制代码
import dash
from dash import Dash, html, dash_table, dcc, Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import plotly.graph_objects as go

Highlight

  • layout: layout负责呈现前端的页面的布局,一般为dash.html模块中的对象。通过传入多个元素或多层嵌套,可以构建更复杂的页面内容。
  • callback: callback负责处理后端的数据交互。一般使用装饰器的形式,实现前后端异步通信交互。
  • 静态/交互部件: 静态部件主要为html中的组件,比如分割线静态图片等等。可交互的组件一般为dccdbc中的部件,它们通过callback实现通信交互。这些组件通过layout组织在一起,共同组成完整的Dash应用。
  • plotly图像: 理论上其他图像也可以放入Dash应用中,但作为Dash一奶同胞的兄弟,plotly天生可与Dash完美融合。
python 复制代码
# 第一个Dash应用
# 展示一个Dash应用最基本的流程:实例化、添加组件、启动应用(实际上还有回调,后面会看到)

from dash import Dash
import dash_html_components as html
import webbrowser

# Dash实例化,所有Dash应用都应当如此
app = Dash(
    	  __name__,  # __name__不能省略
		  external_stylesheets=['css/bootstrap.min.css']  # 规定了CSS的样式,这行是可选的
		  )  

app.layout = html.H1("第一个Dash应用")  # 在布局中添加组件

if __name__ == '__main__':
    webbrowser.open("http://127.0.0.1:8050")  # 自动打开web应用。这句不是必须的,你可以手动打开。
    app.run_server()  # 启动你的Dash服务,在浏览器中打开http://127.0.0.1:8050/可以看到你的web页面。
   	
# 在终端中按下CTRL+C关闭服务

layout

dbc.Container()是组织页面布局的容器,它将页面看作N行12列的表格布局,通过设置component占多少列来设置组件的宽度。

行部件dbc.Row()组成列表传入dbc.Container()中,按照列表顺序从上至下排列。

列部件dbc.Col()组成列表传入行部件dbc.Row()中,按照列表顺序从左至右排列。

一个dbc.Row()中的部件宽度正好为12时充满整行,

小于12时右侧则会空出剩余的宽度,

大于12时将溢出的部件挤到下一行。

总而言之,嵌套等级为:Container() > Row() > Col()。如下所示:

python 复制代码
app.layout = dbc.Container(
    [
        dbc.Row(dbc.Col(html.Div("A single column"))),
        dbc.Row(
            [
                dbc.Col(html.Div("One of three columns"),width=4),
                dbc.Col(html.Div("One of three columns"),width=4),
                dbc.Col(html.Div("One of three columns"),width=4),
            ]
        ),
    ]
)

展示的效果:

ps:以上示例是官网展示的效果,但实际运行并未如期运行。知道原因大佬请指教。

  1. dbc.Row()可接受的常用参数还有justify,表示同一行的多个列元素设置对齐方式,可选项有'start''center''end''between'以及'around'五种

  2. dbc.Col()可接受的常用参数还有以下两个:

  • order: 代表同一个Row下的顺序。可接受的输入为:fristlast和1到12的整数,分别表示第一个,第十二个,和一到十二的任意一个位置。
  • offset: 代表对应Col()部件左侧增加对应的宽度,可接受的参数为1到12。

callback

每个component都有id,这是这个component的唯一索引。

callback一定是多个components之间的响应(有输入组件和输出组件),通过先输出,后输入的顺序传入对象。

  • 单个输入输出直接传入参数。
  • 多个输出输出,组成列表 后再传入参数。只要有一个输入变化就会触发回调。

如下所示:

python 复制代码
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css']
)

app.layout = html.Div(
    [
        html.Br(),
        html.Br(),
        html.Br(),
        dbc.Container(
            [
                dbc.Row(
                    [
                        dbc.Col(dbc.Input(id='input-lastname'), width=3),
                        dbc.Col(html.P('+'), width=1),
                        dbc.Col(dbc.Input(id='input-firstname'), width=3),
                    ],
                    justify='start'
                ),
                html.Hr(),
                dbc.Label(id='output1'),
                html.Br(),
                dbc.Label(id='output2')
            ]
        )
    ]
)

# 回调以装饰器的形式声明
@app.callback(
    [Output('output1', 'children'),  # 多输出时用列表的形式传入,单输出则可以直接传入。
     Output('output2', 'children')],  # Output中第一个参数为输出组件id,第二参数为做出响应的属性。
    [Input('input-lastname', 'value'),  # 多输入时用列表的形式传入,单输入则可以直接传入。
     Input('input-firstname', 'value')]  # Iutput中第一个参数为输出组件id,第二参数为需要监控的属性。
)
def input_to_output(lastname, firstname):  # 这里定义回调函数的具体内容
    try:
        return '完整姓名:' + lastname + firstname, f'姓名长度为{len(lastname+firstname)}'
    except:
        return '等待输入...', '等待输入...'  # 注意返回值,不是列表形式,多个元素直接返回即可


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

惰性交互

以上的例子中,只要输入部件属性改变,回调函数会立马触发。

但有时我们不希望回调函数立马触发,而是在我们一声号令后(比如按下某个按钮),回调函数再触发,这样的回调称为惰性交互。

惰性交互实现非常简单,在callback中增加State()对象,如下所示:

python 复制代码
import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State

# 其余部分省略...

@app.callback(
    Output('output-value', 'children'),
    Input('state-button', 'n_clicks'),  # 注意这里Input中的id是触发按钮。
    State('input-value', 'value')  # State中的id才是真正输入组件的id,
    							# 相当于State替换了立即回调模式中的Input。
)
def input_to_output(n_clicks, value):
    if n_clicks:
        return value.upper()

# 其余部分省略....

阻止初次回调

初次回调一般跟输入框有关系。因为首次打开的网页输入框都是空的,如果回调函数不支持空的输入就会产生错误。

callback中设置prevent_initial_call=True即可阻止初始回调,避免错误。

python 复制代码
# 其余部分省略
@app.callback(
    Output('output1', 'children'),
    Input('input1', 'value'),
    prevent_initial_call=True  # 阻止初次回调
)
def callback1(value):

    return int(value) ** 2

忽略回调匹配错误

在很多时候,我们需要在发生某些交互回调时,才创建一些具有指定id的部件,如果在初始化时触发了回调,则会发生错误。

callback中设置suppress_callback_exceptions=True即可忽略回调时的id匹配错误,避免错误。

python 复制代码
@app.callback(
    Output('output_desc', 'children'),
    Input('output_dropdown', 'options'),
    prevent_initial_call=True  # 忽略回调匹配错误
)
def callback2(options):
    pass

控制部分回调输出不更新

dash.no_update作为Output()的返回值,则对应的组件属性就不会更新。如:

python 复制代码
# 其余部分省略

@app.callback(
    [Output('record-1', 'children'),
     Output('record-2', 'children'),
     Output('record-n', 'children'),
     ],
    Input('button', 'n_clicks'),
    prevent_initial_call=True
)
def record_click_event(n_clicks):
    if n_clicks == 1:
        return (
            '第1次点击:{}'.format(time.strftime('%H:%M:%S', time.localtime(time.time()))),
            dash.no_update,  # 第1次点击, 2和3不参与回调
            dash.no_update
        )

    elif n_clicks == 2:
        return (
            dash.no_update,  # 第2次点击, 1和3不参与回调
            '第2次点击:{}'.format(time.strftime('%H:%M:%S', time.localtime(time.time()))),
            dash.no_update
        )

    elif n_clicks >= 3:
        return (
            dash.no_update,  # 3次及以上点击, 1和2不参与回调
            dash.no_update,
            '第3次及以上点击:{}'.format(time.strftime('%H:%M:%S', time.localtime(time.time()))),
        )

获取本轮回调的输入信息

多个输入的回调,一个输入部件就会触发回调。若想知道是哪个输入触发了回调,就要使用dash.callback_context

  • dash.callback_context.triggered: 字典;触发本轮回调组件的id和属性。
  • dash.callback_context.inputs: 字典;所有输入组件目前的id和属性。
python 复制代码
@app.callback(
    [Output('A-output', 'children'),
     Output('B-output', 'children'),
     Output('C-output', 'children'),
     Output('raw-json', 'children')],
    [Input('A', 'n_clicks'),
     Input('B', 'n_clicks'),
     Input('C', 'n_clicks')],
    prevent_initial_call=True
)
def refresh_output(A_n_clicks, B_n_clicks, C_n_clicks):

    # 获取本轮回调状态下的上下文信息
    ctx = dash.callback_context

    # 取出对应State、最近一次触发部件以及Input信息
    ctx_msg = json.dumps({
        'states': ctx.states,
        'triggered': ctx.triggered,
        'inputs': ctx.inputs
    }, indent=2)

    return A_n_clicks, B_n_clicks, C_n_clicks, ctx_msg

浏览器端执行回调

以上的回调是由服务器运算的,频繁触发会增加服务器压力。因此可以使用clientside_callbackClientsideFunction运行js脚本来将计算转移到浏览器端。示例省略(因为我不会JS)。

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪8 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试