pythoncallbackplotly-dashgeopandasdash-leaflet

App layout and callback for a Dash-Leaflet map


I am adding a Dash-Leaflet map to a callback and want to know how to setup up the app layout. Below are the modules and datasets used.

import dash
import plotly.express as px
from dash import Dash, dcc, html, Input, Output, State, dash_table
from dash.exceptions import PreventUpdate
import plotly.graph_objs as go
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from jupyter_dash import JupyterDash
import dash_bootstrap_components as dbc

import dash_leaflet as dl
import dash_leaflet.express as dlx
import geopandas as gpd
import json


df = df[["Analysis","LGA", "SMA","AVLU", "Address","Locality", "AREA(HA)","Sale Date"]]
gdf1 = gdf[["ANALYSISID","geometry"]]
sales =gdf1.merge(dfb, left_on='ANALYSISID', right_on='Analysis')


app = dash.Dash(__name__, external_stylesheets=[dbc.themes.FLATLY])
app.layout = html.Div([

####app layout code for 4 dropdowns, a radio button, a table, 2 graphs, and 2 statistical ouputs.####

Below is a snippet of the app layout code I have been writing but have not been successful in connecting to a callback and returning a figure.

############################################################################### map layout

    dbc.Row(
        [
            dbc.Col(
                    dl.Map([
                    dl.TileLayer(),  # <-- default basemap
                    geojson,                      
                    id="map", style={'width': '100%', 'height': '80vh', 'margin': "auto", "display": "block"},
                ])
         
            ),
            
        ],
)


@app.callback(
    Output('graph-container1', 'children'),    
    Output('graph-container', 'children'),
    Output('table-container', 'data'),
    Output('my_output', 'children'),
    Output('my_output1', 'children'),
    Output('map', 'children'),
    Input('factor2', 'value'),
    Input('factor1', 'value'),
    Input('radio_items', 'value'),   
    Input('SMA-dpdn', 'value'), 
    Input('LGA-dpdn', 'value'),

prevent_initial_call=True

Solution

  • full working example

    import pandas as pd
    import geopandas as gpd
    import numpy as np
    import dash
    import plotly.express as px
    from dash import Dash, dcc, html, Input, Output, dash_table
    import dash_leaflet as dl
    from jupyter_dash import JupyterDash
    
    # set up some data that is missing from question
    gdf = (
        gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
        .rename(columns={"iso_a3": "ANALYSISID"})
        .sample(30)
    )
    df = pd.DataFrame(
        {
            c: gdf["ANALYSISID"] if c == "Analysis" else np.random.uniform(1, 30, len(gdf))
            for c in ["Analysis", "LGA", "SMA", "AVLU", "Address", "Locality", "AREA(HA)", "Sale Date"]  # fmt: skip
        }
    )
    dfb = df
    df = df[["Analysis", "LGA", "SMA", "AVLU", "Address", "Locality", "AREA(HA)", "Sale Date"]]  # fmt: skip
    gdf1 = gdf[["ANALYSISID", "geometry"]]
    sales = gdf1.merge(dfb, left_on="ANALYSISID", right_on="Analysis")
    
    app = JupyterDash(__name__)
    
    # setup a layout missing from question, partial definition of callback
    app.layout = html.Div(
        [html.Div(id=id) for id in ["my_output", "my_output1"]]
        + [dcc.Input(id=id, type="number") for id in ["factor2", "factor1"]]
        + [
            dcc.RadioItems(
                df["Analysis"].tolist(),
                df["Analysis"].tolist()[0],
                inline=True,
                id="radio_items",
            )
        ]
        + [dcc.Dropdown(["A", "B", "C"], "A", id=id) for id in ["SMA-dpdn", "LGA-dpdn"]]
        + [html.Div(id=id) for id in ["graph-container1", "graph-container", "map"]]
        + [
            dash_table.DataTable(
                id="table-container",
                columns=[
                    {"name": i, "id": i} for i in df.drop(columns=["Analysis"]).columns
                ],
            )
        ]
    )
    
    # many output and inputs so define them as iterators
    @app.callback(
        [
            Output("graph-container1", "children"),
            Output("graph-container", "children"),
            Output("table-container", "data"),
            Output("my_output", "children"),
            Output("my_output1", "children"),
            Output("map", "children"),
        ],
        [
            Input("factor2", "value"),
            Input("factor1", "value"),
            Input("radio_items", "value"),
            Input("SMA-dpdn", "value"),
            Input("LGA-dpdn", "value"),
        ],
    )
    # call back that takes all parameters and returns all outputs
    def big_cb(factor2, factor1, radio_items, sma_dpdn, lga_dpdn):
        # make graphs, table and map dependent on radio button selection
        df_ = df.loc[df["Analysis"].eq(radio_items)].drop(columns=["Analysis"])
        gdf_ = gdf1.loc[gdf1["ANALYSISID"].eq(radio_items)]
        return (
            dcc.Graph(figure=px.scatter(df_)),
            dcc.Graph(figure=px.bar(df_)),
            df_.to_dict("records"),
            factor2,
            radio_items,
            # dash-leaflet the question.  create using geojson from geopandas dataframe
            # centering is just for convenience
            dl.Map(
                [dl.TileLayer(), dl.GeoJSON(data=gdf_.__geo_interface__)],
                center=gdf_.unary_union.centroid.coords[0][::-1],
                style={"width": "1000px", "height": "500px"},
            ),
        )
    
    
    if __name__ == "__main__":
        #     app.run_server(debug=True)
        app.run_server(mode="inline", debug=True, port=8051)