Make the line graph update based on the country clicked on the plotly choropleth map

I created the following script to display a plotly dash choropleth map and a line graph. This is the gist of this data visualization application; the line graph should update a new line chart based on the country clicked, using the clickData event on the choropleth map. The map is working perfectly, based on the two dropdowns and sliders under dcc.Dropdown and dcc.Slider respectively. However, I have issues with creating a responsive line chart to the clickData event in the map. The default clickData value is “Kenya”, however, when I click on a different country say “Botswana” I receive the following error circled in red:

I ensured I understood all I could from a similar interactive graphing dash plotly article. I tried an overly-amateurish idea of converting the clickData value (in the variable country_name) to string but didn’t work. ChatGPT suggestions were not helpful either.

Here is the Github link to the dataframes used.

Here is the code:

import as px
import pandas as pd
from dash import Dash, dcc, Input, Output, html

df_drinking = pd.read_csv("archive/Basic and safely managed drinking water services.csv")
df_sanitation = pd.read_csv("archive/Basic and safely managed sanitation services.csv")
df_handwashing = pd.read_csv("archive/Handwashing with soap.csv")
df_open_defecation = pd.read_csv("archive/Open defecation.csv")

dataframe_dict = {"Titles": ["Drinking_water_dataframe", "Sanitation_services_dataframe", "Handwashing_dataframe", "Open_defecation"],
              "Dataframes": ['drinking', 'sanitation', 'handwashing', 'open_defecation'] }

dataframe_table = pd.DataFrame(dataframe_dict)

# Function to get minimum and maximum year value in each dataframe
for dataframe in dataframe_table["Dataframes"]:
    if dataframe == "drinking":
        min_year = df_drinking["Year"].min()
        max_year = df_drinking["Year"].max()
    elif dataframe == "sanitation":
        min_year = df_sanitation["Year"].min()
        max_year = df_sanitation["Year"].max()
    elif dataframe == "handwashing":
        min_year = df_handwashing["Year"].min()
        max_year = df_handwashing["Year"].max()
        min_year = df_open_defecation["Year"].min()
        max_year = df_open_defecation["Year"].max()

# CSS styling
external_stylesheets = ['']
app = Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div(children=[
    # The interactive plotly map
    html.Div(className="row", children=[

        html.Div(className="six columns", children=[
        # The Dropdown to select the dataframes
            dcc.Dropdown(#options=['df_drinking', 'df_handwashing'],
                style={"width": "50%", "display": "inline-block"})

        html.Div(className="six columns", children=[
        # The Dropdown to select a value from the Residence Type column
                options=["Total", "Urban", "Rural"],
                style={"width": "40%", "display": "inline-block"}




    # The interactive plotly map
    dcc.Graph(id="sanitation_map", clickData={"points": [{"customdata": "Kenya"}]}),

    # Add slider for year
    dcc.Slider(min=min_year, max=max_year, value=min_year, step=None, marks={str(year): str(year) for year in range(min_year, max_year + 1)},
               included=False, id="year_slider"),

    # The Line graph


# Show the selected dataframe
    Output("dataframe_dropdown_output", "children"),
    Input("dataframe_dropdown", "value"),
    Input("residence_area_type", "value")
def dropdown_output(value, residence_value):
    return f"You have chosen the {value} dataframe and the {residence_value} Residence Area Type option"

# Draw a plotly map based on the dropdown value chosen
    Output("sanitation_map", "figure"),
    Input("dataframe_dropdown", "value"),
    Input("year_slider", "value"),
    Input("residence_area_type", "value")
def choropleth_map(dataframe_dropdown, year_slider, residence_area_type):
    if dataframe_dropdown == "drinking":
        df = df_drinking
    elif dataframe_dropdown == "sanitation":
        df = df_sanitation
    elif dataframe_dropdown == "handwashing":
        df = df_handwashing
        df = df_open_defecation

    dff = df[df["Year"] == year_slider]
    dff = dff[dff["Residence Area Type"] == residence_area_type]
    dff = dff.sort_values(by="Year")

    fig = px.choropleth(dff, locations="Country", locationmode="country names", color="Display Value", projection="mercator",
                        hover_name="Country", scope="world", width=1000)

    fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0})

    return fig

# Draw the drinking line graph
## First create the function that will automatically plot the map based on country name (from hover), the dataframe
## selected (from dropdown) and the residence type (from dropdown also)
def check_dropdown(dataframe_dropdown):
    if dataframe_dropdown == "drinking":
        df = df_drinking
    elif dataframe_dropdown == "sanitation":
        df = df_sanitation
    elif dataframe_dropdown == "handwashing":
        df = df_handwashing
        df = df_open_defecation

    return df

# Now create the graph that updates the country name based on hover and showing Years on x-axis and Display value
# of chosen dataframe on y-axis
    Output("line_graph", "figure"),
    Input("sanitation_map", "clickData"),
    Input("dataframe_dropdown", "value"),
    Input("residence_area_type", "value"),
def create_graph(clickData, dataframe_dropdown, residence_area_type):
    if clickData is None:
        country_name = "Kenya"
        country_name = clickData["points"][0]["customdata"]

    # country_name = clickData["points"][0]["customdata"]
    df = check_dropdown(dataframe_dropdown)

    dff = df[df["Country"] == country_name]
    dff = dff[dff["Residence Area Type"] == residence_area_type]

    fig = px.line(dff, x="Year", y="Display Value", markers=True)

    return fig

if __name__ == "__main__":

I followed the advice from one stack overflow member here, and responded since it didn’t help much.

How do I make the line chart display the data for the country clicked, as referenced by clickData event in the def create_graph function?

Hi @sammigachuhi,

the problem seems to be, that you don’t have any customdata in your clickData. Did you try printing the clickdata? Which info does it contain?

You can use this line instead of the line I highlighted in my last post:

 country_name = clickData["points"][0]["location"]

and remove the clickData:

    dcc.Graph(id="sanitation_map" )

You can change from country_name = clickData["points"][0]["customdata"] to country_name = clickData["points"][0]["hovertext"] and it will work.

Recording 2023-07-10 092349

1 Like

Yes, this also works, I also came across a slightly similar solution as shown here: python - Make the line graph update based on the country clicked on the plotly choropleth map - Stack Overflow. A question, how did you know removing clickData from dcc.Graph and instead using ‘location’ for the country_name variable would work since I searched all over the web to know avail?

Yes, you are correct, as I also got to find out here: python - Make the line graph update based on the country clicked on the plotly choropleth map - Stack Overflow. How did you know and where did you find the clues so that next time it will save me several fruitless attempts?

I mentioned it two posts above, just printing the clickData and inspect for the dictionary key which cointains the desired information.

Almost everything in plotly ist stored in a json/dict structure or can be converted into such.

Hello, before finally getting the solution, I followed your advice by printing something like print(country_name) and print(clickData["points"][0]["customdata"] to at least show something in the browser console which I could use to debug but I got back a JavaScript error log that didn’t make sense to me. Would you tell me the appropriate way to do it?