dash + fac 相關筆記

羊驼之歌發表於2024-12-06

安裝

dash 官網跳轉

pip install dash

元件庫 文件跳轉

pip install dash feffery-antd-components -U

工具庫 文件跳轉

pip install feffery-utils-components -U

樣式工具

pip install feffery-dash-utils

模組引入

import dash  # dash應用核心
from dash import html  # dash自帶的原生html元件庫
import feffery_antd_components as fac  # fac 通用元件庫
import feffery_utils_components as fuc  # fuc 通用工具庫
from dash.dependencies import Input, Output, State, ALL, MATCH, ClientsideFunction  # 用於構建應用互動功能的不同角色
from feffery_dash_utils.style_utils import style  # 樣式工具庫

例項化

import dash
from dash import html

app = dash.Dash(__name__, title="測試")

app.layout = html.Div("測試1")

if __name__ == '__main__':
    app.run(debug=True)

可用的引數

title 頁面標題

suppress_callback_exceptions=True 避免未出現的標籤的回撥報錯, 進行忽略, 多級動態頁面的時候很多標籤不是一開始就存在, 因此在校驗回撥函式的時候會有報錯

layout 定義

只能存在一個且必須存在, 後者覆蓋前者

# 單個標籤
app.layout = html.Div("測試1")


# 多個標籤
app.layout = [html.Div("測試1"), html.Div("測試2")]


# 動態內容
def make_time_div():
    return html.Div(time.time())
app.layout = make_time_div


# 簡寫
app.layout = lambda: html.Div(time.time())

佈局排列

排列

fac.AntdSpace    排列, 可水平, 可豎直  direction="vertical"

居中

fac.AntdCenter   居中, 預設基於元素最大寬度, 可設定基於父元素  style={"width": "100%"}

fac.AntdRow    按照24個單元長度定義, gutter 設定間距, 傳兩個就分別橫向和縱向

fac.AntdCol     span=4 設定寬度, 一行最多24 超過自動換行, 子元素block=True  可以撐滿父元素   

樣式 - 預設方式

  style={
        "background": "#bae7ff",  # 背景色
        "width": "100%",  # 寬度
        "height": 300,  # 高度
        "padding": 30,  # 內邊距 格式: 數字/字串 如: "30px 50px" 上下 左右 "30px 40px 50px 60px" 上右下左
        "margin": 50,  # 外邊距 
        "position": "relation",  # 佈局型別 absolute 絕對(預設) relation 相對(父) fixed 固定
        "left": 50,  # 絕對佈局定位使用,  left/right/top/bottom 可正可負
        "zIndex": 10,   # z 軸高度, 數越大越靠上
  }

樣式 - 工具方式

# 使用feffery-dash-utils庫,可以不用使用鍵值對的方式設定style
from feffery_dash_utils.style_utils import style  # 樣式工具庫匯入

# 屬性會有提示, 如果屬性沒有時可以使用字典拆包的方式,**{'margin':'100px'} 
 style=style(padding=10, background="red", fontSize=200)

全域性樣式

透過 className 指定類名. 然後在專案路徑下建立 assets 資料夾, 裡面的 .ccs 檔案全域性生效

回撥函式

匯入

from dash.dependencies import Input, Output, State

示例

# 輸出寫在前面, 輸入寫在後面
# 可以多個輸出, 則返回也要多個, 按順序傳出, 多個輸出要用 [] 包一下
# 可以跟多個輸入, 多個輸入則就需要多個引數, 按順序入參, 不需要用 [] 包
# State會進行記錄, 但是不會即時顯現, 需要在 Input 的觸發下才會顯現
# prevent_initial_call 可以阻止初次執行, 避免頁面載入就觸發
@app.callback([Output("output-show", "children"),
              Output("output-show2", "children")],
              Input("input-button-1", "nClicks"),
              State("input-button-2", "nClicks"),
              prevent_initial_call=True)
