Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📦 improve docs #104

Merged
merged 50 commits into from
Aug 11, 2022
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
223ef01
:pray: enable numerically unstable test
jvdd Jul 17, 2022
39c871c
:pineapple: improving examples
jonasvdd Jul 17, 2022
107d7da
:dash: improving examples & updating :lock:
jonasvdd Jul 17, 2022
65813f2
:broom:
jonasvdd Jul 17, 2022
06708d0
:fire: first working RC on windows
jonasvdd Jul 17, 2022
ed646d4
:chocolate_bar: improving dash examples
jonasvdd Jul 18, 2022
9f4e170
:mag:
jonasvdd Jul 18, 2022
9e85410
:pencil:
jonasvdd Jul 18, 2022
e6fc5df
:apple: improving docs + examples
jonasvdd Jul 18, 2022
03cf4bd
:fire: updating docs
jonasvdd Jul 18, 2022
b6e5400
:icecream: docs
jonasvdd Jul 19, 2022
9254b4d
:sparkles: adding datashader example
jonasvdd Jul 21, 2022
fb8f42f
:see_no_evil: add correct mean-x implementation
jonasvdd Jul 25, 2022
5561f46
:dash: adding python fallback for LTTBc
jonasvdd Jul 28, 2022
f0dbb47
:parachute: catching C extension build fails as we now have a Python …
jonasvdd Jul 28, 2022
be1feef
:robot: hack together output retention in notebooks
jvdd Jul 18, 2022
a67f73a
:see_no_evil:
jvdd Jul 18, 2022
b44b68c
:broom: cleanup code
jvdd Jul 19, 2022
b2d2a31
:hot_face: add tests + cleanup
jvdd Jul 19, 2022
5f6ada5
:pray:
jvdd Jul 19, 2022
ee70565
:robot: add manual test
jvdd Jul 19, 2022
24ca144
:lock:
jvdd Jul 19, 2022
a43d462
:lock: kaleido dependency
jvdd Jul 19, 2022
297c81d
:train2: improve test cov
jvdd Jul 19, 2022
7180567
:wood: check browser logs in selenium tests
jvdd Jul 21, 2022
bdc2c6a
:camping: add browser log checks to selenium tests
jvdd Jul 22, 2022
41d9e96
:pencil: review
jonasvdd Jul 28, 2022
f50d33e
:fire: improving kaleido output
jonasvdd Jul 29, 2022
68e756d
:tea: making kaleido a dev dependency
jonasvdd Jul 29, 2022
db6a1ac
:pencil: add kaleido to docs
jonasvdd Jul 29, 2022
88b9da4
:dash: improving examples & updating :lock:
jonasvdd Jul 17, 2022
68ffd8f
:broom:
jonasvdd Jul 17, 2022
e9ef58d
:fire: first working RC on windows
jonasvdd Jul 17, 2022
25929e2
:ice_cube: adding figure retention example
jonasvdd Jul 29, 2022
0ec65f2
:see_no_evil:
jonasvdd Jul 29, 2022
b7af075
:lock: :pray:
jonasvdd Jul 29, 2022
fe830da
:pray:
jonasvdd Jul 29, 2022
7e453a1
Merge branch 'main' into improve_docs
jonasvdd Jul 29, 2022
389c1b7
:pencil:
jonasvdd Jul 29, 2022
606a51c
:monocle_face:
jonasvdd Jul 29, 2022
380b089
:dash: adding yep
jonasvdd Jul 29, 2022
9b275ce
:thinking: updating efficientLTTB based on benchmark heuristics
jonasvdd Jul 29, 2022
d74cd4c
:see_no_evil: removing yep for CI-CD reasons
jonasvdd Jul 29, 2022
f892f56
:test_tube: adding tests & fixing :bug:s
jonasvdd Jul 30, 2022
12457eb
:muscle: Fix ipywidgets dependency #109
jonasvdd Jul 30, 2022
9777d51
:monocle_face: print build errors on fail
jonasvdd Aug 8, 2022
d5d6080
:pen: review
jvdd Aug 11, 2022
ee2176e
:pencil: meta-review
jonasvdd Aug 11, 2022
070e6fc
:pray: updating tests
jonasvdd Aug 11, 2022
8bed8a7
:pray: :pray:
jonasvdd Aug 11, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ In [this Plotly-Resampler demo](https://github.com/predict-idlab/plotly-resample

* 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!
* The `FigureWidgetResampler` *uses the python main thread* for its data aggregation functionality, so when this main thread is occupied, no resampling logic can be executed. For example; if you perform long computations within your notebook, the kernel will be occupied during these computations, and will only execute the resampling operations which take place during these computations after finishing that computation.
* In general, when using downsampling one should be aware of (possible) [aliasing](https://en.wikipedia.org/wiki/Aliasing) effects.
The <b style="color:orange">[R]</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.
* The plotly **autoscale** event (triggered by the autoscale button or a double-click within the graph), **does not reset the axes but autoscales the current graph-view** of plotly-resampler figures. This design choice was made as it seemed more intuitive for the developers to support this behavior with double-click than the default axes-reset behavior. The graph axes can ofcourse be resetted by using the `reset_axis` button. If you want to give feedback and discuss this further with the developers, see issue [#49](https://github.com/predict-idlab/plotly-resampler/issues/49).
Expand Down
26 changes: 19 additions & 7 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
def get_script_path():
return os.path.dirname(os.path.realpath(sys.argv[0]))


extensions = []
if with_extensions:
extensions = [
Extension(
name="plotly_resampler.aggregation.algorithms.lttbcv2",
sources=["plotly_resampler/aggregation/algorithms/lttbcv2.c"],
name="plotly_resampler.aggregation.algorithms.lttbc",
sources=["plotly_resampler/aggregation/algorithms/lttbc.c"],
define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")],
include_dirs=[np.get_include(), get_script_path()],
),
Expand All @@ -44,15 +45,26 @@ def run(self):
try:
build_ext.run(self)
except (DistutilsPlatformError, FileNotFoundError) as e:
print(" Unable to build the C extensions.")
raise e
print(
" Unable to build the C extensions, will use the slower python "
"fallback for LTTB"
)
print(e)

def build_extension(self, ext):
try:
build_ext.build_extension(self, ext)
except (CCompilerError, DistutilsExecError, DistutilsPlatformError, ValueError) as e:
print(' Unable to build the "{}" C extension, '.format(ext.name))
raise e
except (
DistutilsPlatformError,
CCompilerError,
DistutilsExecError,
ValueError,
) as e:
print(
' Unable to build the "{}" C extension; '.format(ext.name)
+ "will use the slower python fallback."
)
print(e)


def build(setup_kwargs):
Expand Down
162 changes: 162 additions & 0 deletions docs/sphinx/FAQ.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
.. role:: raw-html(raw)
:format: html

.. |br| raw:: html

<br>


FAQ ❓
======

.. raw:: html

<details>
<summary>
<a><b>What does the orange <b style="color:orange">~ time|number </b> suffix in legend name indicate?</b></a>
</summary>
<div style="margin-left:1em">


This tilde suffix is only shown when the data is aggregated and represents the *mean aggregation bin size* which is the mean index-range difference between two consecutive aggregated samples.

* for *time-indexed data*: the mean time-range which is span between 2 consecutive samples.
* for *numeric-indexed data*: the mean numeric range which is span between 2 consecutive samples.

When the index is a range-index; the *mean aggregation bin size* represents the *mean* downsample ratio; i.e., the mean number of samples that are aggregated into one sample.

.. raw:: html

</div>
</details>
<br>
<details>
<summary>
<a><b>What is the difference between plotly-resampler figures and plain plotly figures?</b></a>
</summary>
<div style="margin-left:1em">

plotly-resampler can be thought of as wrapper around plain plotly figures which adds line-chart visualization scalability by dynamically aggregating the data of the figures w.r.t. the front-end view. plotly-resampler thus adds dynamic aggregation functionality to plain plotly figures.

**important to know**:

* ``show`` *always* returns a static html view of the figure, i.e., no dynamic aggregation can be performed on that view.
* To have dynamic aggregation:

* with ``FigureResampler``, you need to call ``show_dash`` (or output the object in a cell via ``IPython.display``) -> which spawns a dash-web app, and the dynamic aggregation is realized with dash callback
* with ``FigureWidgetResampler``, you need to use ``IPython.display`` on the object, which uses widget-events to realize dynamic aggregation.

.. raw:: html

</div>
</details>
<br>
<details>
<summary>
<a><b>What does <code><a href="https://github.com/predict-idlab/trace-updater" target="_blank">TraceUpdater</a></code> do?</b></a>
</summary>
<div style="margin-left:1em">

The ``TraceUpdater`` class is a custom dash component that aids ``dcc.Graph`` components to efficiently sent and update (in our case aggregated) data to the front-end.

For more information on how to use the trace-updater component together with the ``FigureResampler``, see our dash app `examples <https://github.com/predict-idlab/plotly-resampler/tree/main/examples>`_` and look at the `trace-updater <https://github.com/predict-idlab/trace-updater/blob/master/trace_updater/TraceUpdater.py>`_ its documentation.

.. raw:: html

</div>
</details>
<br>
<details>
<summary>
<a><b>What is the difference in approach between plotly-resampler and datashader?</b></a>
</summary>
<div style="margin-left:1em">


`Datashader <https://datashader.org/getting_started/Introduction.html>`_ is a highly scalable `open-source <https://github.com/holoviz/datashader>`_ library for analyzing and visualizing large datasets. More specifically, datashader *“rasterizes”* or *“aggregates”* datasets into regular grids that can be analyzed further or viewed as **images**.


**The main differences are**:

Datashader is able deal with various kinds of data (e.g., location related data, point clouds, ...), and plotly-resampler is more tailored towards time-series data visualizations.
Furthermore, datashader outputs a **rasterized image/array** encompassing all traces their data, whereas plotly-resampler outputs an **aggregated series** per trace. Thus, datashader is more suited for analyzing data where you do not want to pin-out a certain series/trace.

In our opinion, datashader truly shines (for the time series use case) when:

* you want a global, overlaying view of all your traces
* you want to visualize a large number of time series in a single plot (many traces)
* there is a lot of noise on your high-frequency data and want to uncover the underlying pattern
* you want to render all data points in your visualization

In our opinion, plotly-resampler shines when:

* you need the capabilities to interact with the traces (e.g., hovering, toggling traces, hovertext pet trace)
* you want to use a less complex (but more restricted) visualization interface (as opposed to holoviews), i.e., plotly
* you want to make existing plotly time-series figures more scalable and efficient
* to build scalable Dash apps for time-series data visualization

Furthermore combined with holoviews, datashader can also be employed in an interactive manner, see the example below.

.. code:: python

from holoviews.operation.datashader import datashade
import datashader as ds
import holoviews as hv
import numpy as np
import pandas as pd
import panel as pn

hv.extension("bokeh")
pn.extension(comms='ipywidgets')

# Create the dummy dataframe
n = 1_000_000
x = np.arange(n)
noisy_sine = (np.sin(x / 3_000) + (np.random.randn(n) / 10)) * x / 5_000
df = pd.DataFrame(
{"ns": noisy_sine, "ns_abs": np.abs(noisy_sine),}
)

# Visualize interactively with datashader
opts = hv.opts.RGB(width=800, height=400)
ndoverlay = hv.NdOverlay({c:hv.Curve((df.index, df[c])) for c in df.columns})
datashade(ndoverlay, cnorm='linear', aggregator=ds.count(), line_width=3).opts(opts)

.. image:: _static/datashader.png


.. raw:: html

</div>
</details>
<br>
<details>
<summary>
<a><b>I get errors such as:</b><br><ul><li>
<code>RuntimeError: module compiled against API version 0x10 but this version of numpy is 0xe</code></li>
<li><code>ImportError: numpy.core.multiarray failed to import</code></li>
</ul>
</a>
</summary>
<div style="margin-left:1em">

Plotly-resampler uses compiled C code (which uses the NumPy C API) to speed up the LTTB data-aggregation algorithm. This C code gets compiled during the building stage of the package (which might be before you install the package).<br><br>
If this C extension was build against a more recent NumPy version than your local version, you obtain a
<a href="https://numpy.org/devdocs/user/troubleshooting-importerror.html#c-api-incompatibility"><i>NumPy C-API incompatibility</i></a>
and the above error will be raised.<br><br>

These above mentioned errors can thus be resolved by running<br>
&nbsp;&nbsp;&nbsp;&nbsp;<code>pip install --upgrade numpy</code><br>
and reinstalling plotly-resampler afterwards.<br><br>

For more information about compatibility and building upon NumPy, you can consult
<a href="https://numpy.org/doc/stable/user/depending_on_numpy.html?highlight=compiled#for-downstream-package-authors">NumPy's docs for downstream package authors</a>.

We aim to limit this issue as much as possible (by for example using <a href="https://github.com/scipy/oldest-supported-numpy">oldest-supported-numpy</a> in our build.py),
but if you still experience issues, please open an issue on <a href="https://github.com/predict-idlab/plotly-resampler/issues">GitHub</a>.

.. raw:: html

</div>
</details>
<br>
Binary file added docs/sphinx/_static/annotate_twitter.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/sphinx/_static/datashader.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 9 additions & 6 deletions docs/sphinx/dash_app_integration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@



Dash integration 🤝
===================
Dash apps 🤝
============

This documentation page describes how you can integrate ``plotly-resampler`` in a `dash <https://dash.plotly.com/>`_ application.

Expand Down Expand Up @@ -48,16 +48,19 @@ When you add a :class:`FigureResampler <plotly_resampler.figure_resampler.Figure
fig.register_update_graph_callback(app, "graph-id", "trace-updater")
.. warning::

The above example serves as an illustration, but uses a *global variable* to store the ``FigureResampler`` instance; this is not a good practice.
Ideally you should cache the ``FigureResampler`` per session on the server side.
In our `examples folder <https://github.com/predict-idlab/plotly-resampler/tree/main/examples>`_, we provide several dash app examples where we perform server side caching of such figures.


.. tip::

You can make the resampling faster by ensuring the
`TraceUpdater <https://github.com/predict-idlab/trace-updater>`_ its
``sequentialUpdate`` argument is set to ``False``.


* `This TraceUpdater-example <https://github.com/predict-idlab/trace-updater/blob/master/usage.py>`_ serves as a minimal working example.


Limitations
-----------
``plotly_resampler`` relies on `TraceUpdater <https://github.com/predict-idlab/trace-updater>`_ to ensure that the *updateData* is sent
Expand Down
17 changes: 14 additions & 3 deletions docs/sphinx/getting_started.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
.. role:: raw-html(raw)
:format: html

Getting started 🚀
==================
Get started 🚀
==============

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

Expand Down Expand Up @@ -114,6 +114,17 @@ The gif below demonstrates the example usage of of :class:`FigureWidgetResampler

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


.. raw:: html

<br><br>


Furthermore, plotly figurewidget allows to conveniently add callbacks to for example click events. This allows to create a high-frequency time series annotation app in a couple of lines; a shown in the gif below and `this notebook <https://github.com/predict-idlab/plotly-resampler/blob/main/examples/figurewidget_example.ipynb>`_.


.. image:: _static/annotate_twitter.gif

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

Expand Down Expand Up @@ -162,7 +173,7 @@ Working example ⬇️:
.. tip::

The ``FigureWidgetResampler`` graph will not be automatically redrawn after
adjusting the fig its `hf_data` property,. The redrawning can be triggered by
adjusting the fig its `hf_data` property,. The redrawing can be triggered by
manually calling either:

* :func:`FigureWidgetResampler.reload_data <plotly_resampler.figure_resampler.FigureWidgetResampler.reload_data>`, which keeps the current-graph range.
Expand Down
2 changes: 1 addition & 1 deletion docs/sphinx/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ As shown in the demo above, ``plotly-resampler`` maintains its interactiveness o

getting_started
dash_app_integration

api_reference
FAQ



Expand Down
54 changes: 39 additions & 15 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,56 @@
# plotly-resampler examples

This directory withholds several examples, indicating the applicability of
plotly-resampler in various use cases.
This directory withholds several examples, highlighting the applicability of
plotly-resampler for various use cases.

## 0. basic example
To successfully run these examples, make sure that you've installed all the requirements by running:
```bash
pip install -r requirements.txt
```

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)

### 0.1 Figurewidget example
## Prerequisites

To successfully run these examples, make sure that you've installed all the [requirements](requirements.txt) by running:
```bash
pip install -r requirements.txt
```

## 1. Example notebooks
### 1.1 basic examples

The [basic example notebook](basic_example.ipynb) covers most use-cases in which plotly resampler will be employed. It is the ideal hands-on starting point for data-scientists who want to use
plotly-resampler in their day-to-day jupyter environments.

Additionally, this notebook also shows some more advanced functionalities, such as:
* Retaining (a static) plotly-resampler figure in your notebook
* Adjusting trace data of plotly-resampler figures at runtime
* The flexibility of configuring different aggregation-algorithms and number of shown samples per trace


### 1.2 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.

Additionally, this notebook highlights how to use the `FigureWidget` its on-click callback to utilize plotly for large **time series annotation**.

## 1. Dash apps
## 2. Dash apps

The [dash_apps](dash_apps/) folder contains example dash apps in
which `plotly-resampler` is integrated

| app-name | description |
| --- | --- |
| [file visualization](dash_apps/dash_app.py) | load and visualize multiple `.parquet` files with plotly-resampler |
| [dynamic sine generator](dash_apps/construct_dynamic_figures.py) | expeonential sine generator which uses [pattern matching callbacks](https://dash.plotly.com/pattern-matching-callbacks) to remove and construct plotly-resampler graphs dynamically |
| [dynamic static graph](dash_apps/dash_app_coarse_fine.py) | Visualization dashboard in which a dynamic (i.e., plotly-resampler graph) and static graph (i.e., go.Figure) are shown (made for [this issue](https://github.com/predict-idlab/plotly-resampler/issues/56)). Relayout events on the coarse graph update the dynamic graph.

## 2. Other apps
| | description |
|------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **minimal examples** | |
| [global variable](dash_apps/01_minimal_global.py) | *bad practice*: minimal example in which a global `FigureResampler` variable is used |
| [server side caching](dash_apps/02_minimal_cache.py) | *good practice*: minimal example in which we perform server side caching of the `FigureResampler` variable |
| [runtime graph construction](dash_apps/03_minimal_cache_dynamic.py) | minimal example where graphs are constructed based on user interactions at runtime. [Pattern matching callbacks](https://dash.plotly.com/pattern-matching-callbacks) are used construct these plotly-resampler graphs dynamically. Again, server side caching is performed. |
| **advanced apps** | |
| [dynamic sine generator](dash_apps/11_sine_generator.py) | exponential sine generator which uses [pattern matching callbacks](https://dash.plotly.com/pattern-matching-callbacks) to remove and construct plotly-resampler graphs dynamically |
| [file visualization](dash_apps/12_file_selector.py) | load and visualize multiple `.parquet` files with plotly-resampler |
| [dynamic static graph](dash_apps/13_coarse_fine.py) | Visualization dashboard in which a dynamic (i.e., plotly-resampler graph) and static graph (i.e., go.Figure) are shown (made for [this issue](https://github.com/predict-idlab/plotly-resampler/issues/56)). Graph interaction events on the coarse graph update the dynamic graph. |

## 3. Other apps

The [other_apps](other_apps/) folder contains examples of `plotly-resampler` being *integrated* in other apps / frameworks

Expand Down
Loading