|
import dash |
|
import dash_core_components as dcc |
|
import dash_html_components as html |
|
import datetime |
|
import random |
|
from dash.dependencies import Output, Input, State |
|
|
|
|
|
app = dash.Dash() |
|
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'}) # noqa: E501 |
|
|
|
graph_names = ["foo", "bar", "baz"] |
|
|
|
|
|
def random_points(length): |
|
return [random.randint(5, 10) for i in range(length)] |
|
|
|
|
|
graph_figures = [{'data': [{'x': [0, 1, 2], 'y': random_points(3), 'type': 'bar', 'name': '{} widgets'.format(name)}, |
|
{'x': [0, 1, 2], 'y': random_points(3), 'type': 'bar', 'name': '{} trinkets'.format(name)}], |
|
'layout': {'title': name}} for name in graph_names] |
|
|
|
pre_style = {"backgroundColor": "#ddd", "fontSize": 20, "padding": "10px", "margin": "10px"} |
|
hidden_style = {"display": "none"} |
|
hidden_inputs = html.Div(id="hidden-inputs", style=hidden_style, children=[]) |
|
|
|
app.layout = html.Div(children=[ |
|
dcc.Graph(id='foo', figure=graph_figures[0], className="four columns"), |
|
dcc.Graph(id='bar', figure=graph_figures[1], className="four columns"), |
|
dcc.Graph(id='baz', figure=graph_figures[2], className="four columns"), |
|
html.Label('Most recent clickdata'), |
|
html.Pre(id='update-on-click-data', style=pre_style), |
|
html.Label('Bar and Baz, but not Foo'), |
|
html.Pre(id='update-on-click-data-bar-baz', style=pre_style), |
|
html.Label('Foo and Bar, but not Baz'), |
|
html.Pre(id='update-on-click-data-foo-bar', style=pre_style), |
|
hidden_inputs |
|
]) |
|
|
|
|
|
def last_clicked(*dash_input_keys): |
|
""" Get the clickData of the most recently clicked graph in a list of graphs. |
|
|
|
The `value` you will receive as a parameter in your callback will be a dict. The keys you will want to |
|
pay attention to are: |
|
- "last_clicked": the id of the graph that was last clicked |
|
- "last_clicked_data": what clickData would usually return |
|
|
|
This function working depends on a `hidden_inputs` variable existing in the global / file scope. It should be an |
|
html.Div() input with styles applied to be hidden ({"display": "none"}). |
|
|
|
but why, I hear you ask? |
|
clickData does not get set back to None after you've used it. That means that if a callback needs the latest |
|
clickData from two different potential clickData sources, if it uses them both, it will get two sets of clickData |
|
and no indication which was the most recent. |
|
|
|
:type dash_input_keys: list of strings representing dash components |
|
:return: dash.dependencies.Input() to watch value of |
|
""" |
|
dash_input_keys = sorted(list(dash_input_keys)) |
|
str_repr = str(dash_input_keys) |
|
last_clicked_id = str_repr + "_last-clicked" |
|
existing_child = None |
|
for child in hidden_inputs.children: |
|
if child.id == str_repr: |
|
existing_child = child |
|
break |
|
|
|
if existing_child: |
|
return dash.dependencies.Input(last_clicked_id, 'value') |
|
|
|
# If we get to here, this is the first time calling this function with these inputs, so we need to do some setup |
|
# make feeder input/outputs that will store the last time a graph was clicked in addition to it's clickdata |
|
if existing_child is None: |
|
existing_child = html.Div(id=str_repr, children=[]) |
|
hidden_inputs.children.append(existing_child) |
|
|
|
input_clicktime_trackers = [str_repr + key + "_clicktime" for key in dash_input_keys] |
|
existing_child.children.append(dcc.Input(id=last_clicked_id, style=hidden_style, value=None)) |
|
for hidden_input_key in input_clicktime_trackers: |
|
existing_child.children.append(dcc.Input(id=hidden_input_key, style=hidden_style, value=None)) |
|
|
|
# set up simple callbacks that just append the time of click to clickData |
|
for graph_key, clicktime_out_key in zip(dash_input_keys, input_clicktime_trackers): |
|
@app.callback(dash.dependencies.Output(clicktime_out_key, 'value'), |
|
[dash.dependencies.Input(graph_key, 'clickData')], |
|
[dash.dependencies.State(graph_key, 'id')]) |
|
def update_clicktime(clickdata, graph_id): |
|
result = { |
|
"click_time": datetime.datetime.now().timestamp(), |
|
"click_data": clickdata, |
|
"id": graph_id |
|
} |
|
return result |
|
|
|
cb_output = dash.dependencies.Output(last_clicked_id, 'value') |
|
cb_inputs = [dash.dependencies.Input(clicktime_out_key, 'value') for clicktime_out_key in input_clicktime_trackers] |
|
cb_current_state = dash.dependencies.State(last_clicked_id, 'value') |
|
|
|
# use the outputs generated in the callbacks above _instead_ of clickData |
|
@app.callback(cb_output, cb_inputs, [cb_current_state]) |
|
def last_clicked_callback(*inputs_and_state): |
|
clicktime_inputs = inputs_and_state[:-1] |
|
last_state = inputs_and_state[-1] |
|
if last_state is None: |
|
last_state = { |
|
"last_clicked": None, |
|
"last_clicked_data": None, |
|
} |
|
else: |
|
largest_clicktime = -1 |
|
largest_clicktime_input = None |
|
for clicktime_input in clicktime_inputs: |
|
click_time = int(clicktime_input['click_time']) |
|
if clicktime_input['click_data'] and click_time > largest_clicktime: |
|
largest_clicktime_input = clicktime_input |
|
largest_clicktime = click_time |
|
if largest_clicktime: |
|
last_state['last_clicked'] = largest_clicktime_input["id"] |
|
last_state['last_clicked_data'] = largest_clicktime_input["click_data"] |
|
return last_state |
|
|
|
return dash.dependencies.Input(last_clicked_id, 'value') |
|
|
|
|
|
@app.callback(dash.dependencies.Output('update-on-click-data', 'children'), |
|
[last_clicked('bar', 'foo', 'baz')]) |
|
def foobarbaz_last_clicked_callback(last_clickdata): |
|
click_data = last_clickdata["last_clicked_data"] |
|
clicked_id = last_clickdata["last_clicked"] |
|
return "{} was last clicked and contains clickdata:\n{}".format(clicked_id, click_data) |
|
|
|
|
|
@app.callback(dash.dependencies.Output('update-on-click-data-bar-baz', 'children'), |
|
[last_clicked('bar', 'baz')]) |
|
def barbaz_last_clicked_callback(last_clickdata): |
|
click_data = last_clickdata["last_clicked_data"] |
|
clicked_id = last_clickdata["last_clicked"] |
|
return "{} was last clicked and contains clickdata:\n{}".format(clicked_id, click_data) |
|
|
|
|
|
@app.callback(dash.dependencies.Output('update-on-click-data-foo-bar', 'children'), |
|
[last_clicked('foo', 'bar')]) |
|
def foobar_last_clicked_callback(last_clickdata): |
|
click_data = last_clickdata["last_clicked_data"] |
|
clicked_id = last_clickdata["last_clicked"] |
|
return "{} was last clicked and contains clickdata:\n{}".format(clicked_id, click_data) |
|
|
|
|
|
if __name__ == '__main__': |
|
app.run_server(host='0.0.0.0', debug=True) |
This code helped a lot. Thanks for sharing