Skip to content

Instantly share code, notes, and snippets.

@kopp
Created February 11, 2021 22:11
Show Gist options
  • Save kopp/e2a434dafd567872d00969fe57354a8f to your computer and use it in GitHub Desktop.
Save kopp/e2a434dafd567872d00969fe57354a8f to your computer and use it in GitHub Desktop.
Dash App with plotted map and some user interaction
# %%
# Application that displays data on a map and allows to modify the data in a
# callback triggered by a button.
#
# Note: This application must not be executed using multiple workers, but only
# single-threaded (i.e. run with `python <name>`).
# See https://dash.plotly.com/sharing-data-between-callbacks for the reason.
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
import plotly.graph_objects as go
import pandas as pd
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
styles = {
'pre': {
'border': 'thin lightgrey solid',
'overflowX': 'scroll'
}
}
data = pd.DataFrame([(1, 45, -73, 0), (2, 44, -72, 0), (3, 45, -72, 1), (4, 44, -74, 1)], columns=["id", "lat", "lon", "chunk"])
def make_figure_for_data(data):
fig = go.Figure()
fig.add_trace(go.Scattermapbox(
lat=data["lat"],
lon=data["lon"],
customdata=data["id"],
mode='markers',
marker=go.scattermapbox.Marker(
size=12
),
))
fig.update_layout(
hovermode='closest',
clickmode='event+select',
mapbox=dict(
style="open-street-map", # or "carto-positron", "carto-darkmatter", "stamen-terrain", "stamen-toner" or "stamen-watercolor"
bearing=0,
center=go.layout.mapbox.Center(
lat=45,
lon=-73
),
pitch=0,
zoom=5
)
)
fig.update_traces(marker_size=20)
return fig
# %%
# make_figure_for_data(data).show()
# %% UI Elements of the App
app.layout = html.Div([
dcc.Graph(
id='figure',
figure=make_figure_for_data(data),
),
html.Div([
dcc.Markdown("""
**Selected Points:**
Choose the lasso or rectangle tool in the graph's menu
bar and then select points in the graph or hold down the shift
button while clicking.
"""
),
]),
html.Div([
html.Ul(id='selected-data'),
], className='three columns'),
html.Div([
html.Button("Use the Selection", id='use-selection'),
html.P(id='placeholder'),
]),
])
def get_point_ids_from_selected_points(selectedData):
if selectedData is None:
return []
selected_ids = []
for point in selectedData.get("points", []):
selected_ids.append(point.get("customdata", -1))
return selected_ids
@app.callback(
Output('selected-data', 'children'),
[Input('figure', 'selectedData')])
def display_selected_data(selectedData: dict):
selected_ids = get_point_ids_from_selected_points(selectedData)
lis = list(map(lambda id: html.Li(f"point {id}"), selected_ids))
return lis
@app.callback(
[Output('use-selection', 'n_clicks'), Output("figure", "figure")],
[Input('use-selection', "n_clicks")],
[State("figure", "selectedData"), State("figure", "figure")],
)
def process_selected_data(click_count, selected_data, figure):
# Since we have both selection and button as input, the callback
# is called when the selection changes (and button is not pressed).
# To distinguish those: Check the click count and re-set that to 0
# at the end of the callback.
# Note: when using a State for the selection, this is not necessary any more.
button_was_clicked = click_count is not None and click_count > 0
if button_was_clicked:
selected_ids = get_point_ids_from_selected_points(selected_data)
global data
new_common_chunk = data.loc[data["id"].isin(selected_ids), "chunk"].min()
for id in selected_ids:
data.loc[data["id"] == id, "chunk"] = new_common_chunk
# add some visible modifications
data.loc[data["id"] == id, "lat"] -= 0.5
# update the figure's data
# keep the rest of the state as is
figure["data"][0].update(
lat=data["lat"],
lon=data["lon"],
customdata=data["id"],
)
return 0, figure
# %%
if __name__ == '__main__':
app.run_server(debug=True)
# %%
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment