在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)
关键修正点
- 拼写错误修复:将
states-abbrevations改为states-abbreviations,保持变量名一致 - JSON数据解析:从dcc.Store读取的是JSON字符串,必须用
JSON.parse转换为JS对象/数组才能操作 - 触发源判断优化:使用可选链操作符(
?.)避免空值报错,准确识别回调触发源 - **数组操作
相关产品推荐
相关产品推荐

