where some_sophisticated_data depends on the range.
What do I have to use in place of the question marks? I tried ‘xaxis.range’, but that does not fire…
Or do I have to put the slider outside the graph? This would be painful as I’d have to update the graph from the server side which would slow down the web performance (>4000 data points…)
Welcome to the dash forum, and thank you for a good description of you problem. You could make it even better by providing a small standalone example demonstrating the issue
Now, to my knowledge, it is not possible to subscribe to events of the underlying properties of Plotly objects (which seems to be what you are trying to do), but please correct me if i am wrong. Hence to achieve the desired behavior, you would need “something else” to trigger the update. In Dash, that could be an interval component,
import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objects as go
from dash.dependencies import Output, Input, State
from plotly.subplots import make_subplots
figure = make_subplots(rows=2, cols=1, shared_xaxes=True)
figure.update_layout(xaxis=go.layout.XAxis(rangeslider=dict(autorange=True)))
app = dash.Dash(__name__)
app.layout = html.Div([
dcc.Graph(id='my-graph', figure=figure),
dcc.Interval(id='my-interval'),
html.Div(id='my-div')
])
@app.callback(Output('my-div', 'children'),
[Input('my-graph', "figure"), Input('my-interval', 'n_intervals')],
[State('my-div', 'children')])
def update(figure, n_intervals, x_range_state_str):
if figure is None or n_intervals is None:
return dash.no_update
# Check if update is needed.
x_range_value = figure['layout']['xaxis']['range']
x_range_value_str = ("({:3f},{:3f})").format(x_range_value[0], x_range_value[1])
if x_range_value_str == x_range_state_str:
return dash.no_update
# Do the update.
return x_range_value_str
if __name__ == '__main__':
app.run_server()
While the above solution works, an external slider would probably be preferable performancewise (since you avoid the periodic requests invoked by the Interval component).
I managed to find an appropriate property to monitor for the slider: the ‘relayoutData’ event is fired when the slider is moved.
Unfortunately, there seems to be a bug in the event data creation: the data slightly differs between the event fired from moving the graph and moving the handles (or the area) in the rangeslider. So I had to take an additional step to interpret the data in both cases.
Current implementation is
@app.callback(Output('my-table', 'children'),
[Input('my-graph', 'relayoutData')])
def render_bot_summary(relayoutData=dict()):
if 'summary' not in cached_data.keys():
return html.H4(str(cached_data.keys()), style=dict(color='red'))
result = cached_data['result']
summary = cached_data['summary']
firstAsset, secondAsset = (summary['A1'], summary['A2'])
data = cached_data['result']
left = result.index[0]
right = result.index[-1]
if isinstance(relayoutData, dict):
if 'xaxis.range' in relayoutData.keys():
left, right = relayoutData['xaxis.range']
if 'xaxis.range[0]' in relayoutData.keys():
left = relayoutData['xaxis.range[0]']
if 'xaxis.range[1]' in relayoutData.keys():
right = relayoutData['xaxis.range[1]']
left, right = pd.to_datetime(left).round('D'), pd.to_datetime(right).round('D')
df = data[left:right]
I could have used your approach using the figure property as an additional input, that would have saved me some details.
BTW: It seems that it’s really difficult to tie an arbitrary number of graphs to the slider funcitonality: I am using two subplots, but the slider only acts on the first, although the figure has the property shared_xaxes set to True. Let’s see how it works when I separate slider and graph (huh, never dun that before…)
I think the syntax has changed since this was posted(?) In hopes this helps someone else a few hours that I spent, I revised the above slightly and got this to work:
app.layout = html.Div(
id="app-container",
children=[
....
dcc.Store(id='duration_range'),
....
]
)
@app.callback(
Output('duration_range', 'data'),
[Input('duration_histogram', 'relayoutData')])
def display_relayout_data(relIn=dict()):
if isinstance(relIn, dict):
if 'xaxis.range' in relIn.keys():
left, right = relIn["xaxis.range"][0], relIn["xaxis.range"][1]
else: left, right=None, None
else: left, right=None, None
return json.dumps([left, right])