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

Dash Plotly多回调函数组合实现数据过滤问题求助

Dash Plotly 合并多回调实现联合数据过滤可视化

问题背景
现有两个独立的Dash回调功能:

  • 绑定area-dropdown下拉框,通过空间匹配返回指定区域(全球/非洲/欧洲)的点数据子集,更新散点地图和柱状图
  • 绑定Code、Cat复选框及maps单选按钮,过滤指定字段值后更新散点地图(支持散点/六边形两种模式)和柱状图

两类回调单独运行均正常,但无法同时生效,需要将功能合并实现区域+字段的联合过滤。

解决方案
核心思路是将所有输入控件作为统一回调的输入源,先执行区域过滤,再基于区域结果执行字段过滤,最后同步生成两个图表。这样避免了多个回调对同一输出组件的冲突,确保所有过滤条件同时生效。

完整实现代码如下:

import geopandas as gpd
import plotly.express as px
import dash
from dash import dcc, html, Input, Output
import dash_bootstrap_components as dbc
import plotly.graph_objs as go
import plotly.figure_factory as ff
from itertools import cycle

# 加载并预处理数据
gdf_all = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))

# 生成Cat字段
i = iter(['A', 'B', 'C', 'D'])
gdf_all['Cat'] = gdf_all.index.map(dict(zip(gdf_all.index, cycle(i))))

# 生成Code字段
j = iter(['10-20', '20-30', '30-40', '40-50', '60-70'])
gdf_all['Code'] = gdf_all.index.map(dict(zip(gdf_all.index, cycle(j))))

# 加载多边形数据并处理
gdf_poly = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
gdf_poly = gdf_poly.drop('name', axis=1)

gdf_all['LON'] = gdf_all['geometry'].x
gdf_all['LAT'] = gdf_all['geometry'].y

# 预生成区域子集
def merge_withinboundary(gdf1, gdf2):
    return gpd.sjoin(gdf1, gdf2, predicate='within', how='inner').reset_index(drop=True)

Afr_gdf_area = gdf_poly[gdf_poly['continent'] == 'Africa'].reset_index(drop=True)
Eur_gdf_area = gdf_poly[gdf_poly['continent'] == 'Europe'].reset_index(drop=True)
gdf_Africa = merge_withinboundary(gdf_all, Afr_gdf_area)
gdf_Europe = merge_withinboundary(gdf_all, Eur_gdf_area)

# 初始化Dash应用
external_stylesheets = [dbc.themes.SPACELAB, dbc.icons.BOOTSTRAP]
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

# 导航栏布局
nav_bar = html.Div([
    html.P("area-dropdown:"),
    dcc.Dropdown(
        id='data', 
        value='gdf_all', 
        options=[
            {'value': 'gdf_all', 'label': 'gdf_all'},
            {'value': 'gdf_Africa', 'label': 'gdf_Africa'},
            {'value': 'gdf_Europe', 'label': 'gdf_Europe'}
        ],
        clearable=False
    ),
    html.Label('Code', style={'paddingTop': '1rem'}),
    dcc.Checklist(
        id='Code',
        options=[
            {'label': '10-20', 'value': '10-20'},
            {'label': '20-30', 'value': '20-30'},
            {'label': '30-40', 'value': '30-40'},
            {'label': '40-50', 'value': '40-50'},
            {'label': '60-70', 'value': '60-70'},                        
        ],
        value=['10-20', '20-30', '30-40', '40-50', '60-70'],
        style={'display': 'inline', 'margin-right': '50px'}
    ),
    html.Label('Cat', style={'paddingTop': '1rem'}),
    dcc.Checklist(
        id='Cat',
        options=[
            {'label': 'A', 'value': 'A'},
            {'label': 'B', 'value': 'B'},
            {'label': 'C', 'value': 'C'},
            {'label': 'D', 'value': 'D'},                     
        ],
        value=['A', 'B', 'C', 'D'],
        style={'display': 'inline', 'margin-right': '50px'}
    ),
    html.Label('Spatial Map', style={'paddingTop': '1rem'}),
    dcc.RadioItems(
        ['Scatter','Hexbin'],
        'Scatter', 
        id='maps', 
        style={'display': 'inline', 'margin-right': '50px'}
    ),
], className="vstack gap-2 h-50")

# 整体布局
app.layout = dbc.Container([
    dbc.Row([
        dbc.Col(html.Div(nav_bar), className='bg-light', width=2),
        dbc.Col([
            dbc.Row([dbc.Col(dcc.Graph(id='spatial-chart'))]),
            dbc.Row([dbc.Col(dcc.Graph(id='bar-chart'))]),
        ], width=5),
        dbc.Col([], width=5),
    ])
], fluid=True)

# 合并后的核心回调
@app.callback(
    [Output('spatial-chart', 'figure'),
     Output('bar-chart', 'figure')],
    [Input('data', 'value'),
     Input('Cat', 'value'),
     Input('Code', 'value'),
     Input('maps', 'value')]
)
def update_all_charts(selected_area, selected_cats, selected_codes, map_type):
    # 第一步:区域过滤
    if selected_area == 'gdf_Africa':
        filtered_df = gdf_Africa
        zoom_level = 2
    elif selected_area == 'gdf_Europe':
        filtered_df = gdf_Europe
        zoom_level = 2
    else:
        filtered_df = gdf_all
        zoom_level = 0
    
    # 第二步:字段过滤
    filtered_df = filtered_df[filtered_df['Cat'].isin(selected_cats)]
    filtered_df = filtered_df[filtered_df['Code'].isin(selected_codes)]
    
    # 生成空间图表
    if map_type == 'Scatter':
        spatial_fig = px.scatter_mapbox(
            data_frame=filtered_df,
            lat='LAT',
            lon='LON',
            color='Cat',
            opacity=0.5,
            zoom=zoom_level,
            mapbox_style='carto-positron',
            hover_name='Cat'
        )
    else:
        spatial_fig = ff.create_hexbin_mapbox(
            data_frame=filtered_df,
            lat='LAT',
            lon='LON',
            nx_hexagon=100,
            min_count=1,
            mapbox_style='carto-positron',
            zoom=zoom_level
        )
    
    # 生成柱状图
    cat_counts = filtered_df['Cat'].value_counts().sort_index()
    bar_fig = px.bar(
        x=cat_counts.index,
        y=cat_counts.values,
        color=cat_counts.index,
        labels={'x': 'Category', 'y': 'Count'}
    )
    
    return spatial_fig, bar_fig

if __name__ == '__main__':
    app.run_server(debug=True, port=8051)

关键改进点:

  • 合并所有输入控件到单个回调,避免多回调对同一输出的冲突
  • 统一数据过滤逻辑:先区域过滤,再字段过滤,确保所有条件同时生效
  • 优化图表生成逻辑,复用过滤后的数据集,提升效率
  • 移除冗余代码(如重复导入、全局变量df)

内容的提问来源于stack exchange,提问作者Chopin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:44:56