def update_output_show_children(n_1, n_2):
    n_1 = n_1 or 0
    n_2 = n_2 or 0
    # 全部不更新
    if n_1 % 2:
        return dash.no_update
    # 不更新前一個
    if n_1 == 3:
        return dash.no_update, f"總點選次數: {n_1 + n_2}"

    return [f"按鈕1點選次數: {n_1}, 按鈕2點選次數: {n_2}", f"總點選次數: {n_1 + n_2}"]

說明

callback 傳入 Output 作為輸出, 多個則按順序 return 傳出

可傳入多個 Input 進行輸入, 傳入多個則邏輯函式需要相同數量引數, 按順序入參

State 同樣作為 入參, 按照順序傳入

Input 作為觸發條件, 而 State 需要基於 Input 才可以觸發

演示

結合上面示例, 按鈕 1 點選後. 則後面的數字都會變化

但是 按鈕 2 點選後, 後面數字未變化

但是 再次點選 按鈕 1, 數字變化後, 可以看到 按鈕 2 的點選是被記錄下來的

只有 按鈕 1 點選後才可以讓 按鈕 2 的操作顯現

瀏覽器回撥

部分場景下要求較高 , 需要使用原生的 js 的方式進行回撥從而避免與伺服器的請求導致的延遲

使用 app.clientside_callback 定義 js 的相關回撥程式碼, 程式碼較為簡單的可以直接字串在第一個引數中傳遞, 後面還是傳輸出和輸入

app.clientside_callback(
    "(nClick) => true",
    Output('my-div' 'children'),
    [Input('my-input', 'value'),
     Input('another-input', 'value')]
)

如果較為複雜的 js 程式碼可以使用 js 檔案定義後, 以檔案的方式進行引入

from dash.dependencies import Input, Output, ClientsideFunction 

       app.clientside_callback(
            ClientsideFunction('my_clientside_library', 'my_function'),
            Output('my-div' 'children'),
            [Input('my-input', 'value'),
             Input('another-input', 'value')]
        )

示例

使用 clientside_callback 選擇 namespace 和 方法名即可, 定義的 js程式碼檔案放在專案的 assets 資料夾下

set_props 更新屬性

Output 作為回撥定義的輸出, 也可以在函式內使用 set_props 做同樣的操作

示例

如下, 實現一個點選按鈕同時輸出一個資訊提醒和更新顯示點選次數

import time
import dash  # dash應用核心

from dash import html  # dash自帶的原生html元件庫
import feffery_antd_components as fac  # fac 通用元件庫
import feffery_utils_components as fuc  # fuc 通用工具庫
from dash.dependencies import Input, Output, State, ALL, MATCH, ClientsideFunction  # 用於構建應用互動功能的不同角色
from feffery_dash_utils.style_utils import style  # 樣式工具庫

app = dash.Dash(__name__, title="測試系統")

app.layout = html.Div(
    [
        fac.Fragment(id="message"),
        fac.AntdSpace(
            [
                fac.AntdButton("執行計算", id="cal", type="primary"),
                fac.AntdText(id="result")
            ]
        ),
    ],
    style=style(padding=50)
)


# 1. 常規方式實現
# @app.callback(
#     [Output("message", "children"),
#      Output("result", "children")],
#     Input("cal", "nClicks"),
#     prevent_initial_call=True
# )
# def cal_and_alert_message(n_click):
#     return fac.AntdMessage(content="執行成功", type="info"), f"第{n_click}次計算成果"


# 1. set_props 方式實現
@app.callback(
    # Output 裡面就不用寫了, 可以直接寫在具體下面的函式邏輯裡面用 set_props 
    Output("result", "children"),
    Input("cal", "nClicks"),
    prevent_initial_call=True
)
def cal_and_alert_message(n_click):
    # 傳入要更新的元件的 id, 以及要更新的屬性
    dash.set_props(component_id="message", props={"children": fac.AntdMessage(content="執行成功", type="info")})
    # set_props 的更新不是執行完這行程式碼就更新了, 依舊是在return 之後才會更新
    # 如果下面 time.sleep(3) 就 3s 後才可以看到效果
    return f"第{n_click}次計算成果"


