You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Python Dash中用JavaScript编写clientside_callback时遇错误求助

问题描述

我之前研究过如何使用clientside_callback,现在正在自己的Dash仪表盘应用中实践。我的应用包含以下组件:

  • 美国地图:用户可点击地图上的州进行选择
  • 多选下拉菜单:包含「全选(ALL)」选项,也可手动选择单个州
  • 清空按钮:用于清除当前所有选择

用常规Dash回调时应用能正常运行,但为了提升性能,我想改用clientside_callback。不过我对JavaScript不熟悉,代码出现多个错误,希望能得到帮助。

原代码
import random, json
import dash
from dash import dcc, html, Dash, callback, Output, Input, State
import dash_leaflet as dl
import geopandas as gpd
from dash import dash_table


# 加载美国州界GeoJSON数据
us_states_gdf = gpd.read_file("us_states.geojson")
us_states_geojson = json.loads(us_states_gdf.to_json())

options = [{'label': 'Select all', 'value': 'ALL'}, {'label': 'AK', 'value': 'AK'}, {'label': 'AL', 'value': 'AL'},
           {'label': 'AR', 'value': 'AR'}, {'label': 'AZ', 'value': 'AZ'}, {'label': 'CA', 'value': 'CA'},
           {'label': 'CO', 'value': 'CO'}, {'label': 'CT', 'value': 'CT'}, {'label': 'DE', 'value': 'DE'},
           {'label': 'FL', 'value': 'FL'}, {'label': 'GA', 'value': 'GA'}, {'label': 'HI', 'value': 'HI'},
           {'label': 'IA', 'value': 'IA'}, {'label': 'ID', 'value': 'ID'}, {'label': 'IL', 'value': 'IL'},
           {'label': 'IN', 'value': 'IN'}, {'label': 'KS', 'value': 'KS'}, {'label': 'KY', 'value': 'KY'},
           {'label': 'LA', 'value': 'LA'}, {'label': 'MA', 'value': 'MA'}, {'label': 'MD', 'value': 'MD'},
           {'label': 'ME', 'value': 'ME'}, {'label': 'MI', 'value': 'MI'}, {'label': 'MN', 'value': 'MN'},
           {'label': 'MO', 'value': 'MO'}, {'label': 'MS', 'value': 'MS'}, {'label': 'MT', 'value': 'MT'},
           {'label': 'NC', 'value': 'NC'}, {'label': 'ND', 'value': 'ND'}, {'label': 'NE', 'value': 'NE'},
           {'label': 'NH', 'value': 'NH'}, {'label': 'NJ', 'value': 'NJ'}, {'label': 'NM', 'value': 'NM'},
           {'label': 'NV', 'value': 'NV'}, {'label': 'NY', 'value': 'NY'}, {'label': 'OH', 'value': 'OH'},
           {'label': 'OK', 'value': 'OK'}, {'label': 'OR', 'value': 'OR'}, {'label': 'PA', 'value': 'PA'},
           {'label': 'RI', 'value': 'RI'}, {'label': 'SC', 'value': 'SC'}, {'label': 'SD', 'value': 'SD'},
           {'label': 'TN', 'value': 'TN'}, {'label': 'TX', 'value': 'TX'}, {'label': 'UT', 'value': 'UT'},
           {'label': 'VA', 'value': 'VA'}, {'label': 'VT', 'value': 'VT'}, {'label': 'WA', 'value': 'WA'},
           {'label': 'WI', 'value': 'WI'}, {'label': 'WV', 'value': 'WV'}, {'label': 'WY', 'value': 'WY'}]

