Thursday, 30 March 2023

Multiple callbacks to filter data - dash Plotly

I'm hoping to include multiple callbacks or combine them to filter data. These functions will be used to visualise graphs.

The first callback returns point data if it's within a designated region. It is assigned to a dropdown bar called area-dropdown:. The dropdown bar and callback function returns smaller subsets from the main df. This is accomplished by merging the point data within a specific polygon area.

The additional callback functions are for a scatter chart and bar chart. They filter unique values in Code and Cat.

At present, I've got the callback functions that filter unique values in Code and Cat operational. This is outlined in the 2nd batch of code. If I comment this section out and use the 1st batch of code, the area dropdown callback is functional.

I'm aiming to find a method that combines both these functions together.

import geopandas as gpd
import plotly.express as px
import dash
from dash import dcc, html, Input, Output
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import plotly.graph_objs as go
import plotly.figure_factory as ff
import geopandas as gpd
from itertools import cycle

# point data
gdf_all = gpd.read_file(gpd.datasets.get_path("naturalearth_cities"))

i = iter(['A', 'B', 'C', 'D'])
gdf_all['Cat'] = gdf_all.index.map(dict(zip(gdf_all.index, cycle(i))))

j = iter(['10-20', '20-30', '30-40', '40-50', '60-70'])
gdf_all['Code'] = gdf_all.index.map(dict(zip(gdf_all.index, cycle(j))))

# 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 = 'inner').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)

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
  ),

    html.Label('Code', style = {'paddingTop': '1rem'}),
    dcc.Checklist(
            id = 'Code',
            options = [
                 {'label': '10-20', 'value': '10-20'},
                 {'label': '20-30', 'value': '20-30'},
                 {'label': '30-40', 'value': '30-40'},
                 {'label': '40-50', 'value': '40-50'},
                 {'label': '60-70', 'value': '60-70'},                        
                 ],
            value = ['10-20', '20-30', '30-40', '40-50', '60-70'],
            style = {'display': 'inline', 'margin-right': '50px'}
        ),

    html.Label('Cat', style = {'paddingTop': '1rem'}),
    dcc.Checklist(
            id = 'Cat',
            options = [
                 {'label': 'A', 'value': 'A'},
                 {'label': 'B', 'value': 'B'},
                 {'label': 'C', 'value': 'C'},
                 {'label': 'D', 'value': 'D'},                     
                 ],
            value = ['A', 'B', 'C', 'D'],
            style = {'display': 'inline', 'margin-right': '50px'}
        ),

    html.Label('Spatial Map', style = {'paddingTop': '1rem'}),
    dcc.RadioItems(['Scatter','Hexbin'],'Scatter', 
                       id = 'maps', 
                       #labelStyle= {"margin":"1rem"}, 
                       style = {'display': 'inline', 'margin-right': '50px'}
                       ),

 ], className = "vstack gap-2 h-50")


app.layout = dbc.Container([
    dbc.Row([
        dbc.Col(html.Div(nav_bar), className = 'bg-light', width=2),
        dbc.Col([
            dbc.Row([
                dbc.Col(dcc.Graph(id = 'spatial-chart'))
            ]),
            dbc.Row([
                dbc.Col(dcc.Graph(id = 'bar-chart'))
            ]),
        ], width = 5),
        dbc.Col([
        ], width = 5),
    ])
], fluid = True)


df = gdf_all

#================  1st   =======================
# function to return selected df for plotting
#@app.callback(Output('spatial-chart', 'figure'),
#              Output('bar-chart', 'figure'),
#              Input('data', 'value'),
#              prevent_initial_call=True)

# function to return df using smaller areas
#def update_dataset(dropdown_selection):

#    if dropdown_selection == 'gdf_Africa':
#        gdf = gdf_Africa
#        zoom = 2

#    elif dropdown_selection == 'gdf_Europe':
#        gdf = gdf_Europe
#        zoom = 2

#    else:
#        gdf = gdf_all
#        zoom = 0

#    scatter_subset = px.scatter_mapbox(data_frame = gdf, 
#        lat = 'LAT', 
#        lon = 'LON',
#        zoom = zoom,
#        mapbox_style = 'carto-positron', 
#       )
#    count = gdf['name'].value_counts()

#    bar_subset = px.bar(x = count.index, 
#                y = count.values, 
#               color = count.index, 
#                ) 

#    return scatter_subset, bar_subset
#=============================================


#================  2nd   =======================
# function to filter unique Cat/Code for bar chart
@app.callback(
    [Output('bar-chart', 'figure'),
    ],
    [Input('Cat','value'), 
     Input('Code','value'),
     ]
     )     

def date_chart(cat, code):

    dff = df[df['Cat'].isin(cat)]
    dff = dff[dff['Code'].isin(code)]
    count = dff['Cat'].value_counts()

    data = px.bar(x = count.index, 
                 y = count.values,
                 color = count.index, 
                 )

    fig = [go.Figure(data = data)]

    return fig

# function to filter unique Cat/Code for scatter
@app.callback(
    [Output('spatial-chart', 'figure'),
     ],
    [Input('Cat','value'), 
     Input('Code','value'),
     Input("maps", "value"),
     ])     

def scatter_chart(cat, code, maps):

    if maps == 'Scatter':
    
        dff = df[df['Cat'].isin(cat)]
        dff = dff[dff['Code'].isin(code)]

        data = px.scatter_mapbox(data_frame = dff, 
                                   lat = 'LAT', 
                                   lon = 'LON',
                                   color = 'Cat',
                                   opacity = 0.5,
                                   zoom = 1,
                                   mapbox_style = 'carto-positron', 
                                   hover_name = 'Cat',
                                   ) 

        fig = [go.Figure(data = data)]


    elif maps == 'Hexbin':

        dff = df[df['Cat'].isin(cat)]
        dff = dff[dff['Code'].isin(code)]

        data = ff.create_hexbin_mapbox(data_frame = dff, 
                                      lat = "LAT", 
                                      lon = "LON",
                                      nx_hexagon = 100,
                                      min_count = 1,
         )
    

        fig = [go.Figure(data = data)]

    return fig
#=============================================


if __name__ == '__main__':
    app.run_server(debug=True, port = 8051)


from Multiple callbacks to filter data - dash Plotly

No comments:

Post a Comment