if __name__ == '__main__':
    app.run(debug=True)

Patch 區域性更新

匯入

from dash import Patch

案例

在下面的案例中點選每次在原有的基礎上新增一個元素, 這樣需要將原始的資訊用State 拿到後傳回去

如果資料量很大的情況下頻繁地拿取和回傳原始資訊回對效能造成壓力

使用Patch 則可以對輸出的物件直接進行追加, 不需要先拿過來在更新後傳過去

這樣只需要傳過去要追加的資訊即可

import time
import dash  # dash應用核心

from dash import html, Patch  # dash自帶的原生html元件庫
import feffery_antd_components as fac  # fac 通用元件庫
import feffery_utils_components as fuc  # fuc 通用工具庫
from dash.dependencies import Input, Output, State, ALL, MATCH, ClientsideFunction  # 用於構建應用互動功能的不同角色
from feffery_dash_utils.style_utils import style  # 樣式工具庫

app = dash.Dash(__name__, title="測試系統")

app.layout = html.Div(
    fac.AntdSpace([
        fac.AntdButton("執行計算", id="add", type="primary"),
        fac.AntdSpace([], id="items", direction="vertical")],
        direction="vertical"
    ),
    style=style(padding=50)
)


# 1. 傳統方式, 定義 State 把老的元素拿出來之後進行追加更新
# 資料量大元素複雜的的情況下效能較差
# @app.callback(
#     Output("items", "children"),
#     Input("add", "nClicks"),
#     State("items", "children"),
#     prevent_initial_call=True
# )
# def add_items(n_clicks, o_items):
#     return [*o_items, f"row {n_clicks}"]

# 2. Patch, 不需要定義 State 拿到原始資料
# 只需要將當前要追加的資訊進行處理即可
@app.callback(
    Output("items", "children"),
    Input("add", "nClicks"),
    prevent_initial_call=True
)
def add_items(n_clicks):
    p = Patch()
    p.append(f"row {n_clicks}")
    return p


if __name__ == '__main__':
    app.run(debug=True)

ALL 模式匹配

匯入

from dash.dependencies import Input, Output, ALL

案例

50個按鈕, 想統計所有按鈕的點選次數的加和

如果使用傳統的方式那就要寫50個Input了, 採用 ALL 可以一次性監控所有的複合條件的 Input 即可

id 採用字典模式定義, 用部分精準, 部分模糊的方式去匹配即可

import time
import dash  # dash應用核心

from dash import html, Patch  # dash自帶的原生html元件庫
import feffery_antd_components as fac  # fac 通用元件庫
import feffery_utils_components as fuc  # fuc 通用工具庫
from dash.dependencies import Input, Output, State, ALL, MATCH, ClientsideFunction  # 用於構建應用互動功能的不同角色
from feffery_dash_utils.style_utils import style  # 樣式工具庫

app = dash.Dash(__name__, title="測試系統")

app.layout = html.Div(
    fac.AntdSpace(
        [
            fac.AntdSpace(
                [
                    # ALL 的情況下, 需要讓 id 使用字典模式
                    fac.AntdButton(
                        f"按鈕{i}", id={"type": "button", "index": i}, type="primary"
                    )
                    for i in range(50)
                ],
                wrap=True
            ),
            fac.AntdText(id="all-click-sum")
        ],
        direction="vertical",
        style=style(width="100%")
    ),
    style=style(padding=50)
)


@app.callback(
    Output("all-click-sum", "children"),
    # 匹配基於想要匹配的屬性調整為 ALL 即可
    Input({"type": "button", "index": ALL}, "nClicks"),
    prevent_initial_call=True
)
def show_all_click_sum(n):
    return f"共點選了 {sum([i for i in n if i])}"


if __name__ == '__main__':
    app.run(debug=True)

相關文章