state_abbreviations = {'Alabama': 'AL', 'Alaska': 'AK', 'Arizona': 'AZ', 'Arkansas': 'AR', 'California': 'CA', 'Colorado': 'CO',
          'Connecticut': 'CT', 'Delaware': 'DE', 'Florida': 'FL', 'Georgia': 'GA', 'Hawaii': 'HI', 'Idaho': 'ID',
          'Illinois': 'IL', 'Indiana': 'IN', 'Iowa': 'IA', 'Kansas': 'KS', 'Kentucky': 'KY', 'Louisiana': 'LA',
          'Maine': 'ME', 'Maryland': 'MD', 'Massachusetts': 'MA', 'Michigan': 'MI', 'Minnesota': 'MN',
          'Mississippi': 'MS', 'Missouri': 'MO', 'Montana': 'MT', 'Nebraska': 'NE', 'Nevada': 'NV',
          'New Hampshire': 'NH', 'New Jersey': 'NJ', 'New Mexico': 'NM', 'New York': 'NY', 'North Carolina': 'NC',
          'North Dakota': 'ND', 'Ohio': 'OH', 'Oklahoma': 'OK', 'Oregon': 'OR', 'Pennsylvania': 'PA',
          'Rhode Island': 'RI', 'South Carolina': 'SC', 'South Dakota': 'SD', 'Tennessee': 'TN', 'Texas': 'TX',
          'Utah': 'UT', 'Vermont': 'VT', 'Virginia': 'VA', 'Washington': 'WA', 'West Virginia': 'WV', 'Wisconsin':
              'WI', 'Wyoming': 'WY'}
states =  list(state_abbreviations.values())
app = Dash(__name__)
app.layout = html.Div([
#Store lists to use in the callback
    dcc.Store(id='options-store', data=json.dumps(options)),
    dcc.Store(id='states-store', data=json.dumps(states)),
    dcc.Store(id='states-abbrevations', data=json.dumps(state_abbreviations)),
#US Map here
    dl.Map([
        dl.TileLayer(url="http://tile.stamen.com/toner-lite/{z}/{x}/{y}.png"),
        dl.GeoJSON(data=us_states_geojson, id="state-layer")],
        style={'width': '100%', 'height': '250px'},
        id="map",
        center=[39.8283, -98.5795],
    ),
#Drop down menu here
    html.Div(className='row', children=[
        dcc.Dropdown(
            id='state-dropdown',
            options=[{'label': 'Select all', 'value': 'ALL'}] +
                    [{'label': state, 'value': state} for state in states],
            value=[],
            multi=True,
            placeholder='States'
        )]),
    html.Div(className='one columns', children=[
        html.Button(
            'Clear',
            id='clear-button',
            n_clicks=0,
            className='my-button'
        ),
    ]),

])


@app.callback(
    Output('state-dropdown', 'value', allow_duplicate=True),
    [Input('clear-button', 'n_clicks')],
    prevent_initial_call=True
)
def clear_tab(user_click):
    if user_click:
        return []
    else:
        raise dash.exceptions.PreventUpdate

app.clientside_callback(
    """
    function(click_feature, selected_states, defaults_options, states, states_abbreviations) {

        let options = defaults_options
        let select_all_selected = selected_states.includes('ALL');
        let list_states;


        if (select_all_selected) {
            options = [{'label': 'Select All', 'value': 'ALL'}];
            selected_states = states;
            list_states = 'ALL';
        } else {
            list_states = selected_states;

            if (click_feature && dash.callback_context.triggered[0]['prop_id'].split('.')[0] == 'state-layer') {
                let state_name = state_abbreviations[click_feature["properties"]["NAME"]];
                if (!selected_states.includes(state_name)) {
                    selected_states.push(state_name);
                    list_states = selected_states;
                }
            }
        }

        return [options, list_states];
    }
     """,
    Output('state-dropdown', 'options'),
    Output('state-dropdown', 'value'),
    Input('state-layer', 'click_feature'),
    Input('state-dropdown', 'value'),
    State('options-store', 'data'),
    State('states-store', 'data'),
    State('states-abbrevations', 'data'),
    prevent_initial_call=True
)
if __name__ == '__main__':
    app.run_server(debug=True)
修正后代码
import random, json
import dash
from dash import dcc, html, Dash, callback, Output, Input, State
import dash_leaflet as dl
import geopandas as gpd
from dash import dash_table


