I want to make an interactive map with dash. So I added a Scattermapbox to visualize some data.
To select which data is shown, I added a slider. and used a callback to redraw the map.
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import pandas as pd
import plotly.graph_objects as go
df = pd.DataFrame({'place_no': [1, 1, 1, 2, 2, 2],
'lat': [50.941357, 50.941357, 50.941357, 50.932171, 50.932171, 50.932171],
'lon': [6.957768, 6.957768, 6.957768, 6.964412, 6.964412, 6.964412],
'year': [2017, 2018, 2019, 2017, 2018, 2019],
'value': [20, 40, 60, 80, 60, 40]})
def get_map(df_map):
fig = go.Figure(go.Scattermapbox(
lat=df_map['lat'],
lon=df_map['lon'],
mode='markers',
marker=go.scattermapbox.Marker(
size=df_map['value']
),
))
fig.update_layout(
mapbox_style="open-street-map",
mapbox={'center': go.layout.mapbox.Center(lat=50.936600, lon=6.961497), 'zoom': 11}
)
return fig
app = dash.Dash()
app.layout = html.Div([
dcc.Graph(id='map',
figure=get_map(df[df['year'] == 2017])),
dcc.Slider(id='year-picker',
min=2017,
max=2019,
marks={2017: {'label': 2017}, 2018: {'label': 2018}, 2019: {'label': 2019}}
),
html.Div(id='shown-week', style={'textAlign': 'center'})
], )
#app.callback(
Output(component_id='map', component_property='figure'),
[Input(component_id='year-picker', component_property='value')]
)
def update_map(selected_year):
filtered_df = df[df['year'] == selected_year]
fig = get_map(filtered_df)
return fig
if __name__ == '__main__':
app.run_server()
This worked out fine for now. And I get exact the result I expect.
But if move or zoom the map, and then select new data with the slider, the center and the zoom goes back to the initial values, which is very inconvenient.
Is there any way to get the center and zoom of the current view, so I can add it in the update-layout method?
Or is there any other way to save the current view?
Have you tried the uirevision property? If you set this property to a constant value when you update the figure, i.e.
fig['layout']['uirevision'] = 'some-constant'
the zoom etc. should stay the same. For details, see the documentation.
Related
I'm trying to create a Dash App that has a scatterplot and timeseries, where the user can select data points on either graph and they highlight on both. This is similar to the "Crossfiltering" example in the Dash documentation (https://dash.plotly.com/interactive-graphing) but one key difference is that I'm looking for the union of each graph's selection rather than the intersection.
To further complicate - I have a second callback linked to a dropdown and "next" button with the intent that both can be used to change the underlying dataset used in the graphs by filtering on the "ID" column in the original dataset. I've set it up to store the filtered dataframe as a JSON object in a DCC.Store called "asset_df" that can then be pulled as an input for the callback that updates the graphs.
In its current state:
App loads and plots the dataset for the first ID in the list - this works as intended
Clicking the "Next" button or selecting a difference ID from the dropdown updates both graphs with the new dataset - this works as intended
Selecting data on either graph highlights those points on both graphs - this is where it breaks
It doesn't return a callback error or any error messages, but the graphs don't get updated with the selected points highlighted. I believe part of the issue is that the callback for updating the graphs seems to be firing twice, with the second firing returning a blank dataset for "selectedData" for both graphs.
Questions I'm hoping the community can help me with:
- Am I collecting/storing/recalling the asset_df correctly using the DCC.Store and my callback inputs/outputs?
- Why is the callback linked to the display_selected_data function getting called twice when a selection is made on either plot?
If you see any other issues with the code (I'm a beginner so no doubt there are many) please let me know, especially if they may be contributing to the issue described above.
Thank you!
dataset available here: SampleData
from jupyter_dash import JupyterDash
from dash import Dash, dcc, html, dash_table, ctx
import numpy as np
import pandas as pd
import json
from dash.dependencies import Input, Output
import plotly.express as px
from base64 import b64encode
import io
import collections
df_raw = pd.read_csv(PATH_TO_DATA)
df_raw.set_index('PCTimeStamp', inplace=True, drop=False)
asset_col = "Asset"
asset_list = df_raw[asset_col].unique().tolist()
X2_col = "X2_Variable"
timestamp_col = "PCTimeStamp"
Y1_col = "Y1_Variable"
app = JupyterDash(__name__)
app.layout = html.Div([
html.Button('Next',
id='next_button',
n_clicks=0),
dcc.Dropdown(asset_list,
value=asset_list[0],
id='dropdown'),
dcc.Graph(id='scatter',
config={'displayModeBar': True}),
dcc.Graph(id='timeseries',
config={'displayModeBar': True}),
dcc.Store(id='asset_df')
])
def get_figure(df, x_col, y_col, asset_col, selectedpoints):
if x_col == 'PCTimeStamp':
fig = px.scatter(df, x=df[x_col], y=df[y_col], text=df.index, color=df[asset_col])
fig.update_traces(selectedpoints=selectedpoints,
customdata=df.index, mode='markers+lines',
line_color='grey',
marker={'color': 'grey', 'size': 5},
unselected={'marker': {'opacity': 0.3, 'color': 'grey'}, 'textfont': {'color': 'grey'}},
selected={'marker': {'opacity': 1.0, 'color': 'yellow'}, 'textfont': {'color': 'yellow'}})
elif x_col == X2_col:
fig = px.scatter(df, x=df[x_col], y=df[y_col], text=df.index, color=df[asset_col])
fig.update_traces(selectedpoints=selectedpoints,
customdata=df.index, mode='markers',
marker={'color': 'grey', 'size': 10},
unselected={'marker': {'opacity': 0.3, 'color': 'grey'}, 'textfont': {'color': 'grey'}},
selected={'marker': {'opacity': 1.0, 'color': 'yellow'}, 'textfont': {'color': 'yellow'}})
else:
print("something's wrong...")
fig.update_layout(margin={'l': 20, 'r': 0, 'b': 15, 't': 5}, dragmode='select', hovermode=False)
return fig
#app.callback(
Output('asset_df', 'data'),
Output('next_button', 'n_clicks'),
Output('dropdown','value'),
Input('dropdown', 'value'),
Input('next_button', 'n_clicks'),
prevent_initial_call=False
)
def create_asset_df(value, n_clicks):
starting_pos=0
if "next_button" == ctx.triggered_id:
new_position = n_clicks
n_clicks = n_clicks
elif "dropdown" == ctx.triggered_id:
new_position = asset_list.index(value)
n_clicks = new_position
else:
new_position = starting_pos
n_clicks = 0
df_asset = df_raw[df_raw[asset_col] == asset_list[new_position]]
df_asset = df_asset[[asset_col, X2_col, Y1_col, timestamp_col]]
df_json = df_asset.to_json()
return df_json, n_clicks, asset_list[new_position]
#app.callback(
Output('scatter', 'figure'),
Output('timeseries', 'figure'),
Input('scatter', 'selectedData'),
Input('timeseries', 'selectedData'),
Input('asset_df', 'data'),
prevent_initial_call=False
)
def display_selected_data(selection1, selection2, df_json):
print("selection1:")
print(selection1)
print("selection2:")
print(selection2)
df_asset = pd.read_json(df_json)
print("df_asset:")
print(df_asset)
for selected_data in [selection1, selection2]:
if selected_data and selected_data['points']:
selectedpoints = np.union1d(selectedpoints,
[p['customdata'] for p in selected_data['points']])
print('selectedpoints:')
print(selectedpoints)
fig1 = get_figure(df_asset, X2_col , Y1_col, asset_col, selectedpoints)
fig2 = get_figure(df_asset, timestamp_col, Y1_col, asset_col, selectedpoints)
return fig1,fig2
if __name__ == '__main__':
app.run_server(port=8081,debug=True)
I build a Plotly Dash web app to display sensor data. I want to have a map where I can select the stations and therefore I can see the time series chart.
This is my callback right now:
#app.callback(
Output('time_series1', 'figure'),
Input('map_sensors', 'selectedData'))
def display_selected_data(selectedData):
if selectedData is None: # Plot whole Dataframe if nothing is selected.
fig = px.line(data_frame=df, x='date.utc', y='value', color='location')
return fig
else:
selectedData['points'][0]['customdata'][0] # This line shows me the name of the location and I want to add this to a list
return
I can show the location in the selected data. Now my question is, how can I add this to a list?
My goal ist to filter the dataframe like this dff2 = df[df.location.isin(selected_locations)] so that I only plot the selected locations.
My full app right now:
from jupyter_dash import JupyterDash
import plotly.graph_objs as go
from dash import Dash, dcc, html, Input, Output, State
import pandas as pd
import plotly.express as px
import json
loc = pd.read_csv('location_sensors.csv')
df = pd.read_csv('measurement.csv')
style = {'width': '50%', 'height': '500px', 'float': 'left'}
# Build small example app.
app = dash.Dash(__name__)
styles = {
'pre': {
'border': 'thin lightgrey solid',
'overflowX': 'scroll'
}
}
fig_map = px.scatter_mapbox(loc, lat="lat", lon="lon", hover_name="location",
hover_data={'location':True, 'lat':False, 'lon':False}, zoom=3, height=600,
color='location', mapbox_style="open-street-map")
fig_map.update_layout(clickmode='event+select')
app.layout = html.Div([
dcc.Graph(id='map_sensors', figure=fig_map , className='six columns'),
html.Div([dcc.Graph(
id='time_series1',
style={'height': 400}
),
])
])
#app.callback(
Output('time_series1', 'figure'),
Input('map_sensors', 'selectedData'))
def display_selected_data(selectedData):
if selectedData is None:
fig = px.line(data_frame=df, x='date.utc', y='value', color='location')
return fig
else:
# Here I want to filter the dataframe to the selected locations.
return
if __name__ == '__main__':
app.run_server()
Locations csv data:
lat,lon,location
51.20966,4.43182,BETR801
48.83722,2.3939,FR04014
51.49467,-0.13193,London Westminster
Time series data:
https://github.com/pandas-dev/pandas/blob/master/doc/data/air_quality_long.csv
For your #app.callback decorator, I think you want your input to be clickData instead of selectionData. If you look at the first example in the documentation here, once you click a location on the map and it is greyed out, when you click it again at a later time, clickData will input a dictionary with marker information, while selectionData will input null (this means that dash will have trouble knowing when you click on a point again after it's been greyed out if you use selectionData instead of clickData)
You can then have a dynamic list that changes depending on locations the user selects and deselects. Also a very minor point, but I changed your DataFrame variable name from loc to locs since .loc is a pandas DataFrame method.
from jupyter_dash import JupyterDash
import plotly.graph_objs as go
import dash
from dash import Dash, dcc, html, Input, Output, State
import pandas as pd
import plotly.express as px
import json
locs = pd.read_csv('location_sensors.csv')
df = pd.read_csv('https://raw.githubusercontent.com/pandas-dev/pandas/master/doc/data/air_quality_long.csv')
style = {'width': '50%', 'height': '500px', 'float': 'left'}
# Build small example app.
app = dash.Dash(__name__)
styles = {
'pre': {
'border': 'thin lightgrey solid',
'overflowX': 'scroll'
}
}
fig_map = px.scatter_mapbox(locs, lat="lat", lon="lon", hover_name="location",
hover_data={'location':True, 'lat':False, 'lon':False}, zoom=3, height=600,
color='location', mapbox_style="open-street-map")
fig_map.update_layout(clickmode='event+select')
app.layout = html.Div([
dcc.Graph(id='map_sensors', figure=fig_map , className='six columns'),
html.Div([dcc.Graph(
id='time_series1',
style={'height': 400}
),
])
])
## define a list that will hold the columns of the dataframe
## this will be used to modify the px.line chart
selected_locations = list(locs['location'])
#app.callback(
Output('time_series1', 'figure'),
Input('map_sensors', 'clickData'))
def display_selected_data(clickData):
## when the app initializes
if clickData is None:
fig = px.line(data_frame=df, x='date.utc', y='value', color='location')
## when the user clicks on one of the loc points
else:
selection = clickData['points'][0]['customdata'][0]
if selection in selected_locations:
selected_locations.remove(selection)
else:
selected_locations.append(selection)
fig = px.line(data_frame=df[df.location.isin(selected_locations)], x='date.utc', y='value', color='location')
return fig
if __name__ == '__main__':
app.run_server(debug=True)
New to Plotly Dash.
I’m working through the tutorial on my simple example. Learning how to update a graph when new data is added to the data frame (two data frames in this case) and how to connect this with the dropdown that I have on my dashboard.
I want my graphs to get updated with new data on each page load or page refresh (as I will have only a few updates per day.)
This is the code I'm working on:
import pandas as pd
import plotly.express as px
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = dash.Dash(__name__)
data = [['Blue', 20], ['Red ', 12], ['Green', 33]]
df = pd.DataFrame(data, columns=['Color', 'Number'])
data1 = [['A', 10, 88], ['B ', 50, 45], ['C', 25, 120]]
df1 = pd.DataFrame(data1, columns=['Letter', 'Column1', 'Column2'])
fig = px.bar(df, x=df['Color'], y=df['Number'])
fig1 = px.line(x=df1['Letter'], y=df1['Column1'], color=px.Constant('Column1'),
labels=dict(x='Letter', y='Column1', color='Letter'))
fig1.add_bar(x=df1['Letter'], y=df1['Column2'], name='Letter')
app.layout = html.Div(children=[
html.H1(children='Colors and Letters', style={'text-align': 'center'}),
html.Div(children='Color', style={'text-align': 'center'}),
html.Div([
html.Label(['Choose a graph:'], style={'font-weight': 'bold'}),
dcc.Dropdown(
id='dropdown',
options=[
{'label': 'Colors', 'value': 'graph1'},
{'label': 'Letters', 'value': 'graph2'},
],
value='graph1',
style={"width": "60%"}),
html.Div(dcc.Graph(id='graph')),
]),
])
#app.callback(
Output('graph', 'figure'),
[Input(component_id='dropdown', component_property='value')]
)
def select_graph(value):
if value == 'graph1':
return fig
else:
return fig1
if __name__ == '__main__':
app.run_server(debug=True)
Any help would be greatly appreciated. Thanks in advance.
As documented in the section on live update, you should be able to achieve the desired behaviour by defining a function that creates the layout,
def layout():
return html.Div(...)
and assigning this function as the app layout,
app.layout = layout # note no (), you must assign the function itself, not the layout
I am trying to add points to a predefined figure through a dropdown in Dash. If I select one value I want to add a point in a pair of coordinates, and if I select a different value I want to update the graph in a different pair of coordinates. First of all the function to graph the figure is the following:
import plotly.graph_objects as go
import plotly as py
py.offline.init_notebook_mode(connected = True)
def graficar_rectangulos(fig):
fig.add_trace(go.Scatter(
x=[1.5],
y=[0.75],
mode="text",
))
# Set axes properties
fig.update_xaxes(range=[0, 7], showgrid=False)
fig.update_yaxes(range=[0, 3.5])
# Add shapes
fig.add_shape(
# unfilled Rectangle
type="rect",
x0=1,
y0=1,
x1=2,
y1=3,
line=dict(
color="RoyalBlue",
),
)
fig.update_shapes(dict(xref='x', yref='y'))
return True
After that is where I have the problem and specifically updating the graph. I am not sure which are the parameters that should be returned by the function update graph to update the figure with the scatter plots (I am using Jupyter notebook):
from jupyter_plotly_dash import JupyterDash
import plotly.graph_objects as go
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
app = JupyterDash('SimpleExample')
fig = go.Figure()
graficar_rectangulos(fig)
app.layout = html.Div([
dcc.Dropdown(
id='dropdown',
options=[
{'label': 'A', 'value': 'A'},
{'label': 'B', 'value': 'B'},
{'label': 'C', 'value': 'C'}
],
value='NYC'
),
dcc.Graph(id='graph-court', figure = fig)
]
)
#app.callback(
Output('graph-court', 'figure'),
[Input('dropdown', 'value')])
def update_figure(selected_value):
if selected_value=='A':
x,y=3,3
else:
x,y=2,2
return add_trace(go.Scatter(x=x,y=y,marker = dict(size=[15], color=['green']), mode='markers'))
app
How does the function update_figure should work?
import dash
import plotly as py
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.graph_objects as go
from jupyter_plotly_dash import JupyterDash
py.offline.init_notebook_mode(connected = True)
app = JupyterDash('SimpleExample')
app.layout = html.Div([
dcc.Dropdown(id='dropdown', options=[
{'label': 'A', 'value': 'A'},
{'label': 'B', 'value': 'B'}],
value = 'A'),
dcc.Graph(id='graph-court')
])
def create_figure():
layout = dict(xaxis=dict(range=[0, 7], showgrid=False), yaxis=dict(range=[0, 3.5]), showlegend=False,
shapes=[dict(type='rect', x0=1, y0=1, x1=2, y1=3, line=dict(color='RoyalBlue'))])
data = go.Scatter(x=[1.5], y=[0.75], text='Some text', mode='text')
fig = go.Figure(data=data, layout=layout)
return fig
#app.callback(Output('graph-court', 'figure'),
[Input('dropdown', 'value')])
def update_figure(selected_value):
if selected_value == 'A':
x, y = 3, 3
else:
x, y = 2, 2
fig = create_figure()
fig.add_trace(go.Scatter(x=[x], y=[y], marker=dict(size=15, color='green'), mode='markers'))
return fig
app
I'm currently working on my first web-application. I recently started following tutorials on python and on some modules. Most of the questions I have I'm able to figure by looking them up, exceptfor this one. I can't figured it out.
Goal is as follows:
- I'm trying to show a graph with a line-chart which dynamically updates by the user input. The user can choose multiple items from a multiple value dropdown.
If I add multiple 'go.Scatters' I can add multiple lines to the graph, except then the trace in the graph is static. So, I tried to write a for loop in go.Scatter that adds more go.Scatters for each item in data_name. Each time I try I got syntax erros. I tried to add values to the data list in dcc.Graph. Each time i failed.
The questions I've are the following:
- Where can i add the for loop to add traces to the data list?
- How should the for loop be structed?
I'm just a beginner so every advice is welcome :-)
Thanks in advance.
If i'm not clear enough on the question, please let me know.
My code is the following:
import dash
import dash_core_components as dcc
import dash_html_components as html
from pandas_datareader.data import DataReader
import time
from collections import deque
import plotly.graph_objs as go
import random
app = dash.Dash('vehicle-data')
max_length = 50
#times = deque(maxlen=max_length)
times = [1,2,3,4,5,6,7,8,9,10,11,12]
oil_temps = [11,12,13,14,15,16,17,18,19]
intake_temps = [11,12,13,14,15,16,17,18,19]
coolant_temps = [11,12,13,14,15,16,17,18,19]
#rpms = deque(maxlen=max_length)
#speeds = deque(maxlen=max_length)
#throttle_pos = deque(maxlen=max_length)
data_dict = {"NH Utrecht":oil_temps,
"NH Amsterdam": intake_temps,
"NH Schiller": coolant_temps
#"NH Sparrenhorst":rpms,
#"Amsterdam":speeds,
#"Overig":throttle_pos
}
app.layout = html.Div([
html.Div([
html.H2('Hotel',
style={'float': 'left',
}),
]),
dcc.Dropdown(id='vehicle-data-name',
options=[{'label': s, 'value': s}
for s in data_dict.keys()],
value=['NH Utrecht'],
multi=True
),
html.Div(children=html.Div(id='graphs'), className='row'),
dcc.Interval(
id='graph-update',
interval=100),
], className="container",style={'width':'98%','margin-left':10,'margin-right':10,'max-width':50000})
#app.callback(
dash.dependencies.Output('graphs','children'),
[dash.dependencies.Input('vehicle-data-name', 'value')],
events=[dash.dependencies.Event('graph-update', 'interval')]
)
def update_graph(data_names):
graphs = []
#update_obd_values(times, oil_temps, intake_temps, coolant_temps, rpms, speeds, throttle_pos)
if len(data_names)>2:
class_choice = 'col s12 m6 l4'
elif len(data_names) == 2:
class_choice = 'col s12 m6 l6'
else:
class_choice = 'col s12'
html.Div(children=
graphs.append(dcc.Graph(
figure=go.Figure(
data = [
go.Scatter(
x=times,
y=[16, 13, 10, 11, 28, 37, 43, 55, 56, 88, 105, 156],
name='Hotel Okura',
marker=go.Marker(
color='rgb(55, 83, 109)'
),
type='scatter',
connectgaps=True
),
],
layout=go.Layout(
title='Hotel comparison data',
showlegend=True,
legend=go.Legend(
x=1.0,
y=1.0
),
margin=go.Margin(l=40, r=0, t=40, b=30)
),
),
style={'height': 300},
id='my-graph'
)
))
return graphs
external_css = ["https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/css/materialize.min.css"]
for css in external_css:
app.css.append_css({"external_url": css})
external_js = ['https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js']
for js in external_css:
app.scripts.append_script({'external_url': js})
if __name__ == '__main__':
app.run_server(debug=True)
The for loop I used is the following (which obviously doesn't work because i try to add a graph in stead of a data time. But data.append doesn't work):
for data_name in data_names:
graph.append(go.Scatter(
x=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
name='Voorbeeld',
marker=go.Marker(
color='rgb(255, 153, 0)'
),
type='scatter'
))