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

如何在Dash应用中结合地图框选/套索选与点击更新DataTable

问题

现有一个Dash应用,目前用户点击地图上的点位可更新Dash DataTable。希望同时使用地图的box select和lasso select工具来更新表格,且保留原有点击功能。尝试添加包含selectedData的新回调后,所有原有回调均失效,相关代码如下:

import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objs as go
import dash_bootstrap_components as dbc
import pandas as pd
from dash import dash_table

pd.options.mode.chained_assignment = None  # default='warn'

df = pd.read_csv('Data1.csv')

app = dash.Dash(external_stylesheets=[dbc.themes.SLATE])
app.title = "Project 1"
server = app.server

color = {'Mixed': '#e48900',
         'Arable': '#5aa469',
         'Livestock': '#8ab6d6'}

def update_bar(df):
    fig = go.Figure()
    for type in df['Type'].unique():
        df_by_Type = df[df['Type']==type]
        df_by_Type.sort_values('Capacity',inplace=True)
        fig.add_trace(go.Bar({
                              'x':df_by_Type['Capacity'],
                              'y':df_by_Type['Name'],
                              'orientation':'h',
                              'name':type,
                              'marker':{'color':color[type]},
        }))

    fig.update_layout(barmode='stack',height=900)
    fig.update_yaxes(categoryorder='category descending')
    fig.update_xaxes(title_text='Hectares')
    return fig

def update_map(df):
    traces = []
    for type in df['Type'].unique():
        traces.append(go.Scattermapbox(
        lat=df['Latitude'],
        lon=df['Longitude'],
        mode='markers',
        customdata=df.loc[:, ['Type']],
        hovertemplate="<b>%{text}</b><br><br>" +"Type: %{customdata[0]}<br>" +""+ "<extra></extra>",
        showlegend=False,
        marker=go.scattermapbox.Marker(
                size=20,
                color=df['Code']
                ),
        text=df ['Name'],
        name = type
        ))

    return {'data': traces,
             'layout': go.Layout(hovermode='closest',
              height= 900,
              mapbox=dict(
                  accesstoken='MAPBOX_ACCESS_TOKEN',
                  bearing=0,
             center=go.layout.mapbox.Center(
                        lat=56.0,
                        lon=-4.0
                ),
            pitch=0,
            zoom=5
        ))}

app.layout = html.Div([
    dbc.Card(
        dbc.CardBody([
            dbc.Row([
                dbc.Col([
                    html.H1(['Project 1'],style={'text-align':'center'})
                ], width=15),
            ], align='center'),
            html.Br(),
            dbc.Row([
                dbc.Col([
                    html.Label(['Select Region'],style={'font-weight':'bold'},),
                     dcc.Dropdown(id='region-dropdown',
                                  options=[{'label':i,'value':i} for i in df['Region'].unique()],
                                  multi= True
                     )
                ], width=3),
                dbc.Col([
                   html.Div(
                            [
                            dbc.Button('Submit', id='submit-button', color='primary', className='mr-1')
                            ],style={'margin-top':'20px'}
                    )
                ], width=3),
            ], align='center'), 
            html.Br(),
            dbc.Row([
                dbc.Col([
                     html.Div([
                  dcc.Graph(id = 'graph',figure=update_map(df))
                  ])
                ], width=7),
                dbc.Col([
                    html.Div([
                  dcc.Graph(id = 'graph-1',figure=update_bar(df))
                  ])
                ], width=5),
            ], align='center'), 
            html.Br(),
            dbc.Row([
                dbc.Col([dash_table.DataTable(id='table',columns=[{'name': col, 'id': col} for col in df.columns],
                                               style_cell_conditional=[{'if': {'column_id': 'Code',} ,  'display': 'None',}],
        style_table={'overflowX': 'scroll'},
        style_cell={
            'minWidth': '100px', 'width': '100px', 'maxWidth': '90px',
            'height': 'auto',
            'overflow': 'hidden',
            'textOverflow': 'ellipsis','textAlign': 'left',
            'fontSize':20, 'font-family':'sans-serif'
        })
                ], width=8),
              
            ], align='center'),      
        ])
    ) 
])

