如何在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
相关产品推荐
相关产品推荐

