[Help needed] Updating Graph depending on point clicked in Map (callback not working)

Dear Forum Community,

I’m facing some problem with a Dash app, and I don’t know how to solve it. What I want is basically let the user click on markers in a map and depending on the marker he clicked a different graph should be plotted. (Visualizing measurement data of meteorological stations for example).

The Problem is, that my callback, which Output links to the ‘figure’ property of the graph does not seem to work, but not giving any error neither.
Here is some example code, that shows the problem.

import numpy as np
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objs as go
    
mapbox_access_token = "YOUR USER TOKEN"

# example measurement stations
lats = [41.434760, 38.436662]
lons = [-105.925030, -88.962141]
text = ['red', 'blue']

app = dash.Dash()

app.layout = html.Div([
    # map centered to USA
    html.Div([
        dcc.Graph(
            id = "mapbox",
            figure={
                "data": [
                    dict(
                        type = "scattermapbox",
                        lat = lats,
                        lon = lons,
                        mode = "markers",
                        marker = {'size': '14'},
                        text = text
                    )
                ],
                "layout": dict(
                    autosize = True,
                    hovermode = "closest",
                    margin = dict(l = 0, r = 0, t = 0, b = 0),
                    mapbox = dict(
                        accesstoken = mapbox_access_token,
                        bearing = 0,
                        center = dict(lat = 38.30, lon = -90.68),
                        style = "outdoors",
                        pitch = 0,
                        zoom = 3.5,
                        layers = []
                    )   
                )
            },
            style = {"height": "100%"}
        )
    ], style = {"border-style": "solid", "height": "50vh"}),
    # text container
    html.Div([
            html.P(id='station_id',
                   style={'fontSize': '12px'})]),
    # graph container
    html.Div([
            dcc.Graph(id='basic_graph')])

])

@app.callback(
        Output('basic_graph', 'figure'),
        [Input('mapbox', 'clickData')])
def plot_basin(selection):
    if selection is not None:
        x_data = np.linspace(0,500,500)
        y_data = np.random.rand(500)
        
        # depending on the station text use different color for line
        if selection['points'][0]['text'] == 'red':
            color='#ff0000'
        else:
            color='#0000ff'
        data = [go.Scatter(
                    x=x_data,
                    y=y_data,
                    line={'color': color},
                    opacity=0.8,
                    name="Graph"
                )]
        layout = go.Layout(
                    xaxis={'type': 'linear', 'title': "Timestep"},
                    yaxis={'type': 'linear', 'title': "Value"},
                    margin={'l': 60, 'b': 40, 'r': 10, 't': 10},
                    hovermode="False"
                    )
        
        return {'data': data, 'layout': layout}

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

As you will see, nothing will happen, when you click on one of the points. If you comment out the callback function and add the following callback:

@app.callback(
        Output('station_id', 'children'),
        [Input('mapbox', 'clickData')])
def show_name(selection):
    if selection is not None:
        point = selection['points'][0]
        return point['text']

You will see that, when you click on one of the points, the station text will be displayed correctly in the corresponding text container. If I add the figure dictionary I create in the function to the above code, when the container is created, it displays a graph correctly, so this part of the code is also not the problem.
I worked with updating graphs from callbacks before, but never faced this problem and I have simply no clue, why no graph is plotted from the callback.

I would really appreciate any tips.

Edit:
If I add a dropdown menu, in which I can select between two values and link the callback not to the the map and ‘clickData’ but to the dropdown and e.g. ‘value’ the function works without any problems and updates the graph, whenever the dropdown value is changed. I only can’t link map to graph. Map to text container also works as said above…

Edit 2:
I found an error in the developer console of firefox. It is:

TypeError: r is null (bundle.js:26:6472)

Maybe that is helping? Again it seems to be only the combination of map points to graph figure. Which is actually the same as in the Dash example for the oil wells… So it should be possible

Edit 3:

The error message in Chrome is:

Uncaught TypeError: Cannot read property 'data' of null bundle.js:26 at t.value

Edit 4:

Okay I have it working. Changing the callback to:

@app.callback(
        Output('basic_graph', 'figure'),
        [Input('mapbox', 'clickData')])
def plot_basin(selection):
    if selection is None:
        return {}
    else:
        x_data = np.linspace(0,500,500)
        y_data = np.random.rand(500)
        
        # depending on the station text use different color for line
        if selection['points'][0]['text'] == 'red':
            color='#ff0000'
        else:
            color='#0000ff'
        data = [go.Scatter(
                    x=x_data,
                    y=y_data,
                    line={'color': color},
                    opacity=0.8,
                    name="Graph"
                )]
        layout = go.Layout(
                    xaxis={'type': 'linear', 'title': "Timestep"},
                    yaxis={'type': 'linear', 'title': "Value"},
                    margin={'l': 60, 'b': 40, 'r': 10, 't': 10},
                    hovermode="False"
                    )
        
        return {'data': data, 'layout': layout}

let it work. Still pretty unclear to me why ‘if selection is not None’ does not work…since it does in the other callback I posted above, where I display only the text. Is it necessary to always return something, when the output is a dcc.Graph figure?

2 Likes

While perhaps surprising, this makes sense. If you do not explicitly return from a Python function (ie no return statement), it will implicitly return None. So Dash sends this down to the client and the figure prop of the Graph component is set to null, which is the JSON encoded value for None. That explains the error that null does not have a ‘data’.

So yes, I think you do need to return something that is not None. From playing around it looks like this can be a range of things that are not None, but you could be more explicit and return {'data':[]} which makes it clear what’s going on.

1 Like

Thanks Ned,

I thought it must be something like this. I was confused at the beginning, that it works for e.g. text output in the html.P container, but it makes sense that this won’t break the code, while ‘null’ or ‘None’ as figure would. Was just hard to debug, since it didn’t throw any error in the Python/Dash console.

2 Likes

Hello fkratzert. Have you resolved your problem? I am in the same contexte and i want to know if it has worked
and if you can share your code finally.