我希望包括一个带有回调函数的下拉栏,允许用户在较小的区域内显示特定的点。最初,我想默认使用所有点几何数据。然后我的目标是包括一个下拉栏和回调函数,从这个主 df 返回较小的子集。这是通过合并特定多边形区域内的点数据来实现的。
在下面使用,默认 df 被标记为
gdf_all
。这包含一个大区域的点数据。较小的多边形文件是 gdf_poly
的子集。其中包括非洲和欧洲大陆。这些在函数中使用,仅在点数据在多边形形状内相交时才返回点数据。
我已经对下面的输出进行了硬编码。 1) 使用
gdf_all
和 2) 使用非洲大陆的一个子集。
理想情况下,下拉栏将用于输入要在图中可视化的所需点数据。
import geopandas as gpd
import plotly.express as px
import dash
from dash import dcc
from dash import html
import dash_bootstrap_components as dbc
# point data
gdf_all = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))
# polygon data
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
# subset African continent
Afr_gdf_area = gdf_poly[gdf_poly['continent'] == 'Africa'].reset_index(drop = True)
# subset European continent
Eur_gdf_area = gdf_poly[gdf_poly['continent'] == 'Europe'].reset_index(drop = True)
# function to merge point data within selected polygon area
def merge_withinboundary(gdf1, gdf2):
# spatial join data within larger boundary
gdf_out = gpd.sjoin(gdf1, gdf2, predicate = 'within', how = 'left').reset_index(drop = True)
return gdf_out
gdf_Africa = merge_withinboundary(gdf_all, Afr_gdf_area)
gdf_Europe = merge_withinboundary(gdf_all, Eur_gdf_area)
external_stylesheets = [dbc.themes.SPACELAB, dbc.icons.BOOTSTRAP]
app = dash.Dash(__name__, external_stylesheets = external_stylesheets)
# function to return selected df for plotting
def update_dataset(df):
if df == 'gdf_Africa':
gdf = gdf_Africa
elif df == 'gdf_Europe':
gdf = gdf_Europe
else:
gdf = gdf_all
return gdf
nav_bar = html.Div([
html.P("area-dropdown:"),
dcc.Dropdown(
id='data',
value='data',
options=[{'value': 'gdf_all', 'label': 'gdf_all'},
{'value': 'gdf_Africa', 'label': 'gdf_Africa'},
{'value': 'gdf_Europe', 'label': 'gdf_Europe'}
],
clearable=False
),
])
# output 1
df = gdf_all
# output 2
#df = gdf_Africa
scatter = px.scatter_mapbox(data_frame = df,
lat = 'LAT',
lon = 'LON',
zoom = 2,
mapbox_style = 'carto-positron',
)
count = df['name'].value_counts()
bar = px.bar(x = count.index,
y = count.values,
color = count.index,
)
app.layout = dbc.Container([
dbc.Row([
dbc.Col(html.Div(nav_bar), width=2),
dbc.Col([
dbc.Row([
dbc.Col(dcc.Graph(figure = scatter))
]),
dbc.Row([
dbc.Col(dcc.Graph(figure = bar))
]),
], width=5),
dbc.Col([
], width=5),
])
], fluid=True)
if __name__ == '__main__':
app.run_server(debug=True, port = 8051)
输出1:
输出2:
我有一个部分可行的解决方案,但需要进行大量修改。当您使用
px.choropleth
创建等值线图时,geojson 参数必须是 (GeoJSON-formatted dict) – Must contain a Polygon feature collection, with IDs, which are references from locations
(来自 documentation)——传递 geojson = A_gdf_area
将不起作用,因为 A_gdf_area
是 GeoDataFrame
.
此外,您的 geojson 文件需要包含位置和颜色的列,以便在等值线图上显示颜色——我不知道您打算使用哪些列来为地图着色,所以我制作了虚拟列称为
ids
和vals
提供位置和颜色。
由于您在意大利绘制区域(并且 px.choropleth 仅绘制世界各国或美国各州的边界),您需要首先使用从geojson,然后将 choropleth 数据作为跟踪添加到该图(这个想法归功于
this answeron the plotly forum)。我已将其集成到下面的 dash 应用程序中。唯一的问题是与
go.Scattergeo
列对应的颜色似乎没有像我期望的那样在图中呈现 - 当我弄清楚原因时我会更新这个答案。
lats
我认为主要任务是将lons
转换为一个回调函数,该回调函数将下拉选择作为输入,并输出新更新的散点图框和条形图。为此,您需要在
vals
import numpy as np
import pandas as pd
import geopandas as gpd
import json
import plotly.express as px
import plotly.graph_objs as go
import dash
from dash import dcc
from dash import html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
gdf_area = gpd.read_file('https://raw.githubusercontent.com/openpolis/geojson-italy/master/geojson/limits_IT_municipalities.geojson')
np.random.seed(42)
gdf_area['ids'] = np.random.choice([1,2,3], size=len(gdf_area))
gdf_area['vals'] = np.random.uniform(1, 10, size=len(gdf_area))
A_gdf_area = gdf_area[gdf_area['name'].str.startswith('A', na=False)][['name','geometry','ids','vals']]
B_gdf_area = gdf_area[gdf_area['name'].str.startswith('B', na=False)][['name','geometry','ids','vals']]
C_gdf_area = gdf_area[gdf_area['name'].str.startswith('C', na=False)][['name','geometry','ids','vals']]
dropdown_dict = {
'A_gdf_area': A_gdf_area,
'B_gdf_area': B_gdf_area,
'C_gdf_area': C_gdf_area
}
def state_boundaries(geojdata):
pts = []#list of points defining boundaries of polygons, pts has as coordinates the lon and lat
for feature in geojdata['features']:
if feature['geometry']['type'] == 'Polygon':
pts.extend(feature['geometry']['coordinates'][0])
pts.append([None, None])#mark the end of a polygon
elif feature['geometry']['type'] == 'MultiPolygon':
for polyg in feature['geometry']['coordinates']:
pts.extend(polyg[0])
pts.append([None, None])#end of polygon
elif feature['geometry']['type'] == 'LineString':
pts.extend(feature['geometry']['coordinates'])
pts.append([None, None])
else: pass
#else: raise ValueError("geometry type irrelevant for map")
lons, lats = zip(*pts)
return lons, lats
def create_choropleth_figure(gdf_area):
geojson_string = gdf_area.to_json()
geojson = json.loads(geojson_string)
lons, lats = state_boundaries(geojson)
fig = go.Figure(go.Scattergeo(lon=lons, lat=lats, mode="lines", line_width=1, line_color="black"))
fig.update_layout(width=700, height=750,
geo = dict(
scope = 'europe',
resolution = 110, #for info help(go.layout.Geo.resolution)
lataxis_range = [33, 48],
lonaxis_range = [5, 20],
landcolor = 'rgb(225, 225, 225)',
))
reduced_geojson={"type": "FeatureCollection", "features":[]}
for feat in geojson["features"]:
reduced_geojson["features"].append(feat)
figc = px.choropleth(gdf_area, geojson=reduced_geojson, locations='ids',
color="vals", color_continuous_scale="viridis")
fig.add_trace(figc.data[0])
return fig
def merge_withinboundary(gdf1, gdf2):
# spatial join data within larger boundary
gdf_out = gpd.sjoin(gdf1, gdf2, predicate = 'within', how = 'left').reset_index(drop = True)
gdf_out['ids'] = gdf_out['ids_left']
gdf_out['vals'] = gdf_out['vals_left']
gdf_out.drop(columns=['ids_left','ids_right','vals_left','vals_right'], inplace=True)
return gdf_out
gdf_A = merge_withinboundary(A_gdf_area, gdf_area)
fig = create_choropleth_figure(gdf_A)
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='gdf',
value='Site',
options=[{'value': x, 'label': x}
for x in ['A_gdf_area', 'B_gdf_area', 'C_gdf_area']],
clearable=False
),
])
app.layout = dbc.Container([
dbc.Row([
dbc.Col(html.Div(nav_bar), width=2),
dbc.Col([
dbc.Row([
dbc.Col(dcc.Graph(id = 'choropleth-fig', figure = fig))
]),
], width=5),
dbc.Col([
], width=5),
])
], fluid=True)
@app.callback(Output('choropleth-fig', 'figure'),
Input('gdf', 'value'),
prevent_initial_call=True)
def update_choropleth(dropdown_value):
if dropdown_value is None:
return dash.no_update
selected_gdf_area = dropdown_dict[dropdown_value]
new_gdf_area = merge_withinboundary(selected_gdf_area, gdf_area)
fig = create_choropleth_figure(new_gdf_area)
return fig
if __name__ == '__main__':
app.run_server(debug=True, port = 8051)
参数。然后在回调中,您可以根据下拉选择重新创建散点图框和条形图。
我也不确定你的用例,但为了这个例子的目的,我修改了你的update_dataset
函数来执行 id
而不是左连接(使用你提供的示例数据,如果你这样做如果这是 dcc.Graph
的第一个参数,那么您将始终以
merge_withinboundary
结尾——因为 inner join
和 gdf_all
都完全包含在 merge_withinboundary
中)。然而,我会把这个决定留给你——也许左连接是你想要的实际数据集。出于本次演示的目的,我还将 Afr_gdf_area
设置为默认值,因此当用户从下拉列表中选择 Eur_gdf_area
时,所有的点都是可见的。
gdf_all
试试下面的。这会修改下拉列表中的选项列表以包含较小数据框的名称。然后,它创建一个回调函数,从下拉列表中获取所选值,并使用它来过滤主数据框以获取相应的较小数据框。
zoom = 0