Skip to content

Commit 741da5d

Browse files
jonasvddjvdd
andauthored
✈️ fix for #270 (#272)
* :plane: fix for #270 * ✨ add `customdata` to hf_data_container * 💪 adding tests * 💨 linting * 🙈 fix futurewarning * 🧹 formatting * 🙈 fix tests * ✈️ ➡️ 🇧🇪 Autosize support (#273) * ✈️ 🇧🇪 fix for #259 * 💨 linting * 💨 adding autosize support * ✨ updating action versions * 🙈 skip dtype test on window * 🙏 skip test --------- Co-authored-by: Jeroen Van Der Donckt <[email protected]>
1 parent 780062c commit 741da5d

File tree

7 files changed

+456
-231
lines changed

7 files changed

+456
-231
lines changed

.github/workflows/lint.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ jobs:
1010
lint:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v2
13+
- uses: actions/checkout@v4
1414
with:
1515
lfs: true
1616
- name: Set up Python 3.10
17-
uses: actions/setup-python@v2
17+
uses: actions/setup-python@v4
1818
with:
1919
python-version: '3.10'
2020
- name: Install Poetry
2121
uses: snok/install-poetry@v1
2222
- name: Cache poetry
2323
id: cached-poetry-dependencies
24-
uses: actions/cache@v2
24+
uses: actions/cache@v3
2525
with:
2626
path: ~/.cache/pypoetry/virtualenvs
2727
key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }}-python-${{ matrix.python-version }}

.github/workflows/test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ jobs:
3131
shell: bash
3232

3333
steps:
34-
- uses: actions/checkout@v2
34+
- uses: actions/checkout@v4
3535
with:
3636
lfs: true
3737
- name: Set up Python ${{ matrix.python-version }}
38-
uses: actions/setup-python@v2
38+
uses: actions/setup-python@v4
3939
with:
4040
python-version: ${{ matrix.python-version }}
4141

@@ -50,7 +50,7 @@ jobs:
5050
version: 1.5.1
5151
- name: Cache poetry
5252
id: cached-poetry-dependencies
53-
uses: actions/cache@v2
53+
uses: actions/cache@v3
5454
with:
5555
path: ~/.cache/pypoetry/virtualenvs
5656
key: ${{ runner.os }}-poetry-${{ hashFiles('poetry.lock') }}-python-${{ matrix.python-version }}
@@ -70,12 +70,12 @@ jobs:
7070
run: |
7171
poetry run pytest --cov=plotly_resampler --junitxml=junit/test-results-${{ matrix.python-version }}.xml --cov-report=xml tests
7272
- name: Upload pytest test results
73-
uses: actions/upload-artifact@v2
73+
uses: actions/upload-artifact@v3
7474
with:
7575
name: pytest-results-${{ matrix.python-version }}
7676
path: junit/test-results-${{ matrix.python-version }}.xml
7777
# Use always() to always run this step to publish test results when there are test failures
7878
if: ${{ always() }}
7979

8080
- name: Upload coverage to Codecov
81-
uses: codecov/codecov-action@v1
81+
uses: codecov/codecov-action@v3

examples/basic_example.ipynb

Lines changed: 355 additions & 208 deletions
Large diffs are not rendered by default.

plotly_resampler/figure_resampler/figure_resampler.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,8 @@ def show_dash(
521521
), f"mode must be one of {available_modes}"
522522
graph_properties = {} if graph_properties is None else graph_properties
523523
assert "config" not in graph_properties # There is a param for config
524+
if self["layout"]["autosize"] is True and self["layout"]["height"] is None:
525+
graph_properties.setdefault("style", {}).update({"height": "100%"})
524526

