Clickable location on Map as a filter KPI metrics how to

Hi Everyone,

I am new to plotly Dash. I am trying to create a map in plotly dash and filter/slice and dice the data by clicking on a map. I have parts of the code working but i can’t figure out how i can use the call back to filter the KPI’s by clicking on the map.

Here is the code for what i have so far. Can you please help and let me know what i am missing, Thanks!

from dash import Dash, dcc, html, Output, Input  # pip install dash
import dash_bootstrap_components as dbc    # pip install dash-bootstrap-components
import plotly.express as px
import geopandas as gpd
import pyproj
import pandas as pd                        # pip install pandas

# reading in the shapefile
fp=('/Users/T/Downloads/Downloaded files/Downloaded files/test.shp')
map_df = gpd.read_file(fp)
map_df.to_crs(pyproj.CRS.from_epsg(4326), inplace=True)

df = pd.read_csv("/Users/test2.csv")
merged = map_df.set_index('id').join(df.set_index('id'))
#merged = merged.reset_index()
merged.head()

df.rename(columns={'column_1': 'column1', 'column_2': 'column2'}, inplace=True),
# Build your components
app = Dash(__name__, external_stylesheets=[dbc.themes.LUX])
mytitle = dcc.Markdown(children='')
mygraph = dcc.Graph(figure={})
dropdown = dcc.Dropdown(['c1', 'c2'],
       
                        value='column_name',  # initial value displayed when page first loads
                        clearable=False)
(
[
 html.Div(children=[
        html.Pre(id='click-pre', style={'display':'inline-block', 'width':'50%'}, children=[]),
        html.Pre(id='select-pre', style={'display':'inline-block', 'width':'50%'}, children=[])
    ]),
    html.Div(id='hidden-div', style={'display': 'none'})
]
)
def update_graph(column_name):  # function arguments come from the component property of the Input

    print(column_name)
    print(type(column_name))

fig = px.choropleth(merged, 
                        geojson=merged.geometry, 
                        locations=merged.index, 
                        color='column_name')
fig.update_geos(fitbounds="locations",
                    visible=False)
fig.update_layout(clickmode='event+select')

#Define cards
card1 = dbc.Card([
    dbc.CardBody([
        html.H4("Card title", className="card-title",id="card_num1"),
        html.P("This is some card text", className="card-text",id="card_text1")
    ])
],
    style={'display': 'inline-block',
           'width': '33.3%',
           'text-align': 'center',
           'color':'white',
           'background-color': 'rgba(37, 150, 190)'},
    outline=True)

card2 = dbc.Card([
    dbc.CardBody([
        html.H4("Card title", className="card-title",id="card_num2"),
        html.P("This is some card text", className="card-text",id="card_text2")
        ]
     )],
    style={'display': 'inline-block',
           'width': '33.3%',
           'text-align': 'center',
           'color':'white',
           'background-color': 'rgba(37, 150, 190)'},
    outline=True)

card3 = dbc.Card([
    dbc.CardBody([
        html.H4("Card title", className="card-title",id="card_num3"),
        html.P("This is some card text", className="card-text",id="card_text3")
        ]
     )],
    style={'display': 'inline-block',
           'width': '33.3%',
           'text-align': 'center',
           'color':'white',
           'background-color': 'rgba(37, 150, 190)'},
    outline=True)
    
    
# Customize your own Layout
app.layout = dbc.Container([
    dbc.Row([
        dbc.Col([mytitle], width=6)
    ], justify='center'),
    dbc.Row([
        dbc.Col([mygraph], width=12)
    ]),
    dbc.Row([
        dbc.Col([dropdown], width=6)
    ], justify='center'),

], fluid=True)

# Callback allows components to interact
@app.callback(
    Output(mygraph, 'figure'),
    Output(mytitle, 'children'),
    Input(dropdown, 'value')
)
def update_graph(column_name):  # function arguments come from the component property of the Input

    print(column_name)
    print(type(column_name))
    return fig, '# '+column_name  # returned objects are assigned to the component property of the Output


# Run app
if __name__=='__main__':
    app.run_server(port=8052)

Hi,

Welcome to the community! :slightly_smiling_face:

Please take a look on this example: Part 4. Interactive Graphing and Crossfiltering | Dash for Python Documentation | Plotly

You will need a callback that takes “clickData” from mygraph as input and return some part of the dataframe. clickData is a dictionary with some information about the point/trace that you clicked. For example, I imagine that click_data['points'][0]['location'] will give you the index of the array you provided with locations=merged.index. It should be enough for you to filter the dataframe and find the data you want to select.

