Open Joakimden4 opened 7 months ago
Hi @Joakimden4,
Can you verify whether the functionality (and demo posted on #286 resolves your issue)?
Hi @jonasvdd,
The provided example works when it is not combined with dash components. However, when I wrap the figure in a dcc.Graph component and display it in a dash, it behaves the exact same way as originally reported in this bug.
Am I implementing it wrongly within the dash framework? I ran this code on the bug/rangeselector branch using the dependencies specified by you:
import dash
from dash import html, dcc
from plotly_resampler import register_plotly_resampler
import plotly.graph_objects as go
import pandas as pd
register_plotly_resampler(mode="figure", create_overview=True, verbose=True)
df_hourly = pd.read_csv("data.csv", index_col=0)
hourly_fig = go.Figure(
layout=dict(
dragmode='pan',
hovermode='x unified',
xaxis=dict(
rangeslider_visible=True,
rangeselector=dict(
buttons=list([
dict(count=1, label="1 day", step="day", stepmode="backward"),
dict(count=1, label="1 month", step="month", stepmode="backward"),
dict(count=1, label="1 year", step="year", stepmode="backward"),
dict(step="all")
])
),
),
),
)
hourly_fig.add_trace(go.Scattergl(x=df_hourly['Date'], y=df_hourly['MWh'], name='Hourly position', mode='lines'))
app = dash.Dash(__name__, meta_tags=[{'name':'viewport', 'content':'width=device-width, initial-scale=1.0'}])
app.layout = html.Div(
dcc.Graph(
id='graph_bo_hourly',
figure=hourly_fig
)
)
app.run(debug=False, host='localhost')
Hi @Joakimden4,
When you use plotly-resampler from the main-branch, the dash app example below appears to work: What did I change / how did I make this:
register_plotly_resampler
within dash apps, the function is mainly intended for usage within notebook environmentsxaxis overview
dash app folder / xaxis overview fileimport dash
from dash import html, dcc, Input, Output, State, no_update
import plotly.graph_objects as go
import pandas as pd
# For plain dash apps you need to use the FigureResampler class
# (the register function is for notebooks only)
from plotly_resampler import FigureResampler, ASSETS_FOLDER
FigureResampler(create_overview=True, verbose=True)
GRAPH_ID = "graph-id"
OVERVIEW_GRAPH_ID = "overview-graph"
# 0. Load the data
df_hourly = pd.read_csv("data.csv", index_col=0)
# 1. Create the figure and add data
# fmt: off
hourly_fig = FigureResampler(
go.Figure(
layout=dict(
dragmode="pan",
hovermode="x unified",
xaxis=dict(
rangeselector=dict(
buttons=list( [
dict(count=1, label="1 day", step="day", stepmode="backward"),
dict(count=1, label="1 month", step="month", stepmode="backward"),
dict(count=1, label="1 year", step="year", stepmode="backward",),
])
),
),
)
),
)
hourly_fig.add_trace(go.Scattergl(x=df_hourly["Date"], y=df_hourly["MWh"], name="Hourly position", mode="lines"))
# 1.1 Create the overview figure
coarse_fig = hourly_fig._create_overview_figure()
# Create the app in which the figure will be displayed
app = dash.Dash(
__name__,
meta_tags=[
{"name": "viewport", "content": "width=device-width, initial-scale=1.0"}
],
assets_folder=ASSETS_FOLDER,
external_scripts=["https://cdn.jsdelivr.net/npm/lodash/lodash.min.js"],
)
# NOTE: you need to create both a coars and
app.layout = html.Div(
children=[
dcc.Graph(id=GRAPH_ID, figure=hourly_fig),
dcc.Graph(id=OVERVIEW_GRAPH_ID, figure=coarse_fig),
]
)
# -------------------- Callbacks --------------------
# --- Clientside callbacks used to bidirectionally link the overview and main graph ---
app.clientside_callback(
dash.ClientsideFunction(namespace="clientside", function_name="main_to_coarse"),
dash.Output(OVERVIEW_GRAPH_ID, "id", allow_duplicate=True),
dash.Input(GRAPH_ID, "relayoutData"),
[dash.State(OVERVIEW_GRAPH_ID, "id"), dash.State(GRAPH_ID, "id")],
prevent_initial_call=True,
)
app.clientside_callback(
dash.ClientsideFunction(namespace="clientside", function_name="coarse_to_main"),
dash.Output(GRAPH_ID, "id", allow_duplicate=True),
dash.Input(OVERVIEW_GRAPH_ID, "selectedData"),
[dash.State(GRAPH_ID, "id"), dash.State(OVERVIEW_GRAPH_ID, "id")],
prevent_initial_call=True,
)
# --- FigureResampler update callback ---
# The plotly-resampler callback to update the graph after a relayout event (= zoom/pan)
# As we use the figure again as output, we need to set: allow_duplicate=True
@app.callback(
Output(GRAPH_ID, "figure", allow_duplicate=True),
Input(GRAPH_ID, "relayoutData"),
prevent_initial_call=True,
)
def update_fig(relayoutdata: dict):
if relayoutdata is None:
return no_update
return hourly_fig.construct_update_data_patch(relayoutdata)
# Start the app
app.run(debug=False, host="localhost")
I hope this helps you further. Kind regards, Jonas
Hi @jonasvdd, Thank you very much for the provided example. I'd love to try this out in my actual project that uses plotly-resampler as dependency. When are you planning on releasing this?
Hi @Joakimden4,
I plan to release a new version somewhere this week.
( I just want to improve the docs of the register_plotly_resampler
function)
Kind regards, Jonas
Hi @jonasvdd,
That's awesome, thanks a lot for your hard work! :)
Version 0.9.2 was released!
Please let me know whether your code works, and if so, you can close this issue! :)
Hi @jonasvdd,
I've updated to version 0.9.2, but I'm still not able to get it to work.
The issue is that no matter what I try to pass to the construct_update_data_patch
method, it returns a dash.no_update event.
It always executes line 1333 of figure_resampler_interface.py.
My use case is to pass the figure attribute of the dcc.Graph element through a callback and then apply the resampling. The reason for this is that the user can change the data for the figure during runtime of the application, so I cannot set a static figure. However, even if I try rebuilding the figure from scratch within the callback to avoid potential dash callback passing format issues, I still end up with the dash.no_update event.
I've tried with a super simple use case where there is no coarse graph. If you have any ideas how to solve this it would be much appreciated!
@callback(
Output('graph_bo_hourly', 'figure', allow_duplicate=True),
Input('graph_bo_hourly', 'relayoutData'),
State('graph_bo_hourly', 'figure'),
prevent_initial_call=True
)
def resample_fig(relayoutdata, fig_state):
if relayoutdata is None:
return dash.no_update
else:
hourly_fig = FigureResampler(
go.Figure(
layout=dict(
dragmode='pan',
hovermode='x unified',
xaxis=dict(
rangeselector=dict(
buttons=list([
dict(count=1, label="1 day", step="day", stepmode="backward"),
dict(count=1, label="1 month", step="month", stepmode="backward"),
dict(count=1, label="1 year", step="year", stepmode="backward"),
])
),
),
),
)
)
x_dates = [datetime.strptime(date, "%Y-%m-%dT%H:%M:%S") for date in fig_state['data'][0]['x']]
hourly_fig.add_trace(go.Scattergl(x=x_dates, y=fig_state['data'][0]['y'], name='Hourly position', mode='lines'))
return hourly_fig.construct_update_data_patch(relayoutdata)
Hi @jonasvdd, I just realized that I'm continually trying to resample the downsampled dataset, which explains why it's not working. I will have to rethink my whole approach.
I'll let you know when I've verified whether it's working, once I've fixed my approach.
Hi! First off, thanks a lot for your incredible work on this much-needed resampling functionality! Also, I'm quite inexperienced with submitting github issues, so please let me know if you need any additional information.
I am trying to apply resampling in a plotly dash app on a go.Scattergl trace that visualizes a time series of around 200k data points with hourly frequency. The app works fine, but when I zoom in on the graph, the granularity of the data remains very low (e.g. weekly granularity). I suspect it is because the Y-values in my dataset are identical over many subsequent hours, thus in theory not requiring resampling. However, I would like the user to be able to hover and click the data points, as this graph is part of a bigger interactive app, which initializes different visuals when the user clicks the data points in the graph.
So in essence I have two questions: 1) Can you confirm that the missing resampling when zooming in is due to the Y-values being identical over many subsequent hours? 2) If yes, is there a way to force the resampling, so that the graph shows interactable data points on a higher granularity despite identical Y-values?
Below is the code pertaining to the graph in question within the app. I have also attached my dataset here data.csv
Thanks in advance!