525527
# 0. Check if the traces need to be updated when there is a xrange set
526528
# This will be the case when the users has set a xrange (via the `update_layout`
@@ -569,17 +571,18 @@ def show_dash(
569571
else:
570572
# jupyter dash uses a normal Dash app as figure
571573
app = dash.Dash("local_app", **app_init_kwargs)
572-
574+
# fmt: off
573575
div = dash.html.Div(
574-
[
575-
dash.dcc.Graph(
576-
id="resample-figure", figure=self, config=config, **graph_properties
577-
),
578-
TraceUpdater(
579-
id="trace-updater", gdID="resample-figure", sequentialUpdate=False
580-
),
581-
]
576+
style={
577+
"display": "flex", "flex-flow": "column",
578+
"height": "95vh", "width": "100%",
579+
},
580+
children=[
581+
dash.dcc.Graph(id="resample-figure", figure=self, config=config, **graph_properties),
582+
TraceUpdater(id="trace-updater", gdID="resample-figure", sequentialUpdate=False),
583+
],
582584
)
585+
# # fmt: on
583586
if self._create_overview:
584587
overview_config = config.copy() if config is not None else {}
585588
overview_config["displayModeBar"] = False

plotly_resampler/figure_resampler/figure_resampler_interface.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
# `_hf_data_container._asdict()` function is used in
3535
# `AbstractFigureAggregator._construct_hf_data_dict`.
3636
_hf_data_container = namedtuple(
37-
"DataContainer", ["x", "y", "text", "hovertext", "marker_size", "marker_color"]
37+
"DataContainer",
38+
["x", "y", "text", "hovertext", "marker_size", "marker_color", "customdata"],
3839
)
3940

4041

@@ -152,8 +153,12 @@ def __init__(
152153

153154
# A list of al xaxis and yaxis string names
154155
# e.g., "xaxis", "xaxis2", "xaxis3", .... for _xaxis_list
155-
self._xaxis_list = self._re_matches(re.compile("xaxis\d*"), self._layout.keys())
156-
self._yaxis_list = self._re_matches(re.compile("yaxis\d*"), self._layout.keys())
156+
self._xaxis_list = self._re_matches(
157+
re.compile(r"xaxis\d*"), self._layout.keys()
158+
)
159+
self._yaxis_list = self._re_matches(
160+
re.compile(r"yaxis\d*"), self._layout.keys()
161+
)
157162
# edge case: an empty `go.Figure()` does not yet contain axes keys
158163
if not len(self._xaxis_list):
159164
self._xaxis_list = ["xaxis"]
@@ -382,7 +387,7 @@ def _nest_dict_rec(k: str, v: any, out: dict) -> None:
382387
out[k] = v
383388

384389
# Check if (hover)text also needs to be downsampled
385-
for k in ["text", "hovertext", "marker_size", "marker_color"]:
390+
for k in ["text", "hovertext", "marker_size", "marker_color", "customdata"]:
386391
k_val = hf_trace_data.get(k)
387392
if isinstance(k_val, (np.ndarray, pd.Series)):
388393
assert isinstance(
@@ -641,6 +646,8 @@ def _parse_get_trace_props(
641646
else hf_marker_color
642647
)
643648

649+
hf_customdata = trace["customdata"] if hasattr(trace, "customdata") else None
650+
644651
if trace["type"].lower() in self._high_frequency_traces:
645652
if hf_x is None: # if no data as x or hf_x is passed
646653
if hf_y.ndim != 0: # if hf_y is an array
@@ -742,7 +749,13 @@ def _parse_get_trace_props(
742749
trace.marker.color = hf_marker_color
743750

744751
return _hf_data_container(
745-
hf_x, hf_y, hf_text, hf_hovertext, hf_marker_size, hf_marker_color
752+
hf_x,
753+
hf_y,
754+
hf_text,
755+
hf_hovertext,
756+
hf_marker_size,
757+
hf_marker_color,
758+
hf_customdata,
746759
)
747760

748761
def _construct_hf_data_dict(

tests/test_figure_resampler.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
__author__ = "Jonas Van Der Donckt, Jeroen Van Der Donckt, Emiel Deprost"
44

5-
65
import datetime
76
import multiprocessing
87
import subprocess
8+
import sys
99
import time
1010
from datetime import timedelta
1111
from typing import List
@@ -129,6 +129,10 @@ def test_add_trace_not_resampling_insert_gaps():
129129

130130

131131
def test_various_dtypes(float_series):
132+
# skip the test on windows
133+
if sys.platform.lower().startswith("win"):
134+
pytest.skip("Skipping test on windows")
135+
132136
# List of dtypes supported by orjson >= 3.8
133137
valid_dtype_list = [
134138
np.bool_,
@@ -1192,6 +1196,9 @@ def test_showdash_not_hanging_when_port_in_use():
11921196

11931197

11941198
def test_manual_jupyterdashpersistentinline():
1199+
if sys.platform.lower().startswith("win"):
1200+
pytest.skip("This test is currently not supported on windows")
1201+
11951202
# Manually call the JupyterDashPersistentInline its method
11961203
# This requires some gimmicky stuff to mimmick the behaviour of a jupyter notebook.
11971204

tests/test_plotly_express.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import numpy as np
2+
import pandas as pd
3+
import plotly.express as px
4+
5+
from plotly_resampler import register_plotly_resampler, unregister_plotly_resampler
6+
7+
8+
def test_px_hoverlabel_figureResampler():
9+
labels = list(range(0, 3))
10+
N = 60_000
11+
x = np.arange(N)
12+
y = np.random.normal(size=N)
13+
label = np.random.randint(low=labels[0], high=labels[-1] + 1, size=N).astype(str)
14+
description = np.random.randint(low=3, high=5, size=N)
15+
16+
df = pd.DataFrame.from_dict(
17+
{"x": x, "y": y, "label": label, "description": description}
18+
)
19+
20+
x_label = "x"
21+
y_label = "y"
22+
label_label = "label"
23+
df = df.sort_values(by=[x_label])
24+
25+
# Without resampler, shows correct hover data
26+
fig = px.scatter(
27+
df,
28+
x=x_label,
29+
y=y_label,
30+
color=label_label,
31+
title="Without resampler",
32+
hover_data=["description"],
33+
)
34+
35+
# With resampler, shows incorrect hover data
36+
register_plotly_resampler(mode="auto", default_n_shown_samples=1000)
37+
fig2 = px.scatter(
38+
df,
39+
x=x_label,
40+
y=y_label,
41+
color=label_label,
42+
title="With resampler",
43+
hover_data=["description"],
44+
)
45+
46+
# verify whether the selected has the same y and customdata as the original
47+
for idx in range(len(fig.data)):
48+
trc_orig = fig.data[idx]
49+
trc_res = fig2.data[idx]
50+
51+
agg_indices = np.searchsorted(trc_orig["x"], trc_res["x"]).ravel()
52+
for k in ["customdata", "y"]:
53+
assert all(trc_orig[k].ravel()[agg_indices] == trc_res[k].ravel())
54+
55+
unregister_plotly_resampler()

0 commit comments

Comments
 (0)