Skip to content

Commit

Permalink
Merge pull request #313 from tpvasconcelos/fix-plotly-regression
Browse files Browse the repository at this point in the history
Fix test regression for Plotly 6.0+
  • Loading branch information
tpvasconcelos authored Feb 3, 2025
2 parents 8de2ac9 + dda2c25 commit 982fbfd
Show file tree
Hide file tree
Showing 10 changed files with 36 additions and 37 deletions.
31 changes: 16 additions & 15 deletions cicd_utils/ridgeplot_examples/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def round_nested_seq(
raise TypeError(f"Unsupported sequence type: {type(seq)}")


def round_fig_data(fig: go.Figure, sig_figs: int) -> go.Figure:
def round_fig_data(fig: go.Figure, sig_figs: int) -> None:
"""Round the float values in the data of a Plotly figure."""
data_attrs = {"x": round_seq, "y": round_seq, "customdata": round_nested_seq}
for i in range(len(fig.data)):
Expand All @@ -61,7 +61,6 @@ def round_fig_data(fig: go.Figure, sig_figs: int) -> go.Figure:
continue
rounded_attr = round_fn(attr_val, sig_figs)
setattr(fig.data[i], attr, rounded_attr)
return fig


@dataclass
Expand All @@ -71,8 +70,20 @@ class Example:

def __post_init__(self) -> None:
self.fig = self.figure_factory() # pyright: ignore[reportUninitializedInstanceVariable]
round_fig_data(self.fig, sig_figs=8)

def to_html(self, path: Path, minify_html: bool) -> None:
def to_json(self) -> str:
return f"{self.fig.to_json()}\n"

def write_json(self, path: Path) -> None:
# We'll round the float values in the JSON to a fixed number of
# significant figures to make the regression tests more robust.
if not path.exists():
path.mkdir(parents=True)
out_path = path / f"{self.plot_id}.json"
out_path.write_text(self.to_json(), "utf-8")

def write_html(self, path: Path, minify_html: bool) -> None:
fig = deepcopy(self.fig)

if fig.layout.height is None:
Expand Down Expand Up @@ -114,7 +125,7 @@ def to_html(self, path: Path, minify_html: bool) -> None:
out_path = path / f"{self.plot_id}.html"
out_path.write_text(html_str, "utf-8")

def to_webp(self, path: Path) -> None:
def write_webp(self, path: Path) -> None:
fig = deepcopy(self.fig)
fig = tighten_margins(fig, px=40)
if not path.exists():
Expand All @@ -129,7 +140,7 @@ def to_webp(self, path: Path) -> None:
engine="kaleido",
)

def to_jpeg(self, path: Path) -> None:
def write_jpeg(self, path: Path) -> None:
fig = deepcopy(self.fig)
fig = tighten_margins(fig, px=40)
if not path.exists():
Expand All @@ -143,13 +154,3 @@ def to_jpeg(self, path: Path) -> None:
scale=1,
engine="kaleido",
)

def to_json(self, path: Path, sig_figs: int) -> None:
# We'll round the float values in the JSON to a fixed number of
# significant figures to make the regression tests more robust.
round_fig_data(self.fig, sig_figs=sig_figs)
if not path.exists():
path.mkdir(parents=True)
out_path = path / f"{self.plot_id}.json"
json_txt = f"{self.fig.to_json()}\n"
out_path.write_text(json_txt, "utf-8")
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,8 @@ def compile_all_plotly_charts() -> None:
path_charts = PATH_DOCS / "_static/charts"
print(f"Writing image artifacts to {path_charts}...")
for example in ALL_EXAMPLES:
example.to_html(path_charts, minify_html=True)
example.to_webp(path_charts)
example.write_html(path_charts, minify_html=True)
example.write_webp(path_charts)

# Fix the end-of-file markers in the generated HTML files
from pre_commit_hooks.end_of_file_fixer import main as end_of_file_fixer
Expand Down
4 changes: 3 additions & 1 deletion docs/reference/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ This document outlines the list of changes to ridgeplot between each release. Fo
Unreleased changes
------------------

- ...
### Internal

- Fix regression tests for Plotly 6.0+ by updating the JSON test artifacts ({gh-pr}`313`)

---

Expand Down
8 changes: 4 additions & 4 deletions tests/cicd_utils/test_ridgeplot_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_round_fig_data() -> None:
)
]
)
fig_rounded = round_fig_data(fig, 5)
assert fig_rounded.data[0].x == (123460, 123.46, 0.12346)
assert fig_rounded.data[0].y == (123460, 123.46, 0.12346)
assert fig_rounded.data[0].customdata == ([123460], [123.46], [0.12346])
round_fig_data(fig, 5)
assert fig.data[0].x == (123460, 123.46, 0.12346)
assert fig.data[0].y == (123460, 123.46, 0.12346)
assert fig.data[0].customdata == ([123460], [123.46], [0.12346])
2 changes: 1 addition & 1 deletion tests/e2e/artifacts/basic.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/e2e/artifacts/basic_hist.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/e2e/artifacts/lincoln_weather.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/e2e/artifacts/lincoln_weather_red_blue.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/e2e/artifacts/probly.json

Large diffs are not rendered by default.

16 changes: 6 additions & 10 deletions tests/e2e/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
import pytest

from ridgeplot_examples import ALL_EXAMPLES, Example
from ridgeplot_examples._base import round_fig_data

JSON_SIG_FIGS = 8

PATH_ROOT = Path(__file__).parents[2].resolve()
PATH_ARTIFACTS = PATH_ROOT / "tests/e2e/artifacts"
Expand All @@ -29,11 +26,9 @@ def test_examples_width_height_set(example: Example) -> None:


@pytest.mark.parametrize("example", ALL_EXAMPLES, ids=lambda e: e.plot_id)
def test_regressions(example: Example) -> None:
"""Verify that the rendered JPEG images match the current artifacts."""
def test_json_regressions(example: Example) -> None:
expected = (PATH_ARTIFACTS / f"{example.plot_id}.json").read_text()
fig = round_fig_data(example.fig, sig_figs=JSON_SIG_FIGS)
assert fig.to_dict() == json.loads(expected)
assert json.loads(example.to_json()) == json.loads(expected)


def _update_all_artifacts() -> None:
Expand All @@ -48,18 +43,19 @@ def _update_all_artifacts() -> None:
"""
for example in ALL_EXAMPLES:
print(f"Updating artifacts for: {example.plot_id!r}") # noqa: T201
# Save JSONs for regression tests
example.to_json(PATH_ARTIFACTS, sig_figs=JSON_SIG_FIGS)
example.write_json(PATH_ARTIFACTS)
# We also save JPEGs for visual inspection (e.g., in PRs)
# (Don't use JPEGs for regression tests because outputs
# will vary between Plotly versions and platforms)
example.to_jpeg(PATH_ARTIFACTS)
example.write_jpeg(PATH_ARTIFACTS)
# Just to keep things in sync with the docs, we should also
# regenerate the WebP images used there. These are tracked
# by Git because some are used in the README (which needs
# to be rendered on GitHub), otherwise they would be in
# the .gitignore file (like the HTML artifacts).
example.to_webp(PATH_CHARTS)
example.write_webp(PATH_CHARTS)


if __name__ == "__main__":
Expand Down

0 comments on commit 982fbfd

Please sign in to comment.