Skip to content

Commit

Permalink
Merge pull request #47 from predict-idlab/figurewidget
Browse files Browse the repository at this point in the history
🌾 figurewidget support
  • Loading branch information
jonasvdd authored May 6, 2022
2 parents 8810bd5 + 2ad0752 commit 1e80f4e
Show file tree
Hide file tree
Showing 16 changed files with 3,349 additions and 413 deletions.
47 changes: 35 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
[![support-version](https://img.shields.io/pypi/pyversions/plotly-resampler)](https://img.shields.io/pypi/pyversions/plotly-resampler)
[![codecov](https://img.shields.io/codecov/c/github/predict-idlab/plotly-resampler?logo=codecov)](https://codecov.io/gh/predict-idlab/plotly-resampler)
[![Code quality](https://img.shields.io/lgtm/grade/python/github/predict-idlab/plotly-resampler?label=code%20quality&logo=lgtm)](https://lgtm.com/projects/g/predict-idlab/plotly-resampler/context:python)
[![Downloads](https://pepy.tech/badge/plotly-resampler)](https://pepy.tech/project/plotly-resampler)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?)](http://makeapullrequest.com)
[![Documentation](https://github.com/predict-idlab/plotly-resampler/actions/workflows/deploy-docs.yml/badge.svg)](https://github.com/predict-idlab/plotly-resampler/actions/workflows/deploy-docs.yml)
[![Testing](https://github.com/predict-idlab/plotly-resampler/actions/workflows/test.yml/badge.svg)](https://github.com/predict-idlab/plotly-resampler/actions/workflows/test.yml)
Expand All @@ -17,7 +18,7 @@

> `plotly_resampler`: visualize large sequential data by **adding resampling functionality to Plotly figures**
[Plotly](https://github.com/plotly/plotly.py) is an awesome interactive visualization library, however it can get pretty slow when a lot of data points are visualized (100 000+ datapoints). This library solves this by downsampling the data respective to the view and then plotting the downsampled points. When you interact with the plot (panning, zooming, ...), [dash](https://github.com/plotly/dash) callbacks are used to resample and redraw the figures.
[Plotly](https://github.com/plotly/plotly.py) is an awesome interactive visualization library, however it can get pretty slow when a lot of data points are visualized (100 000+ datapoints). This library solves this by downsampling (aggregating) the data respective to the view and then plotting the aggregated points. When you interact with the plot (panning, zooming, ...), callbacks are used to aggregate data and update the figure.

<p align="center">
<a href="#readme">
Expand All @@ -27,6 +28,10 @@

In [this Plotly-Resampler demo](https://github.com/predict-idlab/plotly-resampler/blob/main/examples/basic_example.ipynb) over `110,000,000` data points are visualized!

<!-- These dynamic aggregation callbacks are realized with: -->
<!-- * [Dash](https://github.com/plotly/dash) when a `go.Figure` object is wrapped with dynamic aggregation functionality, see example ⬆️. -->
<!-- * The [FigureWidget.layout.on_change](https://plotly.com/python-api-reference/generated/plotly.html?highlight=on_change#plotly.basedatatypes.BasePlotlyType.on_change) method, when a `go.FigureWidget` is used within a `.ipynb` environment. -->

<!-- #### Useful links -->

<!-- - [Documentation]() work in progress 🚧 -->
Expand All @@ -41,48 +46,66 @@ In [this Plotly-Resampler demo](https://github.com/predict-idlab/plotly-resample

## Usage

To **add dynamic resampling to your plotly Figure**, you should;
1. wrap the plotly Figure with `FigureResampler`
2. call `.show_dash()` on the Figure
To **add dynamic resampling** to your plotly Figure
* using a web application with *Dash* callbacks, you should;
1. wrap the plotly Figure with `FigureResampler`
2. call `.show_dash()` on the Figure
* within a *jupyter* environment and *without creating a web application*, you should:
1. wrap the plotly Figure with `FigureWidgetResampler`
2. output the `FigureWidgetResampler` instance in a cell

> **Note**:
> Any plotly Figure can be wrapped with FigureResampler! 🎉
> Any plotly Figure can be wrapped with `FigureResampler` and `FigureWidgetResampler`! 🎉
> But, (obviously) only the scatter traces will be resampled.
> **Tip** 💡:
> For significant faster initial loading of the Figure, we advise to wrap the constructor of the plotly Figure with `FigureResampler` and add the trace data as `hf_x` and `hf_y`
> For significant faster initial loading of the Figure, we advise to wrap the constructor of the plotly Figure and add the trace data as `hf_x` and `hf_y`
### Minimal example

```python
import plotly.graph_objects as go; import numpy as np
from plotly_resampler import FigureResampler
from plotly_resampler import FigureResampler, FigureWidgetResampler

x = np.arange(1_000_000)
noisy_sin = (3 + np.sin(x / 200) + np.random.randn(len(x)) / 10) * x / 1_000

# OPTION 1 - FigureResampler: dynamic aggregation via a Dash web-app
fig = FigureResampler(go.Figure())
fig.add_trace(go.Scattergl(name='noisy sine', showlegend=True), hf_x=x, hf_y=noisy_sin)

fig.show_dash(mode='inline')
```

#### FigureWidgetResampler: dynamic aggregation via `FigureWidget.layout.on_change`
```python
...
# OPTION 2 - FigureWidgetResampler: dynamic aggregation via `FigureWidget.layout.on_change`
fig = FigureWidgetResampler(go.Figure())
fig.add_trace(go.Scattergl(name='noisy sine', showlegend=True), hf_x=x, hf_y=noisy_sin)

fig
```

### Features

* **Convenient** to use:
* just add the `FigureResampler` decorator around a plotly Figure and call `.show_dash()`
* just add either
* `FigureResampler` decorator around a plotly Figure and call `.show_dash()`
* `FigureWidgetResampler` decorator around a plotly Figure and output the instance in a cell
* allows all other plotly figure construction flexibility to be used!
* **Environment-independent**
* can be used in Jupyter, vscode-notebooks, Pycharm-notebooks, Google Colab, and even as application (on a server)
* Interface for **various downsampling algorithms**:
* ability to define your preferred sequence aggregation method
* Interface for **various aggregation algorithms**:
* ability to develop or select your preferred sequence aggregation method


### Important considerations & tips

* When running the code on a server, you should forward the port of the `FigureResampler.show_dash()` method to your local machine.
* When running the code on a server, you should forward the port of the `FigureResampler.show_dash()` method to your local machine.<br>
**Note** that you can add dynamic aggregation to plotly figures with the `FigureWidgetResampler` wrapper without needing to forward a port!
* In general, when using downsampling one should be aware of (possible) [aliasing](https://en.wikipedia.org/wiki/Aliasing) effects.
The <b><a style="color:orange">[R]</a></b> in the legend indicates when the corresponding trace is being resampled (and thus possibly distorted) or not.
The <b><a style="color:orange">[R]</a></b> in the legend indicates when the corresponding trace is being resampled (and thus possibly distorted) or not. Additionally, the `~<range>` suffix represent the mean aggregation bin size in terms of the sequence index.

## Future work 🔨

Expand Down
26 changes: 13 additions & 13 deletions docs/sphinx/aggregation.rst
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
-----------
Aggregation
-----------
----------------------------
Series-wise data aggregation
----------------------------

^^^^^^^^^^^^^^^^^^^^^
Aggregation interface
^^^^^^^^^^^^^^^^^^^^^

^^^^^^^^^^^^^^^^^^
Aggregator classes
^^^^^^^^^^^^^^^^^^

.. automodule:: plotly_resampler.aggregation.aggregators
.. automodule:: plotly_resampler.aggregation.aggregation_interface
:members:
:undoc-members:
:show-inheritance:

----------

^^^^^^^^^^^^^^^^^^^^^
Aggregation interface
^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^
Aggregator classes
^^^^^^^^^^^^^^^^^^

.. automodule:: plotly_resampler.aggregation.aggregation_interface
.. automodule:: plotly_resampler.aggregation.aggregators
:members:
:undoc-members:
:show-inheritance:
:show-inheritance:
26 changes: 23 additions & 3 deletions docs/sphinx/figure_resampler.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
----------------
^^^^^^^^^^^^^^^^^^^^^^^^
AbstractFigureAggregator
^^^^^^^^^^^^^^^^^^^^^^^^

.. autoclass:: plotly_resampler.figure_resampler.figure_resampler_interface.AbstractFigureAggregator
:members:
:undoc-members:
:show-inheritance:

^^^^^^^^^^^^^^^
FigureResampler
----------------
^^^^^^^^^^^^^^^


.. autoclass:: plotly_resampler.figure_resampler.FigureResampler
:members:
:undoc-members:
:show-inheritance:


^^^^^^^^^^^^^^^^^^^^^
FigureWidgetResampler
^^^^^^^^^^^^^^^^^^^^^

.. automodule:: plotly_resampler.figure_resampler
.. autoclass:: plotly_resampler.figure_resampler.FigureWidgetResampler
:members:
:undoc-members:
:show-inheritance:
Expand Down
58 changes: 37 additions & 21 deletions docs/sphinx/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,57 @@
Getting started 🚀
==================

``plotly-resampler`` serves two main **modules**:

``plotly-resampler`` maintains its interactiveness on large data by applying front-end
**resampling**.


Users can interact with 2 components:

* :ref:`FigureResampler <FigureResampler>`: a wrapper for *plotly.graph\_objects* that serves the adaptive resampling functionality.
* :ref:`aggregation <aggregation>`: this module withholds various data aggregation methods.
* :py:mod:`figure_resampler <plotly_resampler.figure_resampler>`: a wrapper for *plotly.graph\_objects Figures*, coupling the dynamic resampling functionality to the *Figure*.
* :py:mod:`aggregation <plotly_resampler.aggregation>`: a module that withholds various data aggregation methods.

Installation ⚙️
---------------

Install via :raw-html:`<a href="https://pypi.org/project/plotly-resampler/"><b>pip</b><a>`:
Install via `pip <https://pypi.org/project/plotly-resampler>`_:

.. code:: bash
pip install plotly-resampler
How to use 📈
-------------

To **add dynamic resampling to a plotly Figure**, you should;
Dynamic resampling callbacks are realized with either:

* `Dash <https://github.com/plotly/dash>`_ callbacks, when a ``go.Figure`` object is wrapped with dynamic aggregation functionality.

.. note::

This is especially useful when working with **dash functionality** or when you do **not want to solely operate in jupyter environments**.

To **add dynamic resampling**, you should:
1. wrap the plotly Figure with :class:`FigureResampler <plotly_resampler.figure_resampler.FigureResampler>`
2. call :func:`.show_dash() <plotly_resampler.figure_resampler.FigureResampler.show_dash>` on the Figure

1. wrap the plotly Figure with :class:`FigureResampler <plotly_resampler.figure_resampler.FigureResampler>`
2. call :func:`.show_dash() <plotly_resampler.figure_resampler.FigureResampler.show_dash>` on the Figure
* `FigureWidget.layout.on_change <https://plotly.com/python-api-reference/generated/plotly.html?highlight=on_change#plotly.basedatatypes.BasePlotlyType.on_change>`_ , when a ``go.FigureWidget`` is used within a ``.ipynb`` environment.

.. note::

This is especially useful when developing in ``jupyter`` environments and when **you cannot open/forward a network-port**.


To **add dynamic resampling** using a **FigureWidget**, you should:
1. wrap your plotly Figure (can be a ``go.Figure``) with :class:`FigureWidgetResampler <plotly_resampler.figure_resampler.FigureWidgetResampler>`
2. output the ```FigureWidgetResampler`` instance in a cell

.. tip::

For **significant faster initial loading** of the Figure, we advise to wrap the constructor of the plotly Figure with :class:`FigureResampler <plotly_resampler.figure_resampler.FigureResampler>` and add the trace data as ``hf_x`` and ``hf_y``
For **significant faster initial loading** of the Figure, we advise to wrap the constructor of the plotly Figure with either :class:`FigureResampler <plotly_resampler.figure_resampler.FigureResampler>` or :class:`FigureWidgetResampler <plotly_resampler.figure_resampler.FigureWidgetResampler>` and add the trace data as ``hf_x`` and ``hf_y``

.. note::

Any plotly Figure can be wrapped with :class:`FigureResampler <plotly_resampler.figure_resampler.FigureResampler>`! 🎉 :raw-html:`<br>`
But, (obviously) only the scatter traces will be resampled.
Any plotly Figure can be wrapped with dynamic aggregation functionality! 🎉 :raw-html:`<br>`
But, (obviously) only the scatter traces will be resampled.

Working example
------------------
Working examples
-------------------

.. code:: py
Expand All @@ -57,6 +69,10 @@ Working example ✅
fig.show_dash(mode='inline')
The gif below demonstrates the example usage of of :class:`FigureWidgetResampler <plotly_resampler.figure_resampler.FigureWidgetResampler>`, where ``JupyterLab`` is used as environment and the ``FigureWidgetResampler`` instance it's output is redirected into a new view. Also note how you are able to dynamically add traces!

.. image:: https://raw.githubusercontent.com/predict-idlab/plotly-resampler/main/docs/sphinx/_static/figurewidget.gif

Important considerations & tips 🚨
----------------------------------

Expand Down Expand Up @@ -99,7 +115,7 @@ Plotly-resampler & not high-frequency traces 🔍
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. Tip::

In the *Skin conductance example* of the :raw-html:`<a href="https://github.com/predict-idlab/plotly-resampler/tree/main/examples"><b>basic_example.ipynb</b><a>`, we deal with such low-frequency traces.

The :func:`add_trace <plotly_resampler.figure_resampler.FigureResampler.add_trace>` method allows configuring argument which allows us to deal with low-frequency traces.
Expand All @@ -108,11 +124,11 @@ The :func:`add_trace <plotly_resampler.figure_resampler.FigureResampler.add_trac
Use-cases
"""""""""

* **not resampling** trace data: To achieve this, set:
* **not resampling** trace data: To achieve this, set:

* ``max_n_samples`` = len(hf_x)

* **not resampling** trace data, but **slicing to the view**: To achieve this, set:
* **not resampling** trace data, but **slicing to the view**: To achieve this, set:

* ``max_n_samples`` = len(hf_x)
* ``limit_to_view`` = True
Expand Down
6 changes: 6 additions & 0 deletions docs/sphinx/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ This is the documentation of `plotly-resampler <https://github.com/predict-idlab

.. image:: https://raw.githubusercontent.com/predict-idlab/plotly-resampler/main/docs/sphinx/_static/basic_example.gif

.. raw:: html

<br><br>

As shown in the demo above, ``plotly-resampler`` maintains its interactiveness on large data by applying front-end **resampling respective to the view**.

.. raw:: html

<embed>
Expand Down
7 changes: 6 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ plotly-resampler in various use cases.

The testing CI/CD of plotly resampler uses _selenium_ and _selenium-wire_ to test the
interactiveness of various figures. All these figures are shown in
the [basic-example notebook](basic_example.ipynb)
the [basic example notebook](basic_example.ipynb)

### 0.1 Figurewidget example

The [figurewidget example notebook](figurewidget_example.ipynb) utilizes the `FigureWidgetResampler` wrapper to
create a `go.FigureWidget` with dynamic aggregation functionality. A major advantage of this approach is that this does not create a web application, thus not needing to be able to create / forward a network port.

## 1. Dash apps

Expand Down
Loading

0 comments on commit 1e80f4e

Please sign in to comment.