Skip to content

Commit d5d6080

Browse files
committed
🖊️ review
1 parent 9777d51 commit d5d6080

File tree

15 files changed

+67
-68
lines changed

15 files changed

+67
-68
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
venv*
22
.cache_datasets/
33
*.DS_Store
4+
file_system_store/
45

56
# Sphinx documentation
67
*_build/

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ In [this Plotly-Resampler demo](https://github.com/predict-idlab/plotly-resample
158158

159159
* When running the code on a server, you should forward the port of the `FigureResampler.show_dash()` method to your local machine.<br>
160160
**Note** that you can add dynamic aggregation to plotly figures with the `FigureWidgetResampler` wrapper without needing to forward a port!
161-
* 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.
161+
* The `FigureWidgetResampler` *uses the IPython 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 that take place during these computations after finishing that computation.
162162
* In general, when using downsampling one should be aware of (possible) [aliasing](https://en.wikipedia.org/wiki/Aliasing) effects.
163163
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.
164164
* 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).

docs/sphinx/FAQ.rst

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ FAQ ❓
2020

2121
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.
2222

23-
* for *time-indexed data*: the mean time-range which is span between 2 consecutive samples.
24-
* for *numeric-indexed data*: the mean numeric range which is span between 2 consecutive samples.
23+
* for *time-indexed data*: the mean time-range between 2 consecutive (sampled) samples.
24+
* for *numeric-indexed data*: the mean numeric range between 2 consecutive (sampled) samples.
2525

2626
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.
2727

@@ -44,7 +44,7 @@ plotly-resampler can be thought of as wrapper around plain plotly figures which
4444
* To have dynamic aggregation:
4545

4646
* 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
47-
* with ``FigureWidgetResampler``, you need to use ``IPython.display`` on the object, which uses widget-events to realize dynamic aggregation.
47+
* with ``FigureWidgetResampler``, you need to use ``IPython.display`` on the object, which uses widget-events to realize dynamic aggregation (via the running IPython kernel).
4848

4949
.. raw:: html
5050

@@ -57,7 +57,7 @@ plotly-resampler can be thought of as wrapper around plain plotly figures which
5757
</summary>
5858
<div style="margin-left:1em">
5959

60-
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.
60+
The ``TraceUpdater`` class is a custom dash component that aids ``dcc.Graph`` components to efficiently send and update (in our case aggregated) data to the front-end.
6161

6262
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.
6363

@@ -78,19 +78,19 @@ For more information on how to use the trace-updater component together with the
7878

7979
**The main differences are**:
8080

81-
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.
81+
Datashader can deal with various kinds of data (e.g., location related data, point clouds), whereas plotly-resampler is more tailored towards time-series data visualizations.
8282
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.
8383

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

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

9191
In our opinion, plotly-resampler shines when:
9292

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

docs/sphinx/getting_started.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Dynamic resampling callbacks are realized:
5656

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

6161
.. tip::
6262

@@ -120,7 +120,7 @@ The gif below demonstrates the example usage of of :class:`FigureWidgetResampler
120120
<br><br>
121121

122122

123-
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>`_.
123+
Furthermore, plotly's ``FigureWidget`` allows to conveniently add callbacks to for example click events. This allows creating a high-frequency time series annotation app in a couple of lines; as shown in the gif below and in `this notebook <https://github.com/predict-idlab/plotly-resampler/blob/main/examples/figurewidget_example.ipynb>`_.
124124

125125

126126
.. image:: _static/annotate_twitter.gif

examples/README.md

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
# plotly-resampler examples
22

3-
This directory withholds several examples, highlighting the applicability of
4-
plotly-resampler for various use cases.
5-
6-
To successfully run these examples, make sure that you've installed all the requirements by running:
7-
```bash
8-
pip install -r requirements.txt
9-
```
3+
This directory withholds several examples, highlighting the applicability of plotly-resampler for various use cases.
104

115

126
## Prerequisites
@@ -19,8 +13,7 @@ pip install -r requirements.txt
1913
## 1. Example notebooks
2014
### 1.1 basic examples
2115

22-
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
23-
plotly-resampler in their day-to-day jupyter environments.
16+
The [basic example notebook](basic_example.ipynb) covers most use-cases in which plotly resampler will be employed. It servers as an ideal starting point for data-scientists who want to use plotly-resampler in their day-to-day jupyter environments.
2417

2518
Additionally, this notebook also shows some more advanced functionalities, such as:
2619
* Retaining (a static) plotly-resampler figure in your notebook
@@ -30,7 +23,7 @@ Additionally, this notebook also shows some more advanced functionalities, such
3023

3124
### 1.2 Figurewidget example
3225

33-
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.
26+
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, avoiding starting an application on a port (and forwarding that port when working remotely).
3427

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

@@ -48,7 +41,7 @@ which `plotly-resampler` is integrated
4841
| **advanced apps** | |
4942
| [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 |
5043
| [file visualization](dash_apps/12_file_selector.py) | load and visualize multiple `.parquet` files with plotly-resampler |
51-
| [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. |
44+
| [dynamic static graph](dash_apps/13_coarse_fine.py) | Visualization dashboard in which a dynamic (i.e., plotly-resampler graph) and a coarse, 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. |
5245

5346
## 3. Other apps
5447

examples/basic_example.ipynb

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"outputs": [],
99
"source": [
1010
"%load_ext autoreload\n",
11-
"%autoreload 2\n"
11+
"%autoreload 2"
1212
]
1313
},
1414
{
@@ -23,7 +23,6 @@
2323
"\n",
2424
"import plotly.graph_objects as go\n",
2525
"from plotly.subplots import make_subplots\n",
26-
"from datetime import datetime\n",
2726
"\n",
2827
"from helper import groupby_consecutive\n",
2928
"\n",
@@ -344,7 +343,7 @@
344343
" <li>we add a unique _uid to each object</li>\n",
345344
" <li>we add a new endpoint to the underlying flask app that</li><ul>\n",
346345
" <li>is only accessible via the corresponding app its _uid</li>\n",
347-
" <li>hasCORS rights for any origin and 'Content-Type' headers</li>\n",
346+
" <li>has CORS rights for any origin and 'Content-Type' headers</li>\n",
348347
" </ul>\n",
349348
" <li>Note that this is the only CORS endpoint of the JupyterDash app & is only preset when \"inline_persistent\" is used!</li>\n",
350349
" <li>we check in the JavaScript output of the notebook cell whether that endpoint is reachable and emits the expected message (i.e., \"Alive\")</li><ul>\n",
@@ -461,7 +460,7 @@
461460
"* restart the kernel; and reopen this notebook\n",
462461
"* export this notebook to html\n",
463462
"\n",
464-
"ou should see a static (aggregated) image of the above figure"
463+
"you should see a static (aggregated) image of the above figure"
465464
]
466465
},
467466
{
@@ -506,8 +505,8 @@
506505
"id": "6bffa11a",
507506
"metadata": {},
508507
"source": [
509-
"now we adjust the figure data \n",
510-
"**note**: after running the cell below, we need to manually trigger a graph update (by for example zooming / resetting the axes) to ensure that the new data is shown."
508+
"Now we adjust the figure data \n",
509+
"**Note**: after running the cell below, we need to manually trigger a graph update (by for example zooming / resetting the axes) to ensure that the new data is shown."
511510
]
512511
},
513512
{
@@ -547,8 +546,8 @@
547546
"id": "22687274",
548547
"metadata": {},
549548
"source": [
550-
"now we adjust the figure data \n",
551-
"**note**: after running the cell below, we need to manually trigger a graph update (by for example zooming / resetting the axes) to ensure that the new data is shown."
549+
"Now we adjust the figure data \n",
550+
"**Note**: after running the cell below, we need to manually trigger a graph update (by for example zooming / resetting the axes) to ensure that the new data is shown."
552551
]
553552
},
554553
{
@@ -566,7 +565,7 @@
566565
"id": "f4130fac",
567566
"metadata": {},
568567
"source": [
569-
"**pro tip**: `FigureWidgetResampler` has the `reload_data` and `reset_axes` methods to do this automatically"
568+
"**Pro tip**: `FigureWidgetResampler` has the `reload_data` and `reset_axes` methods to do this automatically"
570569
]
571570
},
572571
{
@@ -1137,19 +1136,14 @@
11371136
")\n",
11381137
"fig.show_dash(mode=\"inline\", port=9032)\n"
11391138
]
1140-
},
1141-
{
1142-
"cell_type": "code",
1143-
"execution_count": null,
1144-
"id": "336f4a9f",
1145-
"metadata": {},
1146-
"outputs": [],
1147-
"source": []
11481139
}
11491140
],
11501141
"metadata": {
1142+
"interpreter": {
1143+
"hash": "7de7e435a0bf9ca45826b967e5fb3b58dc0ed05475145ede0348ba81bb2b2016"
1144+
},
11511145
"kernelspec": {
1152-
"display_name": "Python 3.10.5 ('plotly-resampler-MpVFAeoZ-py3.10')",
1146+
"display_name": "Python 3.8.10 ('venv')",
11531147
"language": "python",
11541148
"name": "python3"
11551149
},
@@ -1163,7 +1157,7 @@
11631157
"name": "python",
11641158
"nbconvert_exporter": "python",
11651159
"pygments_lexer": "ipython3",
1166-
"version": "3.10.5"
1160+
"version": "3.8.10"
11671161
},
11681162
"toc-autonumbering": true,
11691163
"vscode": {

examples/dash_apps/01_minimal_global.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Minimal dash app example.
22
3-
Click on a button, and see a plotly-resampler graph of a noisy sinusoid.
3+
Click on a button, and see a plotly-resampler graph of two noisy sinusoids.
44
No dynamic graph construction / pattern matching callbacks are needed.
55
66
This example uses a global FigureResampler object, which is considered a bad practice.
@@ -30,7 +30,7 @@
3030
app = dash.Dash(__name__)
3131
fig: FigureResampler = FigureResampler()
3232
# NOTE: in this example, this reference to a FigureResampler is essential to preserve
33-
# throughout the whole dash app! If your dash app want to create a new go.Figure(),
33+
# throughout the whole dash app! If your dash app wants to create a new go.Figure(),
3434
# you should not construct a new FigureResampler object, but replace the figure of this
3535
# FigureResampler object by using the FigureResampler.replace() method.
3636

examples/dash_apps/02_minimal_cache.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
"""Minimal dash app example.
22
3-
Click on a button, and see a plotly-resampler graph of a noisy sinusoid.
3+
Click on a button, and see a plotly-resampler graph of two noisy sinusoids.
44
No dynamic graph construction / pattern matching callbacks are needed.
55
66
This example uses the dash-extensions its ServersideOutput functionality to cache
77
the FigureResampler per user/session on the server side. This way, no global figure
8-
variable is used and shows the best practice of using plotly-resampler Figures within
9-
dash-apps.
8+
variable is used and shows the best practice of using plotly-resampler within dash-apps.
109
1110
"""
1211

@@ -34,7 +33,7 @@
3433
html.H1("plotly-resampler + dash-extensions", style={"textAlign": "center"}),
3534
html.Button("plot chart", id="plot-button", n_clicks=0),
3635
html.Hr(),
37-
# The graph and it's needed components to serialize and update efficiently
36+
# The graph and its needed components to serialize and update efficiently
3837
# Note: we also add a dcc.Store component, which will be used to link the
3938
# server side cached FigureResampler object
4039
dcc.Graph(id="graph-id"),

examples/dash_apps/03_minimal_cache_dynamic.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
"""Minimal dynamic dash app example.
22
3-
Click on a button, and draw a new plotly-resampler graph of a noisy sinusoid.
3+
Click on a button, and draw a new plotly-resampler graph of two noisy sinusoids.
44
This example uses pattern-matching callbacks to update dynamically constructed graphs.
55
The plotly-resampler graphs themselves are cached on the server side.
66
7-
The main difference between this example and the dash_app_minimal_cache.py is that here,
8-
we want to cache using a dcc.Store that is not yet available on the client side. As a
9-
result we split up our logic into two callbacks: (1) the callback used to construct the
10-
necessary components and send them to the client-side, and (2) the callback used to
11-
construct the actual plotly-resampler graph and cache it on the server side. These
12-
two callbacks are chained together using the dcc.Interval component.
7+
The main difference between this example and 02_minimal_cache.py is that here, we want
8+
to cache using a dcc.Store that is not yet available on the client side. As a result we
9+
split up our logic into two callbacks: (1) the callback used to construct the necessary
10+
components and send them to the client-side, and (2) the callback used to construct the
11+
actual plotly-resampler graph and cache it on the server side. These two callbacks are
12+
chained together using the dcc.Interval component.
1313
1414
"""
1515

1616
from uuid import uuid4
17+
from typing import List
1718

1819
import numpy as np
1920
import plotly.graph_objects as go
@@ -46,18 +47,18 @@
4647

4748
# ------------------------------------ DASH logic -------------------------------------
4849
# This method adds the needed components to the front-end, but does not yet contain the
49-
# figureResampler graph construction logic.
50+
# FigureResampler graph construction logic.
5051
@app.callback(
5152
Output("container", "children"),
5253
Input("add-chart", "n_clicks"),
5354
State("container", "children"),
5455
prevent_initial_call=True,
5556
)
56-
def add_graph_div(n_clicks: int, div_children: list[html.Div]):
57+
def add_graph_div(n_clicks: int, div_children: List[html.Div]):
5758
uid = str(uuid4())
5859
new_child = html.Div(
5960
children=[
60-
# The graph and it's needed components to serialize and update efficiently
61+
# The graph and its needed components to serialize and update efficiently
6162
# Note: we also add a dcc.Store component, which will be used to link the
6263
# server side cached FigureResampler object
6364
dcc.Graph(id={"type": "dynamic-graph", "index": uid}, figure=go.Figure()),
@@ -75,7 +76,7 @@ def add_graph_div(n_clicks: int, div_children: list[html.Div]):
7576
return div_children
7677

7778

78-
# This method constructs the figureResampler graph and caches it on the server side
79+
# This method constructs the FigureResampler graph and caches it on the server side
7980
@app.callback(
8081
ServersideOutput({"type": "store", "index": MATCH}, "data"),
8182
Output({"type": "dynamic-graph", "index": MATCH}, "figure"),

0 commit comments

Comments
 (0)