@app.callback([Output('graph','figure'),
              Output('graph-1','figure')],
              [Input('submit-button','n_clicks')],
              [State('region-dropdown','value')],
             )
def update_graph(n_clicks,region): 
    frames = []
    for region_name in region:
            frames.append(df[(df['Region']==region_name)])
    df_filtered = pd.concat(frames,ignore_index=True)
    return [update_map(df_filtered), update_bar(df_filtered)]

@app.callback(
    Output('table', 'data'),
    Input('graph', 'clickData'),
    State('table', 'data'),
            )   
def update_table(selected_point, current_data):
    if selected_point is None:
        return current_data
    else:
        lat, lon = selected_point['points'][0]['lat'], selected_point['points'][0]['lon']
        selected_row = df[(df['Latitude'] == lat) & (df['Longitude'] == lon)]
        if selected_row.empty:
            return current_data
        else:
            return selected_row.to_dict('records')

if __name__ == '__main__':
    app.run_server()
解决方案

要实现点击、框选、套索选同步更新表格的功能,需对代码做以下修改:

  • 启用地图的多选交互
    在update_map函数的layout中添加交互配置,开启框选和套索选支持:
def update_map(df):
    # ... 保留原有trace生成逻辑
    return {'data': traces,
             'layout': go.Layout(
                 hovermode='closest',
                 height=900,
                 mapbox=dict(...),  # 保留原有mapbox配置
                 dragmode='select',  # 默认启用框选,用户可切换为套索选
                 selectmode='multiple'  # 允许多选点位
             )}
  • 合并回调逻辑,同时监听多种交互事件
    删除原有表格更新回调,替换为同时监听clickData、selectedData和区域筛选提交事件的回调,确保所有交互都能触发表格更新:
@app.callback(
    Output('table', 'data'),
    [Input('graph', 'clickData'),
     Input('graph', 'selectedData'),
     Input('submit-button', 'n_clicks')],
    [State('region-dropdown', 'value')]
)   
def update_table(click_data, selected_data, n_clicks, selected_regions):
    # 先处理区域筛选后的数据源
    df_filtered = df[df['Region'].isin(selected_regions)] if selected_regions else df.copy()

    # 优先处理点击事件(点击会自动清空选中状态)
    if click_data:
        lat, lon = click_data['points'][0]['lat'], click_data['points'][0]['lon']
        selected_rows = df_filtered[(df_filtered['Latitude'] == lat) & (df_filtered['Longitude'] == lon)]
        return selected_rows.to_dict('records')
    # 处理框选/套索选事件
    elif selected_data and selected_data['points']:
        selected_coords = [(p['lat'], p['lon']) for p in selected_data['points']]
        selected_rows = df_filtered[
            df_filtered.apply(lambda row: (row['Latitude'], row['Longitude']) in selected_coords, axis=1)
        ]
        return selected_rows.to_dict('records')
    # 初始状态或无选中时返回空表格
    else:
        return []
  • 修复地图轨迹的数据源错误
    原update_map函数中,每个Type的trace都使用了整个数据源,需按Type筛选后再生成轨迹:
def update_map(df):
    traces = []
    for type in df['Type'].unique():
        df_type = df[df['Type'] == type]  # 按Type筛选数据
        traces.append(go.Scattermapbox(
            lat=df_type['Latitude'],
            lon=df_type['Longitude'],
            mode='markers',
            customdata=df_type.loc[:, ['Type']],
            hovertemplate="<b>%{text}</b><br><br>Type: %{customdata[0]}<br><extra></extra>",
            showlegend=False,
            marker=go.scattermapbox.Marker(size=20, color=df_type['Code']),
            text=df_type['Name'],
            name=type
        ))
    # ... 保留原有layout配置
  • 优化区域筛选的空值处理
    原update_graph函数在未选择区域时会报错,添加空值判断:
def update_graph(n_clicks, region): 
    if not region:
        df_filtered = df.copy()
    else:
        frames = [df[(df['Region']==region_name)] for region_name in region]
        df_filtered = pd.concat(frames,ignore_index=True)
    return [update_map(df_filtered), update_bar(df_filtered)]

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 15:39:57