plotly-resampler

repository·main·Indexed 22 days ago

https://github.com/predict-idlab/plotly-resampler

A library for visualizing large time series with Plotly through dynamic resampling. It allows smooth interaction with high-frequency data by rendering only the points relevant to the current view. The library provides FigureResampler for general use and FigureWidgetResampler for Jupyter environments, and supports integration with Dash and Streamlit. It includes features such as EfficientLTTB aggregation, support for multiple y-axes, and high-fidelity data management via hf_x and hf_y.

Tokens
8.5K
Snippets
18
Records
34
Agent score
78%

What's inside plotly-resampler

  1. Difference between plotly-resampler and plain plotly figures

    main

    A plotly-resampler figure is a wrapper around a plain Plotly figure that adds dynamic aggregation to improve scalability for line charts based on the front-end view.

    Enabling Dynamic Aggregation

    Important: Calling .show() on a figure returns a static HTML view with no dynamic aggregation.

    To enable dynamic aggregation, use one of the following methods depending on your environment:

    1. Using FigureResampler: Call .show_dash(). This spawns a Dash web app where dynamic aggregation is handled via Dash callbacks.
    2. Using FigureWidgetResampler (IPython/Jupyter): Output the object directly in a cell via IPython.display. This uses widget-events to perform dynamic aggregation via the running IPython kernel.

    Behavior Changes

    • Autoscale vs Reset Axes: In plotly-resampler, double-clicking within a line-chart area triggers an Autoscale event (updating the y-range to show all data within the current x-range) rather than the vanilla Plotly Reset Axes behavior.
  2. Understand the `~time|number` legend suffix

    main

    When data is aggregated, a tilde suffix (~) appears in the legend name. This indicates the mean aggregation bin size, which represents the average index-range difference between two consecutive aggregated samples.

    • For time-indexed data: It is the mean time-range between 2 consecutive sampled points.
    • For numeric-indexed data: It is the mean numeric range between 2 consecutive sampled points.
    • For range-index: It represents the mean downsample ratio (the mean number of samples aggregated into a single sample).
  3. Compare plotly-resampler and Datashader

    main

    Both libraries handle large datasets, but they use different approaches:

    FeatureDatashaderplotly-resampler
    Output TypeRasterized image/array (grid)Aggregated series (per trace)
    Primary Use CasePoint clouds, many traces, noise reductionTime-series, interactive traces
    InteractivityLimited (viewing an image)High (hover, toggle, hovertext)
    Data ModelGlobal overlay of all tracesIndividual trace manipulation

    Use Datashader when: You need a global view of many traces, want to visualize high-frequency noise patterns, or need to render every single data point as a rasterized image.

    Use plotly-resampler when: You need to interact with specific traces (hovering, toggling), want to use the Plotly interface, or are building scalable Dash apps for time-series data.

  4. How automatic resampling works with register_plotly_resampler

    main

    The automatic approach allows you to use your existing Plotly workflow with minimal code changes. By calling register_plotly_resampler(), all subsequent plotly.graph_objects.Figure or FigureWidget instances will be automatically wrapped into either a FigureResampler or a FigureWidgetResampler based on the mode argument.

    • IPython/Jupyter environments: Automatically uses FigureWidgetResampler.
    • Other environments: Automatically uses FigureResampler.

    To revert this behavior, call unregister_plotly_resampler().

    import plotly.graph_objects as go
    import numpy as np
    from plotly_resampler import register_plotly_resampler, unregister_plotly_resampler
    
    # Call the register function once to wrap all future Figures
    register_plotly_resampler(mode='auto')
    
    x = np.arange(1_000_000)
    noisy_sin = (3 + np.sin(x / 200) + np.random.randn(len(x)) / 10) * x / 1_000
    
    # This Figure will now be a FigureWidgetResampler (in IPython) or FigureResampler
    f = go.Figure()
    f.add_trace({"y": noisy_sin + 2, "name": "yp2"})
    f
  5. Use plotly-resampler in Jupyter Notebooks

    main

    For data scientists working in Jupyter environments, plotly-resampler provides two main ways to interact with data:

    1. Basic Usage: Use the standard FigureResampler for most use cases. The basic_example.ipynb notebook demonstrates advanced features like:

      • Retaining static figures in the notebook.
      • Using an x-axis overview (rangeslider) for navigation.
      • Styling marker color and size.
      • Adjusting trace data at runtime.
      • Adding shaded confidence bounds.
      • Configuring different aggregation algorithms and sample counts.
      • Handling logarithmic x-axes (e.g., using LogLTTB).
      • Using fill_value for gap handling in filled area plots.
      • Using multiple y-axes in subplots.
      • Note: Basic examples require plotly-resampler>=0.9.0rc3.
    2. FigureWidget Usage: Use the FigureWidgetResampler wrapper to create a go.FigureWidget. This is ideal for notebook environments because it enables dynamic aggregation without starting a web application on a port, which is useful for remote work. It also supports using FigureWidget on-click callbacks for large time series annotation.

  6. Integrate plotly-resampler with a Dash app

    main

    To use plotly-resampler within a Dash application, you must register the FigureResampler figure's callbacks with the Dash app instance. This allows the resampler to handle interactive updates (like zooming or panning) by triggering server-side recalculations of the resampled data.

    Steps to integrate:

    1. Construct the figure: Create your resampled figure using FigureResampler(plotly_figure).
    2. Set up the Dash layout: Include a dcc.Graph component in your app layout and assign it a unique id.
    3. Register the callback: Call fig.register_update_graph_callback(app, "graph-id"), where app is your Dash instance and "graph-id" is the ID of the dcc.Graph component.

    !!! warning The basic implementation pattern often uses a global variable for the FigureResampler instance. For production applications, you should implement server-side caching to store the FigureResampler instance per session to avoid issues with concurrency and state management.

    # Construct the to-be resampled figure
    fig = FigureResampler(px.line(...))
    
    # Construct app & its layout
    app = dash.Dash(__name__)
    app.layout = html.Div(children=[dcc.Graph(id="graph-id", figure=fig)])
    
    # Register the callback
    fig.register_update_graph_callback(app, "graph-id")
    
    # start the app
    app.run_server(debug=True)
  7. Optimize performance for datetime indices using `hf_x` and `hf_y`

    main

    Using Pandas or NumPy datetime objects can be significantly slower than Unix epoch timestamps because Plotly's scatter(gl) constructor performs slower serialization for non-numeric (object) arrays.

    To avoid this performance bottleneck, do not pass datetime data directly into the go.Scatter constructor. Instead, pass the high-frequency data using the hf_x and hf_y arguments in the FigureResampler.add_trace (or FigureWidgetResampler.add_trace) method. This allows plotly-resampler to handle the aggregation and only pass the necessary aggregated data to the Plotly object.

    import plotly.graph_objects as go
    import pandas as pd
    import numpy as np
    from plotly_resampler import FigureResampler
    
    # Create the dummy dataframe
    y = np.arange(1_000_000)
    x = pd.date_range(start="2020-01-01", periods=len(y), freq="1s")
    
    # Create the plotly-resampler figure
    fig = FigureResampler()
    # fig.add_trace(go.Scatter(x=x, y=y))  # This is slow
    fig.add_trace(go.Scatter(), hf_x=x, hf_y=y)  # This is fast
  8. Use FigureResampler for Dash or non-Jupyter environments

    main

    For manual control, especially when working with Dash or outside of Jupyter, you can wrap a go.Figure directly with FigureResampler. This approach allows you to use Dash callbacks to provide dynamic aggregation. Use the .show_dash() method on the figure to launch the interface.

    # NOTE: this example works in a notebook environment
    import plotly.graph_objects as go
    import numpy as np
    from plotly_resampler import FigureResampler
    
    x = np.arange(1_000_000)
    sin = (3 + np.sin(x / 200) + np.random.randn(len(x)) / 10) * x / 1_000
    
    fig = FigureResampler(go.Figure())
    fig.add_trace(go.Scattergl(name='noisy sine', showlegend=True), hf_x=x, hf_y=sin)
    
    fig.show_dash(mode='inline')
  9. Integrate plotly-resampler with Dash

    main

    The dash_apps/ directory contains several patterns for integrating plotly-resampler into Dash applications.

    Minimal Patterns

    • Server-side Caching (Recommended): Use server-side caching for the FigureResampler variable to follow best practices (dash_apps/02_minimal_cache.py). Avoid using global variables for FigureResampler (dash_apps/01_minimal_global.py).
    • Dynamic Construction: Use Dash pattern matching callbacks to construct plotly-resampler graphs dynamically based on user interaction (dash_apps/03_minimal_cache_dynamic.py).
    • X-Axis Overviews: Implement a linked x-axis rangeslider using clientside callbacks (dash_apps/04_minimal_cache_overview.py) or for subplots (dash_apps/05_cache_overview_subplots.py).
    • Range Selectors: Combine a linked x-axis overview with a rangeselector and a reset axis button (dash_apps/06_cache_overview_range_buttons.py).

    Advanced Patterns

    • Dynamic Data Generation: Use pattern matching callbacks to remove and reconstruct graphs (e.g., a dynamic sine generator in dash_apps/11_sine_generator.py).
    • File Visualization: Load and visualize multiple .parquet files (dash_apps/12_file_selector.py).
    • Coarse-to-Fine Interaction: Create a dashboard where a coarse, static go.Figure interacts with a dynamic plotly-resampler graph (dash_apps/13_coarse_fine.py).