# 加载美国州界GeoJSON数据
us_states_gdf = gpd.read_file("us_states.geojson")
us_states_geojson = json.loads(us_states_gdf.to_json())

options = [{'label': 'Select all', 'value': 'ALL'}, {'label': 'AK', 'value': 'AK'}, {'label': 'AL', 'value': 'AL'},
           {'label': 'AR', 'value': 'AR'}, {'label': 'AZ', 'value': 'AZ'}, {'label': 'CA', 'value': 'CA'},
           {'label': 'CO', 'value': 'CO'}, {'label': 'CT', 'value': 'CT'}, {'label': 'DE', 'value': 'DE'},
           {'label': 'FL', 'value': 'FL'}, {'label': 'GA', 'value': 'GA'}, {'label': 'HI', 'value': 'HI'},
           {'label': 'IA', 'value': 'IA'}, {'label': 'ID', 'value': 'ID'}, {'label': 'IL', 'value': 'IL'},
           {'label': 'IN', 'value': 'IN'}, {'label': 'KS', 'value': 'KS'}, {'label': 'KY', 'value': 'KY'},
           {'label': 'LA', 'value': 'LA'}, {'label': 'MA', 'value': 'MA'}, {'label': 'MD', 'value': 'MD'},
           {'label': 'ME', 'value': 'ME'}, {'label': 'MI', 'value': 'MI'}, {'label': 'MN', 'value': 'MN'},
           {'label': 'MO', 'value': 'MO'}, {'label': 'MS', 'value': 'MS'}, {'label': 'MT', 'value': 'MT'},
           {'label': 'NC', 'value': 'NC'}, {'label': 'ND', 'value': 'ND'}, {'label': 'NE', 'value': 'NE'},
           {'label': 'NH', 'value': 'NH'}, {'label': 'NJ', 'value': 'NJ'}, {'label': 'NM', 'value': 'NM'},
           {'label': 'NV', 'value': 'NV'}, {'label': 'NY', 'value': 'NY'}, {'label': 'OH', 'value': 'OH'},
           {'label': 'OK', 'value': 'OK'}, {'label': 'OR', 'value': 'OR'}, {'label': 'PA', 'value': 'PA'},
           {'label': 'RI', 'value': 'RI'}, {'label': 'SC', 'value': 'SC'}, {'label': 'SD', 'value': 'SD'},
           {'label': 'TN', 'value': 'TN'}, {'label': 'TX', 'value': 'TX'}, {'label': 'UT', 'value': 'UT'},
           {'label': 'VA', 'value': 'VA'}, {'label': 'VT', 'value': 'VT'}, {'label': 'WA', 'value': 'WA'},
           {'label': 'WI', 'value': 'WI'}, {'label': 'WV', 'value': 'WV'}, {'label': 'WY', 'value': 'WY'}]

state_abbreviations = {'Alabama': 'AL', 'Alaska': 'AK', 'Arizona': 'AZ', 'Arkansas': 'AR', 'California': 'CA', 'Colorado': 'CO',
          'Connecticut': 'CT', 'Delaware': 'DE', 'Florida': 'FL', 'Georgia': 'GA', 'Hawaii': 'HI', 'Idaho': 'ID',
          'Illinois': 'IL', 'Indiana': 'IN', 'Iowa': 'IA', 'Kansas': 'KS', 'Kentucky': 'KY', 'Louisiana': 'LA',
          'Maine': 'ME', 'Maryland': 'MD', 'Massachusetts': 'MA', 'Michigan': 'MI', 'Minnesota': 'MN',
          'Mississippi': 'MS', 'Missouri': 'MO', 'Montana': 'MT', 'Nebraska': 'NE', 'Nevada': 'NV',
          'New Hampshire': 'NH', 'New Jersey': 'NJ', 'New Mexico': 'NM', 'New York': 'NY', 'North Carolina': 'NC',
          'North Dakota': 'ND', 'Ohio': 'OH', 'Oklahoma': 'OK', 'Oregon': 'OR', 'Pennsylvania': 'PA',
          'Rhode Island': 'RI', 'South Carolina': 'SC', 'South Dakota': 'SD', 'Tennessee': 'TN', 'Texas': 'TX',
          'Utah': 'UT', 'Vermont': 'VT', 'Virginia': 'VA', 'Washington': 'WA', 'West Virginia': 'WV', 'Wisconsin':
              'WI', 'Wyoming': 'WY'}