Hope this helps!

1 Like

Hi @newuser2
I think @jlfsjunior post is the solution.

Take this fake example where I pretend that `district’ is the store, and I filter the dataframe from the clickdata location of the map.

from dash import Dash, html, dcc, Output, Input, callback
import pandas as pd
import plotly.express as px

app = Dash(__name__)

df = px.data.election()
geojson = px.data.election_geojson()
print(df.head())

fig = px.choropleth(df, geojson=geojson, color="Bergeron",
                    locations="district", featureidkey="properties.district",
                    projection="mercator"
                   )
fig.update_geos(fitbounds="locations", visible=False)
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})

app.layout = html.Div(
    [
        html.Div(id='page-content'),
        dcc.Graph(figure=fig, id='my-graph')
    ]
)

@callback(
    Output('page-content', 'children'),
    Input('my-graph', 'clickData')
)
def store_data(clickData):
    if clickData is None:
        return ""
    else:
        print(clickData)
        location = clickData['points'][0]['location']
        # filter dataframe by store location, then sum all orders of that store.
        dff = df[df['district']==location]
        print(dff)
        
        # get all orders from this stor location 

        return f'the location of store is: {location}'

if __name__ == "__main__":
    app.run_server(debug=True)

Hi @jlfsjunior and @adamschroeder ,

I was able to put the pieces together for the most part How ever i am struggling with the showing the data based on click event. Here is my code below. Can you please let me know what i am doing wrong. I am able to show the map, drop down and click on the map but the data is not being called when i click on the map districts.

I need to be able to show the map and be able to select a value from the drop down and when i click on a district i need to display that district on the top.

from dash import Dash, dcc, html, Output, Input  # pip install dash
import dash_bootstrap_components as dbc    # pip install dash-bootstrap-components
import plotly.express as px
import geopandas as gpd
import pyproj
import pandas as pd                        


# reading in the shapefile
fp=('/Users/districts.shp')
map_df = gpd.read_file(fp)
map_df.to_crs(pyproj.CRS.from_epsg(4326), inplace=True)

df = pd.read_csv("/Users/Testdata.csv")
merged = map_df.set_index('NAME').join(df.set_index('NAME'))
#merged = merged.reset_index()
merged.head()
features = df.columns
# Build your components
app = Dash(__name__, external_stylesheets=[dbc.themes.LUX])
mytitle = dcc.Markdown(children='')
mygraph = dcc.Graph(figure={})
dropdown = dcc.Dropdown(['value1','value2', 'value3'],
                        value='value1',  # initial value displayed when page first loads
                        clearable=False)

(
[
 html.Div(children=[
        html.Pre(id='click-pre', style={'display':'inline-block', 'width':'50%'}, children=[]),
        html.Pre(id='select-pre', style={'display':'inline-block', 'width':'50%'}, children=[])
    ]),
    html.Div(id='hidden-div', style={'display': 'none'})
]
)
# Customize your own Layout
app.layout = dbc.Container([
    dbc.Row([
        dbc.Col([mytitle], width=6)
    ], justify='center'),
    dbc.Row([
        dbc.Col([mygraph], width=12)
    ]),
    dbc.Row([
        dbc.Col([dropdown], width=6)
    ], justify='center'),

], fluid=True)




@app.callback(Output('page-content', 'children'),
              [Input('graph', 'clickData'),
               ])
def display_click_data(clickData):
     if clickData is None:
        return ""
     else:
        print(clickData)
        location = clickData['points'][0]['location']
        # filter dataframe by store location, then sum all orders of that store.
        dff = df[df['district']==location]
        print(dff)

# Callback allows components to interact
@app.callback(
    Output(mygraph, 'figure'),
    Output(mytitle, 'children'),
    Input(dropdown, 'value')
)
def update_graph(column_name):  # function arguments come from the component property of the Input

    print(column_name)
    print(type(column_name))
    # map

    fig = px.choropleth(merged, 
                        geojson=merged.geometry, 
                        locations=merged.index, 
                        color=column_name)
    fig.update_geos(fitbounds="locations",
                    visible=False)
    fig.update_layout(clickmode='event+select')
    
    return fig, '# '+column_name  # returned objects are assigned to the component property of the Output


# Run app
if __name__=='__main__':
    app.run_server(port=8052)

Two observations regarding the following callback:

@app.callback(Output('page-content', 'children'),
              [Input('graph', 'clickData'),
               ])
def display_click_data(clickData):
     if clickData is None:
        return ""
     else:
        print(clickData)
        location = clickData['points'][0]['location']
        # filter dataframe by store location, then sum all orders of that store.
        dff = df[df['district']==location]
        print(dff)
  1. I don’t see where the component with id = “page-content” is defined.
  2. Your callback should return a value when clickData is not None. print will simply print the object on the server.

Thanks @jlfsjunior

I have updated the component id and added the return as well. I am still not able to click on the map and display the data.
Could you please let me know if i am not calling the correct components. I tried several sceneraio’s and nothing seems to be working.

from dash import Dash, dcc, html, Output, Input  # pip install dash
import dash_bootstrap_components as dbc    # pip install dash-bootstrap-components
import plotly.express as px
import geopandas as gpd
import pyproj
import pandas as pd                        


# reading in the shapefile
fp=('/Users/districts.shp')
map_df = gpd.read_file(fp)
map_df.to_crs(pyproj.CRS.from_epsg(4326), inplace=True)

df = pd.read_csv("/Users/Testdata.csv")
merged = map_df.set_index('NAME').join(df.set_index('NAME'))
#merged = merged.reset_index()
merged.head()
features = df.columns
# Build your components
app = Dash(__name__, external_stylesheets=[dbc.themes.LUX])
mytitle = dcc.Markdown(children='')
mygraph = dcc.Graph(figure={})
dropdown = dcc.Dropdown(['value1','value2', 'value3'],
                        value='value1',  # initial value displayed when page first loads
                        clearable=False)

(
[
 html.Div(children=[
        html.Pre(id='click-pre', style={'display':'inline-block', 'width':'50%'}, children=[]),
        html.Pre(id='select-pre', style={'display':'inline-block', 'width':'50%'}, children=[])
    ]),
    html.Div(id='hidden-div', style={'display': 'none'})
]
)
# Customize your own Layout
app.layout = dbc.Container([
    dbc.Row([
        dbc.Col([mytitle], width=6)
    ], justify='center'),
    dbc.Row([
        dbc.Col([mygraph], width=12)
    ]),
    dbc.Row([
        dbc.Col([dropdown], width=6)
    ], justify='center'),

], fluid=True)




@app.callback(Output('mygraph', 'children'),
              [Input('mygraph', 'clickData'),
               ])
def display_click_data(clickData):
     if clickData is None:
        return ""
     else:
        print(clickData)
        location = clickData['points'][0]['location']
        # filter dataframe by store location, then sum all orders of that store.
        dff = df[df['NAME']==location]
        print(dff)
        return f'the location of store is: {location}'
 

# Callback allows components to interact
@app.callback(
    Output(mygraph, 'figure'),
    Output(mytitle, 'children'),
    Input(dropdown, 'value')
)
def update_graph(column_name):  # function arguments come from the component property of the Input

    print(column_name)
    print(type(column_name))
    # map

    fig = px.choropleth(merged, 
                        geojson=merged.geometry, 
                        locations=merged.index, 
                        color=column_name)
    fig.update_geos(fitbounds="locations",
                    visible=False)
    fig.update_layout(clickmode='event+select')
    
    return fig, '# '+column_name  # returned objects are assigned to the component property of the Output


# Run app
if __name__=='__main__':
    app.run_server(port=8052)

Hi,

I want these two call backs to work (one call back is for dropdown and the second call back is for click data). I am only able to get one call back working at any one time.

Can you please help.

Thanks

Hi @adamschroeder and @jlfsjunior
what do i need to add to add a filter if i follow this code, this works for selecting and clicking the map however i would also need to add a dropdown call back to this example. for example if i want to add a drop with the ability to highlight the map and show highest to lowest male population as one metric and the ability to show highest to lowest female population as a second metric in the district map.

from dash import Dash, html, dcc, Output, Input, callback
import pandas as pd
import plotly.express as px

app = Dash(__name__)

df = px.data.election()
geojson = px.data.election_geojson()
print(df.head())

fig = px.choropleth(df, geojson=geojson, color="Bergeron",
                    locations="district", featureidkey="properties.district",
                    projection="mercator"
                   )
fig.update_geos(fitbounds="locations", visible=False)
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})

app.layout = html.Div(
    [
        html.Div(id='page-content'),
        dcc.Graph(figure=fig, id='my-graph')
    ]
)

@callback(
    Output('page-content', 'children'),
    Input('my-graph', 'clickData')
)
def store_data(clickData):
    if clickData is None:
        return ""
    else:
        print(clickData)
        location = clickData['points'][0]['location']
        # filter dataframe by store location, then sum all orders of that store.
        dff = df[df['district']==location]
        print(dff)
        
        # get all orders from this stor location 

        return f'the location of store is: {location}'

if __name__ == "__main__":
    app.run_server(debug=True)

hi @newuser2
In this code, I created a dropdown to choose between ‘total’ and ‘Coderre’. Imagine that these are women vs men population. The user chooses the dropdown option, which updates the color property of the choropleth (second callback).

from dash import Dash, html, dcc, Output, Input, callback
import pandas as pd
import plotly.express as px

app = Dash(__name__)

df = px.data.election()
geojson = px.data.election_geojson()
print(df.columns)


app.layout = html.Div(
    [
        dcc.Dropdown(['total','Coderre'], 'total', id='map_color_by'),
        html.Div(id='page-content'),
        dcc.Graph(id='my-graph')
    ]
)


@callback(
    Output('page-content', 'children'),
    Input('my-graph', 'clickData'),
)
def store_data(clickData):
    if clickData is None:
        return ""
    else:
        print(clickData)
        location = clickData['points'][0]['location']
        # filter dataframe by store location, then sum all orders of that store.
        dff = df[df['district'] == location]
        print(dff)

        # get all orders from this stor location

        return f'the location of store is: {location}'


@callback(
    Output('my-graph', 'figure'),
    Input('map_color_by','value')
)
def store_data(highlight_map_by):
    print(highlight_map_by)
    fig = px.choropleth(df, geojson=geojson, color=highlight_map_by,
                        locations="district",
                        featureidkey="properties.district",
                        projection="mercator"
                        )
    fig.update_geos(fitbounds="locations", visible=False)
    fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})

    return fig
if __name__ == "__main__":
    app.run_server(debug=True)

Thank you so much @adamschroeder

Hi @adamschroeder and @jlfsjunior ,

how would i show a metric that is based on the dropdown instead of district. for example if i want to show ‘total’ along with the location when i select ‘total’ in the drop down and show ‘Coderre’ when i select ‘Coderre’ in the dropdown.

from dash import Dash, html, dcc, Output, Input, callback
import pandas as pd
import plotly.express as px

app = Dash(__name__)

df = px.data.election()
geojson = px.data.election_geojson()
print(df.columns)


app.layout = html.Div(
    [
        dcc.Dropdown(['total','Coderre'], 'total', id='map_color_by'),
        html.Div(id='page-content'),
        dcc.Graph(id='my-graph')
    ]
)


@callback(
    Output('page-content', 'children'),
    Input('my-graph', 'clickData'),
)
def store_data(clickData):
    if clickData is None:
        return ""
    else:
        print(clickData)
        location = clickData['points'][0]['location']
        # filter dataframe by store location, then sum all orders of that store.
        dff = df[df['district'] == location]
        print(dff)

        # get all orders from this stor location

        return f'the location of store is: {location}'


@callback(
    Output('my-graph', 'figure'),
    Input('map_color_by','value')
)
def store_data(highlight_map_by):
    print(highlight_map_by)
    fig = px.choropleth(df, geojson=geojson, color=highlight_map_by,
                        locations="district",
                        featureidkey="properties.district",
                        projection="mercator"
                        )
    fig.update_geos(fitbounds="locations", visible=False)
    fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})

    return fig
if __name__ == "__main__":
    app.run_server(debug=True)

Just to clarify, when you say “show a metric along the location”, do you mean to show the value in one column of the dataframe (like the values selected in map_color_by )?

If that is the case, you can do something like:

@callback(
    Output('page-content', 'children'),
    Input('my-graph', 'clickData'),
    State('map_color_by','value')
)
def store_data(clickData, selected_column):
    if clickData is None:
        return ""
    else:
        location = clickData['points'][0]['location']
        # filter dataframe by store location, then sum all orders of that store.
        dff = df[df['district'] == location]

        return f'Metric: {dff[selected_column]}'  # Or some operation over the dataframe

Is that what you mean?

1 Like

Hi @adamschroeder @newuser2 @jlfsjunior I have a similar query. I want to use my clickable feature on map as filter for data frame to update graph.

I have a data frame of 10 agricultural parcels (polygons on map). Every parcel has a data that I want to display on graphs. For filtering I am using two ways.

1: Dropdown menu

This part is completed and working properly. Whenever I select any parcel number its corresponding data is plotted as visible in left graph.

2: Clickable feature on polygon in map

The second way I want to filter data is if I click on any polygon of parcel its corresponding graph is updated on graph below which is empty for now. I have tried this with hover_feature callback and it is even showning the parcel number on dashboard if I use this callback:

@app.callback(Output("state", "children"), [Input("geojson", "hover_feature")])

def state_hover(features):
    if features is not None:
        clickfeatureresult =f"{features['properties']['parcel_id']}"
        return clickfeatureresult

However now when I want to use this return answer variable as input to filter graph its not possible because the variable is not stored outside the function. Is there a way I can do this? or is there anyother way to achive this goal of clicking any polygon and filtering the dataframe for graph. Please guide. My full code and dashboard image is as follows


.

Code

import dash
import dash_leaflet as dl
import geopandas as gpd
from dash import html, Input, Output
import dash_leaflet as dl
from dash_extensions.javascript import arrow_function, assign
import geojson
from dash import dcc
import plotly.express as px
import pandas as pd
import requests

with open('C:/Users/AbdullahToqeer/Desktop/dashboard data/map.geojson') as f:
    geojson_data = geojson.load(f)


column_names = ["parcel_foreign_id_x", "s1product_end_time", "s1product_ron","cohvh_avg", "cohvv_avg", "vhvv_avg","parcel_foreign_id_y", "s2product_start_time", "s2product_ron", "ndvi_avg" ]

df = pd.DataFrame(columns = column_names)

foreign=1

while (foreign <=10):


    s1_time_series_url_p6 = 'https://demodev2.kappazeta.ee/ard_api_demo/v1/time_series/s1?limit_to_rasters=true&parcel_foreign_id=0&properties=parcel_foreign_id%2Cs1product_end_time%2Cs1product_ron%2Ccohvh_avg%2Ccohvv_avg%2Cvhvv_avg'
    s2_time_series_url_p6 = 'https://demodev2.kappazeta.ee/ard_api_demo/v1/time_series/s2?limit_to_rasters=true&parcel_foreign_id=0&properties=parcel_foreign_id%2Cs2product_start_time%2Cs2product_ron%2Cndvi_avg'
    position = 101
    foreign_n=str(foreign)
    s1_time_series_url_p6 = s1_time_series_url_p6[:position] + foreign_n + s1_time_series_url_p6[position+1:]
    s2_time_series_url_p6 = s2_time_series_url_p6[:position] + foreign_n + s2_time_series_url_p6[position+1:]
    r_s1_time_series_p6 = requests.get(s1_time_series_url_p6)
    r_s2_time_series_p6 = requests.get(s2_time_series_url_p6)
    json_s1_time_series_p6 = r_s1_time_series_p6.json()
    json_s2_time_series_p6 = r_s2_time_series_p6.json()
    df_s1_time_series_p6 = pd.DataFrame(json_s1_time_series_p6['s1_time_series'])
    df_s2_time_series_p6 = pd.DataFrame(json_s2_time_series_p6['s2_time_series'])
    df_s2_time_series_p6.s2product_start_time=df_s2_time_series_p6.s2product_start_time.str[0:11]
    df_s1_time_series_p6.s1product_end_time=df_s1_time_series_p6.s1product_end_time.str[0:11]
    dfinal_p6 = df_s1_time_series_p6.merge(df_s2_time_series_p6, how='inner', left_on='s1product_end_time', right_on='s2product_start_time')
    cols_p6 = ['parcel_foreign_id_x', 's1product_ron','parcel_foreign_id_y','s2product_ron']
    dfinal_p6[cols_p6] = dfinal_p6[cols_p6].apply(pd.to_numeric, errors='coerce', axis=1)
    df = pd.concat([dfinal_p6,df],ignore_index = True)
    foreign = foreign+1

df = df.astype({"parcel_foreign_id_x": str}, errors='raise') 
df['s1product_end_time']= pd.to_datetime(df['s1product_end_time'])



external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

classes = [0, 10, 20, 50, 100, 200, 500, 1000]
colorscale = ['#FFEDA0', '#FED976', '#FEB24C', '#FD8D3C', '#FC4E2A', '#E31A1C', '#BD0026', '#800026']
style = dict(weight=2, opacity=1, color='white', dashArray='3', fillOpacity=0.7)



app.layout = html.Div([

    html.H2('Agriculture Parcels Dashboard', style={'textAlign': 'center'}),
    html.Div([
        "Select parcel ID" ,
    dcc.Dropdown(['1', '2', '3','4', '5', '6','7', '8', '9','10'], '1', multi=False, id='my-dropdown', style={"width": "50%"}) ]),
    html.Div([

        html.Div([
                
                dcc.Graph(
                    id='my-graph',
                       className="six columns",
                       ),
                      
            html.Div([
                dl.Map(center = [47.3824, 2.9253],
                zoom=10,children=[
                    dl.TileLayer(),
                    dl.GeoJSON(data=geojson_data, zoomToBounds=True, zoomToBoundsOnClick=True, hoverStyle=arrow_function(dict(weight=5, color='#666', dashArray='')), hideout=dict(colorscale=colorscale, classes=classes, style=style, colorProp="density"),options=dict(style=dict(color="green")), id="geojson"),
                    ], style={'width': '500px', 'height': '500px', 'margin': "auto", "display": "block"}, id="map"),
],className="six columns"),

html.Div([  
                dcc.Graph(
                    id='my-graph_1',
                    className="six columns"),

                html.Div(id="state")

])
])
])
])

@app.callback(Output('my-graph', 'figure'),Input('my-dropdown', 'value'))

def update_graph(parcel_foreign_id_x_selected):
    dff = df[df.parcel_foreign_id_x==parcel_foreign_id_x_selected]
    fig = px.line(data_frame=dff, x="s1product_end_time", y=["ndvi_avg", "vhvv_avg","cohvv_avg","cohvh_avg"],markers=True, width=1000,height=600,
                      labels={"s1product_end_time": "Date"})
    return fig


@app.callback(Output("state", "children"), [Input("geojson", "hover_feature")])

def state_hover(features):
    if features is not None:
        clickfeatureresult =f"{features['properties']['parcel_id']}"
        return clickfeatureresult


@app.callback(Output('my-graph_1', 'figure'),Input('clickfeatureresult', 'value'))

def update_graph(parcel_foreign_id_x_selected):
    dff = df[df.parcel_foreign_id_x==parcel_foreign_id_x_selected]
    fig = px.line(data_frame=dff, x="s1product_end_time", y=["ndvi_avg", "vhvv_avg","cohvv_avg","cohvh_avg"],markers=True, width=1000,height=600,
                      labels={"s1product_end_time": "Date"})
    return fig

if __name__ == '__main__':
        app.run_server(port=4099)

Hi @abdullah,

You can use the same callback to return the figure you want to add to the graph below. Here’s how I would do schematically:

@app.callback(Output("graph-below", "figure"), [Input("geojson", "hover_feature")])

def state_hover(features):
    if features is not None:
        clickfeatureresult =f"{features['properties']['parcel_id']}"
       # Use clickfeatureresult to filter data and create figure..
       fig = # ... 
       return fig

You can find an example here: Part 4. Interactive Graphing and Crossfiltering | Dash for Python Documentation | Plotly

2 Likes

Hi @jlfsjunior I implemented your suggestion and it worked perfectly. So it was for If i am plotling a new graph. Now if I want it to be implemented on the same graph I have to use Duplicate callback outputs as I want to have a same output from two different callbacks so the solution is to use Duplicate callback outputs. I have gone through the documentation about it at
https://dash.plotly.com/duplicate-callback-outputs
and have tried to implement in my case as follows:

@app.callback(Output('graph', 'figure'),Input('my-dropdown', 'value'), Input('geojson', 'hover_feature'), prevent_initial_call=True)

def update_graph(b1, b2):
    triggered_id = ctx.triggered_id
    print(triggered_id)
    if triggered_id == 'my-dropdown':
         return draw_graph()
    elif triggered_id == 'geojson':
         return state_hover()

def draw_graph(parcel_foreign_id_x_selected):
    dff = df[df.parcel_foreign_id_x==parcel_foreign_id_x_selected]
    fig = px.line(data_frame=dff, x="s1product_end_time", y=["ndvi_avg", "vhvv_avg","cohvv_avg","cohvh_avg"],markers=True, width=1000,height=600,
                      labels={"s1product_end_time": "Date"})
    return fig

def state_hover(features):
    if features is not None:
        clickfeatureresult =f"{features['properties']['parcel_id']}"
        dfff= df[df.parcel_foreign_id_x==clickfeatureresult]
        fig = px.line(data_frame=dfff, x="s1product_end_time", y=["ndvi_avg", "vhvv_avg","cohvv_avg","cohvh_avg"],markers=True, width=1000,height=600,
                      labels={"s1product_end_time": "Date"})
    return fig

The problem is when I run this the graph doesnot get updated against any of the inputs. The error I get are

TypeError: state_hover() missing 1 required positional argument: ‘features’
TypeError: draw_graph() missing 1 required positional argument: ‘parcel_foreign_id_x_selected’

One reason it might be is my function is draw_graph(parcel_foreign_id_x_selected) and state_hover(features) but I am using draw_graph() and state_hover() in the def update_graph(b1, b2) but if I put draw_graph(parcel_foreign_id_x_selected) and state_hover(features) in the update_graph() it says features and parcel_foreign_id_x_selected are not defined.

Can you please guide what I am doing wrong here. My complete code is as follows:

import dash
import dash_leaflet as dl
import geopandas as gpd
from dash import html, Input, ctx, Output
import dash_leaflet as dl
from dash_extensions.javascript import arrow_function, assign
import geojson
from dash import dcc
import plotly.express as px
import pandas as pd
import requests

with open('C:/Users/AbdullahToqeer/Desktop/dashboard data/map.geojson') as f:
    geojson_data = geojson.load(f)

column_names = ["parcel_foreign_id_x", "s1product_end_time", "s1product_ron","cohvh_avg", "cohvv_avg", "vhvv_avg","parcel_foreign_id_y", "s2product_start_time", "s2product_ron", "ndvi_avg" ]

df = pd.DataFrame(columns = column_names)

foreign=1

while (foreign <=10):

    s1_time_series_url_p6 = 'https://demodev2.kappazeta.ee/ard_api_demo/v1/time_series/s1?limit_to_rasters=true&parcel_foreign_id=0&properties=parcel_foreign_id%2Cs1product_end_time%2Cs1product_ron%2Ccohvh_avg%2Ccohvv_avg%2Cvhvv_avg'
    s2_time_series_url_p6 = 'https://demodev2.kappazeta.ee/ard_api_demo/v1/time_series/s2?limit_to_rasters=true&parcel_foreign_id=0&properties=parcel_foreign_id%2Cs2product_start_time%2Cs2product_ron%2Cndvi_avg'
    position = 101
    foreign_n=str(foreign)
    s1_time_series_url_p6 = s1_time_series_url_p6[:position] + foreign_n + s1_time_series_url_p6[position+1:]
    s2_time_series_url_p6 = s2_time_series_url_p6[:position] + foreign_n + s2_time_series_url_p6[position+1:]
    r_s1_time_series_p6 = requests.get(s1_time_series_url_p6)
    r_s2_time_series_p6 = requests.get(s2_time_series_url_p6)
    json_s1_time_series_p6 = r_s1_time_series_p6.json()
    json_s2_time_series_p6 = r_s2_time_series_p6.json()
    df_s1_time_series_p6 = pd.DataFrame(json_s1_time_series_p6['s1_time_series'])
    df_s2_time_series_p6 = pd.DataFrame(json_s2_time_series_p6['s2_time_series'])
    df_s2_time_series_p6.s2product_start_time=df_s2_time_series_p6.s2product_start_time.str[0:11]
    df_s1_time_series_p6.s1product_end_time=df_s1_time_series_p6.s1product_end_time.str[0:11]
    dfinal_p6 = df_s1_time_series_p6.merge(df_s2_time_series_p6, how='inner', left_on='s1product_end_time', right_on='s2product_start_time')
    cols_p6 = ['parcel_foreign_id_x', 's1product_ron','parcel_foreign_id_y','s2product_ron']
    dfinal_p6[cols_p6] = dfinal_p6[cols_p6].apply(pd.to_numeric, errors='coerce', axis=1)
    df = pd.concat([dfinal_p6,df],ignore_index = True)
    foreign = foreign+1

df = df.astype({"parcel_foreign_id_x": str}, errors='raise') 
df['s1product_end_time']= pd.to_datetime(df['s1product_end_time'])

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

classes = [0, 10, 20, 50, 100, 200, 500, 1000]
colorscale = ['#FFEDA0', '#FED976', '#FEB24C', '#FD8D3C', '#FC4E2A', '#E31A1C', '#BD0026', '#800026']
style = dict(weight=2, opacity=1, color='white', dashArray='3', fillOpacity=0.7)


app.layout = html.Div([

    html.H2('Agriculture Parcels Dashboard', style={'textAlign': 'center'}),
    html.Div([
        "Select parcel ID" ,
    dcc.Dropdown(['1', '2', '3','4', '5', '6','7', '8', '9','10'], '1', multi=False, id='my-dropdown', style={"width": "50%"}) ]),
    html.Div([

        html.Div([
            
            dcc.Graph(
                    id='graph',
                       className="six columns",
                       ),
                     
            html.Div([
                dl.Map(center = [47.3824, 2.9253],
                zoom=10,children=[
                    dl.TileLayer(),
                    dl.GeoJSON(data=geojson_data, zoomToBounds=True, zoomToBoundsOnClick=True, hoverStyle=arrow_function(dict(weight=5, color='#666', dashArray='')), hideout=dict(colorscale=colorscale, classes=classes, style=style, colorProp="density"),options=dict(style=dict(color="green")), id="geojson"),
                    ], style={'width': '500px', 'height': '500px', 'margin': "auto", "display": "block"}, id="map"),
],className="six columns"),], className='row'),
        

])
])

@app.callback(Output('graph', 'figure'),Input('my-dropdown', 'value'), Input('geojson', 'hover_feature'), prevent_initial_call=True)

def update_graph(b1, b2):
    triggered_id = ctx.triggered_id
    print(triggered_id)
    if triggered_id == 'my-dropdown':
         return draw_graph()
    elif triggered_id == 'geojson':
         return state_hover()

def draw_graph(parcel_foreign_id_x_selected):
    dff = df[df.parcel_foreign_id_x==parcel_foreign_id_x_selected]
    fig = px.line(data_frame=dff, x="s1product_end_time", y=["ndvi_avg", "vhvv_avg","cohvv_avg","cohvh_avg"],markers=True, width=1000,height=600,
                      labels={"s1product_end_time": "Date"})
    return fig

def state_hover(features):
    if features is not None:
        clickfeatureresult =f"{features['properties']['parcel_id']}"
        dfff= df[df.parcel_foreign_id_x==clickfeatureresult]
        fig = px.line(data_frame=dfff, x="s1product_end_time", y=["ndvi_avg", "vhvv_avg","cohvv_avg","cohvh_avg"],markers=True, width=1000,height=600,
                      labels={"s1product_end_time": "Date"})
    return fig

if __name__ == '__main__':
        app.run_server(port=4099)

Hi @jlfsjunior @adamschroeder,

Is there a way in plotly dash to be able to click and change data based on the selecting and de selecting data. for example in this case below from the code i am able to show the selected store location when i click on the map, however when i deselect (unclick) on the map i want to show different data or maybe even not show anything.

I am seeing that when i unclick it still shows the last selected event. Is there are functionality to support unclick data? Can you please let me know.


from dash import Dash, html, dcc, Output, Input, callback
import pandas as pd
import plotly.express as px

app = Dash(__name__)

df = px.data.election()
geojson = px.data.election_geojson()
print(df.columns)


app.layout = html.Div(
    [
        dcc.Dropdown(['total','Coderre'], 'total', id='map_color_by'),
        html.Div(id='page-content'),
        dcc.Graph(id='my-graph')
    ]
)


@callback(
    Output('page-content', 'children'),
    Input('my-graph', 'clickData'),
)
def store_data(clickData):
    if clickData is None:
        return ""
    else:
        print(clickData)
        location = clickData['points'][0]['location']
        # filter dataframe by store location, then sum all orders of that store.
        dff = df[df['district'] == location]
        print(dff)

        # get all orders from this stor location

        return f'the location of store is: {location}'


@callback(
    Output('my-graph', 'figure'),
    Input('map_color_by','value')
)
def store_data(highlight_map_by):
    print(highlight_map_by)
    fig = px.choropleth(df, geojson=geojson, color=highlight_map_by,
                        locations="district",
                        featureidkey="properties.district",
                        projection="mercator"
                        )
    fig.update_geos(fitbounds="locations", visible=False)
    fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})

    return fig
if __name__ == "__main__":
    app.run_server(debug=True)

You can use the trick showed here: Reset Click Data when clicking on map - #3 by konsti_papa

Plotly doesn’t capture clicks outside the traces, so the trick is to add the graph into a div and use n_clicks to reset the selection.

You might need some extra code if you want to use clickmode="event" (to highlight the selection), since this approach won’t remove the highlight.

Aren’t b1=parcel_foreign_id_x_selected and b2=features in this callback:

@app.callback(Output(‘graph’, ‘figure’),Input(‘my-dropdown’, ‘value’), Input(‘geojson’, ‘hover_feature’), prevent_initial_call=True)

def update_graph(b1, b2):
triggered_id = ctx.triggered_id
print(triggered_id)
if triggered_id == ‘my-dropdown’:
return draw_graph()
elif triggered_id == ‘geojson’:
return state_hover()

1 Like

Got it. I tried looking into documentation but there was not explanation about b1 and b2. Thanks for help!