Skip to content

Commit

Permalink
💪 xaxis overview example
Browse files Browse the repository at this point in the history
  • Loading branch information
jonasvdd committed Oct 22, 2023
1 parent b23b09a commit 12c07fb
Showing 1 changed file with 124 additions and 0 deletions.
124 changes: 124 additions & 0 deletions examples/dash_apps/04_minimal_cache_overview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""Minimal dash app example.
Click on a button, and see a plotly-resampler graph of two sinusoids.
In addition, another graph is shown, which is an overview of the main graph.
This other graph is linked to the main graph, such that when you select a region
in the overview graph, the main graph will zoom in on that region and vice versa.
This example uses the dash-extensions its ServersideOutput functionality to cache
the FigureResampler per user/session on the server side. This way, no global figure
variable is used and shows the best practice of using plotly-resampler within dash-apps.
"""

import numpy as np
import plotly.graph_objects as go
import dash
from dash import Input, Output, State, callback_context, dcc, html, no_update
from dash_extensions.enrich import DashProxy, Serverside, ServersideOutputTransform
from trace_updater import TraceUpdater

# The overview figure requires clientside callbacks, whose JavaScript code is located
# in the assets folder. We need to tell dash where to find this folder.
from plotly_resampler import FigureResampler, ASSETS_FOLDER

# -------------------------------- Data and constants ---------------------------------
# Data that will be used for the plotly-resampler figures
x = np.arange(2_000_000)
noisy_sin = (3 + np.sin(x / 200) + np.random.randn(len(x)) / 10) * x / 1_000

# The ids of the components used in the app (we put them here to avoid typos)
GRAPH_ID = "graph-id"
OVERVIEW_GRAPH_ID = "overview-graph"
STORE_ID = "store"
TRACEUPDATER_ID = "traceupdater"


# --------------------------------------Globals ---------------------------------------
# Remark how the assests folder is passed to the Dash(proxy) application
app = DashProxy(
__name__, transforms=[ServersideOutputTransform()], assets_folder=ASSETS_FOLDER
)

app.layout = html.Div(
[
html.H1("plotly-resampler + dash-extensions", style={"textAlign": "center"}),
html.Button("plot chart", id="plot-button", n_clicks=0),
html.Hr(),
# The graph and its needed components to serialize and update efficiently
# Note: we also add a dcc.Store component, which will be used to link the
# server side cached FigureResampler object
dcc.Graph(id=GRAPH_ID),
dcc.Graph(id=OVERVIEW_GRAPH_ID),
dcc.Loading(dcc.Store(id=STORE_ID)),
TraceUpdater(id=TRACEUPDATER_ID, gdID=GRAPH_ID),
]
)


# ------------------------------------ DASH logic -------------------------------------
# --- construct and store the FigureResampler on the serverside ---
@app.callback(
[
Output(GRAPH_ID, "figure"),
Output(OVERVIEW_GRAPH_ID, "figure"),
Output(STORE_ID, "data"),
],
Input("plot-button", "n_clicks"),
prevent_initial_call=True,
)
def plot_graph(_):
global app
ctx = callback_context
if len(ctx.triggered) and "plot-button" in ctx.triggered[0]["prop_id"]:
fig: FigureResampler = FigureResampler(xaxis_overview=True)

# Figure construction logic
fig.add_trace(go.Scattergl(name="log"), hf_x=x, hf_y=noisy_sin * 0.9999995**x)
fig.add_trace(go.Scattergl(name="exp"), hf_x=x, hf_y=noisy_sin * 1.000002**x)

fig.update_layout(legend=dict(orientation="h", yanchor="bottom", y=1.02))
fig.update_layout(margin=dict(b=10), template="plotly_white")

coarse_fig = fig._create_overview_figure()
return fig, coarse_fig, Serverside(fig)
else:
return no_update


# --- The clientside callbacks used to 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
), # TODO -> look for clean output
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 logic ---
@app.callback(
Output(TRACEUPDATER_ID, "updateData"),
Input(GRAPH_ID, "relayoutData"),
State(STORE_ID, "data"), # The server side cached FigureResampler per session
prevent_initial_call=True,
)
def update_fig(relayoutdata, fig):
if fig is None:
return no_update
return fig.construct_update_data(relayoutdata)


# --------------------------------- Running the app ---------------------------------
if __name__ == "__main__":
app.run_server(debug=False, port=9023, use_reloader=False)

0 comments on commit 12c07fb

Please sign in to comment.