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?