回调到子集几何数据 - dash Plotly

问题描述 投票:0回答:3

我希望包括一个带有回调函数的下拉栏,允许用户在较小的区域内显示特定的点。最初,我想默认使用所有点几何数据。然后我的目标是包括一个下拉栏和回调函数,从这个主 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:

python callback plotly plotly-dash choropleth
3个回答
1
投票

我有一个部分可行的解决方案,但需要进行大量修改。当您使用

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 answer
on the plotly forum)。
我已将其集成到下面的 dash 应用程序中。唯一的问题是与 
go.Scattergeo
列对应的颜色似乎没有像我期望的那样在图中呈现 - 当我弄清楚原因时我会更新这个答案。 lats

    

我认为主要任务是将lons转换为一个回调函数,该回调函数将下拉选择作为输入,并输出新更新的散点图框和条形图。为此,您需要在

vals

0
投票
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


-1
投票

© www.soinside.com 2019 - 2024. All rights reserved.