I have a Dash
app where the user is expected to use the click feature to enter data into multiple drop down and text menus via maps. I was wondering if it is possible to utilize client callback to avoid page refresh to speed up the application while still preserving the same behavior.
I am sharing a MWE with a single map and a single drop down menu. I do not have any experience with client callbacks. I was wondering if someone could provide help here with which I can make the required updates in my original code.
import random, json
import dash
from dash import dcc, html, Dash, callback, Output, Input, State
import dash_leaflet as dl
import geopandas as gpd
from dash import dash_table
#https://gist.github.com/incubated-geek-cc/5da3adbb2a1602abd8cf18d91016d451?short_path=2de7e44
us_states_gdf = gpd.read_file("us_states.geojson")
us_states_geojson = json.loads(us_states_gdf.to_json())
app = Dash(__name__)
app.layout = html.Div([
dl.Map([
dl.TileLayer(url="http://tile.stamen.com/toner-lite/{z}/{x}/{y}.png"),
dl.GeoJSON(data=us_states_geojson, id="state-layer")],
style={'width': '100%', 'height': '250px'},
id="map",
center=[39.8283, -98.5795],
),
html.Div(id='state-container', children=[]), #
dash_table.DataTable(id='state-table', columns=[{"name": i, "id": i} for i in ["state"]], data=[])
])
@app.callback(
Output('state-table', 'data'),
Output('state-container', 'children'),
Input('state-layer', 'click_feature'),
State('state-table', 'data')
)
def update_options(click_feature, current_data):
if click_feature is None:
raise dash.exceptions.PreventUpdate
else:
state = click_feature['properties']['NAME']
if not any(d['state'] == state for d in current_data):
current_data.append({'state': state})
return current_data, f"Clicked: {state}"
if __name__ == '__main__':
app.run_server(debug=True)
Indeed, you can change your callback to be client-side, and thus speed up the interaction.
You have multiple ways to write client-side callbacks, as explained in the documentation. I would recommend using a separate .js
file in the assets/
folder, but here is a quick example of writing the JS function in a python string. You should get the same interaction as before, without the client <-> server back and forth.
app.clientside_callback(
"""
function(clickFeature, currentData) {
if(!clickFeature){
return window.dash_clientside.no_update
}
const state = clickFeature.properties.NAME
const currentStates = currentData.map(item => item.state)
let newData = []
if(!currentStates.includes(state)){
newData = [...currentData, {"state": state}]
}else{
newData = currentData
}
const stateText = `Clicked: ${state}`
return [newData, stateText]
}
""",
Output("state-table", "data"),
Output("state-container", "children"),
Input("state-layer", "click_feature"),
State("state-table", "data"),
)