states =  list(state_abbreviations.values())
app = Dash(__name__)
app.layout = html.Div([
#Store lists to use in the callback
    dcc.Store(id='options-store', data=json.dumps(options)),
    dcc.Store(id='states-store', data=json.dumps(states)),
    dcc.Store(id='states-abbreviations', data=json.dumps(state_abbreviations)),  # 修正拼写错误
#US Map here
    dl.Map([
        dl.TileLayer(url="http://tile.stamen.com/toner-lite/{z}/{x}/{y}.png"),
        dl.GeoJSON(data=us_states_geojson, id="state-layer")],
        style={'width': '100%', 'height': '250px'},
        id="map",
        center=[39.8283, -98.5795],
    ),
#Drop down menu here
    html.Div(className='row', children=[
        dcc.Dropdown(
            id='state-dropdown',
            options=[{'label': 'Select all', 'value': 'ALL'}] +
                    [{'label': state, 'value': state} for state in states],
            value=[],
            multi=True,
            placeholder='States'
        )]),
    html.Div(className='one columns', children=[
        html.Button(
            'Clear',
            id='clear-button',
            n_clicks=0,
            className='my-button'
        ),
    ]),

])


@app.callback(
    Output('state-dropdown', 'value', allow_duplicate=True),
    [Input('clear-button', 'n_clicks')],
    prevent_initial_call=True
)
def clear_tab(user_click):
    if user_click:
        return []
    else:
        raise dash.exceptions.PreventUpdate

app.clientside_callback(
    """
    function(click_feature, selected_states, defaults_options, states, states_abbreviations) {
        // 解析dcc.Store传来的JSON字符串
        const parsedOptions = JSON.parse(defaults_options);
        const parsedStates = JSON.parse(states);
        const parsedAbbr = JSON.parse(states_abbreviations);
        
        let currentOptions = [...parsedOptions];
        let currentValues = [...(selected_states || [])];
        const selectAllSelected = currentValues.includes('ALL');

        // 获取触发源ID
        const triggerId = dash.callback_context.triggered?.[0]?.prop_id.split('.')[0];

        if (selectAllSelected) {
            // 选中ALL时,仅保留ALL选项,值设为['ALL']
            currentOptions = [{label: 'Select all', value: 'ALL'}];
            currentValues = ['ALL'];
        } else {
            // 处理地图点击事件
            if (triggerId === 'state-layer' && click_feature) {
                const stateName = click_feature.properties.NAME;
                const stateAbbr = parsedAbbr[stateName];
                
                if (currentValues.includes(stateAbbr)) {
                    // 已选中则移除
                    currentValues = currentValues.filter(abbr => abbr !== stateAbbr);
                } else {
                    // 未选中则添加
                    currentValues.push(stateAbbr);
                }
            }
            // 重置为完整选项列表
            currentOptions = parsedOptions;
        }

        return [currentOptions, currentValues];
    }
     """,
    Output('state-dropdown', 'options'),
    Output('state-dropdown', 'value'),
    Input('state-layer', 'click_feature'),
    Input('state-dropdown', 'value'),
    State('options-store', 'data'),
    State('states-store', 'data'),
    State('states-abbreviations', 'data'),
    prevent_initial_call=False
)
if __name__ == '__main__':
    app.run_server(debug=True)
关键修正点
  1. 拼写错误修复:将states-abbrevations改为states-abbreviations,保持变量名一致
  2. JSON数据解析:从dcc.Store读取的是JSON字符串,必须用JSON.parse转换为JS对象/数组才能操作
  3. 触发源判断优化:使用可选链操作符(?.)避免空值报错,准确识别回调触发源
  4. **数组操作
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 00:07:27