Plotly地图分支与客户点异色显示问题求助
问题描述
我需要在地图上可视化分支机构及其指定半径范围内的客户,要求分支点与客户点使用不同颜色。已为分支设置下拉菜单,选中后地图会居中并缩放至该分支,但目前无法实现分支与客户点异色显示,相关代码如下:
import plotly.express as px import pandas as pd import geopandas as gpd from shapely.geometry import Polygon from geopy.distance import geodesic # Sample data branches = pd.DataFrame({ 'name': ['Branch A', 'Branch B'], 'lat': [39.647868, 41.06212], 'lon': [27.882012, 28.98888] }) customers = pd.DataFrame({ 'name': ['Customer 1', 'Customer 2', 'Customer 3'], 'lat': [39.65113, 41.01879, 39.606046], 'lon': [27.89559, 29.01249, 27.784534] }) # Create a GeoDataFrame branches_gdf = gpd.GeoDataFrame(branches, geometry=gpd.points_from_xy(branches.lon, branches.lat)) customers_gdf = gpd.GeoDataFrame(customers, geometry=gpd.points_from_xy(customers.lon, customers.lat)) # Function to create a circle def create_circle(center, radius_km): angle_step = 10 # degrees angles = range(0, 360, angle_step) circle_points = [ geodesic(kilometers=radius_km).destination((center.y, center.x), angle) for angle in angles ] return Polygon([(point.longitude, point.latitude) for point in circle_points]) # Define a perimeter in kilometers perimeter_radius_km = 5 # Plot initial setup fig = px.scatter_mapbox( lat=customers_gdf.geometry.y, lon=customers_gdf.geometry.x, hover_name=customers_gdf['name'], zoom=4, mapbox_style='carto-positron' ) # Add all branch locations fig.add_scattermapbox( lat=branches_gdf.geometry.y, lon=branches_gdf.geometry.x, mode='markers', marker=dict(size=12, color='red'), name='Branches', hovertext=branches_gdf['name'] ) # Add dropdown for selecting branches buttons = [] for branch in branches_gdf.itertuples(): branch_location = branch.geometry perimeter = create_circle(branch_location, perimeter_radius_km) customers_within = customers_gdf[customers_gdf.within(perimeter)] buttons.append(dict( args=[{ 'lat': [[branch_location.y] + customers_within.geometry.y.tolist(), [point[1] for point in perimeter.exterior.coords]], 'lon': [[branch_location.x] + customers_within.geometry.x.tolist(), [point[0] for point in perimeter.exterior.coords]], 'mode': ['markers', 'lines'], 'marker': [dict(size=12, color='red')] + [dict(size=8, color='blue')] * len(customers_within) + [dict(size=0, color='blue')], 'hovertext': [[branch.name] + customers_within['name'].tolist(), ['Perimeter']], 'name': ['Branch and Customers', 'Perimeter'] }], label=branch.name, method='update' )) buttons.append(dict( args=[{ 'lat': [customers_within.geometry.y.tolist() + [branch_location.y]], 'lon': [customers_within.geometry.x.tolist() + [branch_location.x]], 'marker': dict(size=8, color='blue'), # Customer color 'mapbox.center': {'lat': branch_location.y, 'lon': branch_location.x}, 'mapbox.zoom': 12 }], label=branch.name, method='update' )) # Update the map center and zoom buttons[-1]['args'].append({'mapbox.center': {'lat': branch_location.y, 'lon': branch_location.x}, 'mapbox.zoom': 12}) fig.update_layout( updatemenus=[{ 'buttons': buttons, 'direction': 'down', 'showactive': True }] ) # Save the interactive map to an HTML file fig.write_html("interactive_map.html")
问题分析与修正方案
原代码的核心问题是下拉按钮的更新逻辑错误:
- 单个Trace无法为不同点设置不同颜色,必须将分支和客户拆分为独立Trace
- 第二个按钮错误地将所有点统一设置为蓝色,覆盖了分支的红色样式
- 更新参数结构混乱,导致图层属性冲突
以下是修正后的代码,确保分支点(红色)、客户点(蓝色)、周长(灰色线条)清晰区分,且下拉选中后正确居中缩放:
import plotly.express as px import pandas as pd import geopandas as gpd from shapely.geometry import Polygon from geopy.distance import geodesic # 样本数据 branches = pd.DataFrame({ 'name': ['Branch A', 'Branch B'], 'lat': [39.647868, 41.06212], 'lon': [27.882012, 28.98888] }) customers = pd.DataFrame({ 'name': ['Customer 1', 'Customer 2', 'Customer 3'], 'lat': [39.65113, 41.01879, 39.606046], 'lon': [27.89559, 29.01249, 27.784534] }) # 创建GeoDataFrame branches_gdf = gpd.GeoDataFrame(branches, geometry=gpd.points_from_xy(branches.lon, branches.lat)) customers_gdf = gpd.GeoDataFrame(customers, geometry=gpd.points_from_xy(customers.lon, customers.lat)) # 创建圆形范围的函数 def create_circle(center, radius_km): angle_step = 10 # 角度步长 angles = range(0, 360, angle_step) circle_points = [ geodesic(kilometers=radius_km).destination((center.y, center.x), angle) for angle in angles ] return Polygon([(point.longitude, point.latitude) for point in circle_points]) # 定义半径(公里) perimeter_radius_km = 5 # 初始地图设置:显示所有客户和分支 fig = px.scatter_mapbox( lat=customers_gdf.geometry.y, lon=customers_gdf.geometry.x, hover_name=customers_gdf['name'], zoom=4, mapbox_style='carto-positron', color_discrete_sequence=['blue'], marker={'size':8}, title='All Customers & Branches' ) # 添加所有分支点 fig.add_scattermapbox( lat=branches_gdf.geometry.y, lon=branches_gdf.geometry.x, mode='markers', marker=dict(size=12, color='red'), name='Branches', hovertext=branches_gdf['name'] ) # 构建下拉按钮 buttons = [] # 添加"显示全部"按钮 buttons.append(dict( args=[{ 'data': [ # 客户点 { 'lat': customers_gdf.geometry.y.tolist(), 'lon': customers_gdf.geometry.x.tolist(), 'mode': 'markers', 'marker': dict(size=8, color='blue'), 'hovertext': customers_gdf['name'].tolist(), 'name': 'Customers' }, # 分支点 { 'lat': branches_gdf.geometry.y.tolist(), 'lon': branches_gdf.geometry.x.tolist(), 'mode': 'markers', 'marker': dict(size=12, color='red'), 'hovertext': branches_gdf['name'].tolist(), 'name': 'Branches' } ], 'layout': { 'mapbox.center': {'lat': (branches.lat.mean()), 'lon': (branches.lon.mean())}, 'mapbox.zoom': 4, 'title': 'All Customers & Branches' } }], label='Show All', method='update' )) # 为每个分支添加按钮 for branch in branches_gdf.itertuples(): branch_location = branch.geometry perimeter = create_circle(branch_location, perimeter_radius_km) customers_within = customers_gdf[customers_gdf.within(perimeter)] # 提取周长的坐标 perimeter_lats = [point[1] for point in perimeter.exterior.coords] perimeter_lons = [point[0] for point in perimeter.exterior.coords] buttons.append(dict( args=[{ 'data': [ # 当前分支点 { 'lat': [branch_location.y], 'lon': [branch_location.x], 'mode': 'markers', 'marker': dict(size=12, color='red'), 'hovertext': [branch.name], 'name': 'Selected Branch' }, # 范围内的客户点 { 'lat': customers_within.geometry.y.tolist(), 'lon': customers_within.geometry.x.tolist(), 'mode': 'markers', 'marker': dict(size=8, color='blue'), 'hovertext': customers_within['name'].tolist(), 'name': 'Customers in Range' }, # 周长线条 { 'lat': perimeter_lats, 'lon': perimeter_lons, 'mode': 'lines', 'line': dict(color='gray', width=2), 'hovertext': ['Perimeter (5km)'], 'name': 'Perimeter' } ], 'layout': { 'mapbox.center': {'lat': branch_location.y, 'lon': branch_location.x}, 'mapbox.zoom': 12, 'title': f'{branch.name} & Customers in 5km Range' } }], label=branch.name, method='update' )) # 更新布局添加下拉菜单 fig.update_layout( updatemenus=[{ 'buttons': buttons, 'direction': 'down', 'showactive': True, 'x': 0.1, 'y': 1.1 }], margin={'r':0, 't':50, 'l':0, 'b':0} ) # 保存为HTML文件 fig.write_html("interactive_map.html")
核心修改点
- 将分支、客户、周长拆分为独立的Trace,每个Trace单独设置颜色和样式
- 统一下拉按钮的更新逻辑,通过
data参数完整替换所有图层,避免冲突 - 添加"显示全部"按钮,方便返回初始视图
- 明确设置每个Trace的
name属性,图例显示更清晰 - 优化地图标题,当前视图信息一目了然
内容的提问来源于stack exchange,提问作者aearslan
相关产品推荐
相关产品推荐

