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

Fix regressions tests #299

Merged
merged 10 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Project specific
# ============================================================================

docs/_static/charts
docs/api/autogen
docs/api/public

Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ repos:
- id: pretty-format-json
args: [ --autofix, --no-sort-keys ]
# ignore jupyter notebooks
exclude: (^.*\.ipynb$|^pyrightconfig\.json$)
exclude: (^.*\.ipynb$|^pyrightconfig\.json$|^tests/e2e/artifacts/.*\.json$)
- id: pretty-format-json
args: [ --autofix, --no-sort-keys, --indent=1, --no-ensure-ascii ]
# only jupyter notebooks
Expand Down
6 changes: 4 additions & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ include docs/make.bat
include docs/robots.txt
include docs/_static/favicon.ico
recursive-include docs *.md *.py *.rst
recursive-include docs/_static/charts *.html *.webp *.jpeg
recursive-include docs/_static/charts *.html *.webp
recursive-include docs/_static/css *.css
recursive-include docs/_static/img *.png *.webp *.svg *.gif *.jpg
recursive-include docs/_static/js *.js
recursive-include docs/_templates *.html
prune docs/build
prune docs/_build
prune docs/_static/charts
prune docs/api/autogen
prune docs/api/public

Expand All @@ -37,6 +38,7 @@ recursive-include cicd_utils README.md *.py
recursive-include misc *.py *.ipynb *.txt *.png
recursive-include requirements *.txt
recursive-include tests *.py
recursive-include tests/e2e/artifacts *.jpeg *.json

# Globally excluded patterns
recursive-exclude * *.py[cod] *.egg-info __pycache__ .ipynb_checkpoints/*
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,16 @@ jupyter-init: install ## initialise a jupyter environment
# ==============================================================

.PHONY: clean-all
clean-all: clean-build clean-pyc clean-cov clean-ci-caches clean-tox clean-venv ## remove all artifacts
clean-all: clean-docs clean-build clean-pyc clean-cov clean-ci-caches clean-tox clean-venv ## remove all artifacts
@echo "==> Removed all artifacts!"


.PHONY: clean-docs
clean-docs: ## remove documentation build artifacts
@echo "==> Removing documentation build artifacts..."
rm -fr docs/_build/ docs/_static/charts docs/api/autogen/ docs/api/public/


.PHONY: clean-build
clean-build: ## remove build artifacts
@echo "==> Removing build artifacts..."
Expand Down
134 changes: 0 additions & 134 deletions cicd_utils/cicd/compile_plotly_charts.py

This file was deleted.

32 changes: 12 additions & 20 deletions cicd_utils/ridgeplot_examples/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,15 @@

from typing import TYPE_CHECKING

import plotly.graph_objects as go
from ridgeplot_examples._base import Example

if TYPE_CHECKING:
from collections.abc import Callable
import plotly.graph_objects as go


def tighten_margins(fig: go.Figure, px: int = 0) -> go.Figure:
"""Tighten the margins of a Plotly figure."""
if fig.layout.margin != go.layout.Margin():
# If the Figure's margins are different from the default values,
# we'll assume that the user has set these values intentionally
return fig
# If the Figure does not have a title, we'll leave 40px of space at the
# top to account for the Plotly toolbar. If the Figure does include
# a title, we'll leave the top margin with the default value.
margin_top = None if fig.layout.title.text else max(40, px)
return fig.update_layout(margin=dict(l=px, r=px, t=margin_top, b=px))
__all__ = [
"ALL_EXAMPLES",
"Example",
]


def load_basic() -> go.Figure:
Expand Down Expand Up @@ -51,10 +43,10 @@ def load_probly() -> go.Figure:
return main()


ALL_EXAMPLES: list[tuple[str, Callable[[], go.Figure]]] = [
("basic", load_basic),
("basic_hist", load_basic_hist),
("lincoln_weather", load_lincoln_weather),
("lincoln_weather_red_blue", load_lincoln_weather_red_blue),
("probly", load_probly),
ALL_EXAMPLES: list[Example] = [
Example("basic", load_basic),
Example("basic_hist", load_basic_hist),
Example("lincoln_weather", load_lincoln_weather),
Example("lincoln_weather_red_blue", load_lincoln_weather_red_blue),
Example("probly", load_probly),
]
154 changes: 154 additions & 0 deletions cicd_utils/ridgeplot_examples/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from __future__ import annotations

from copy import deepcopy
from dataclasses import dataclass
from typing import TYPE_CHECKING

import plotly.graph_objects as go
from minify_html import minify

if TYPE_CHECKING:
from collections.abc import Callable, Sequence
from pathlib import Path

from ridgeplot._types import NumericT


def tighten_margins(fig: go.Figure, px: int = 0) -> go.Figure:
"""Tighten the margins of a Plotly figure."""
if fig.layout.margin != go.layout.Margin():
# If the Figure's margins are different from the default values,
# we'll assume that the user has set these values intentionally
return fig
# If the Figure does not have a title, we'll leave 40px of space at the
# top to account for the Plotly toolbar. If the Figure does include
# a title, we'll leave the top margin with the default value.
margin_top = None if fig.layout.title.text else max(40, px)
return fig.update_layout(margin=dict(l=px, r=px, t=margin_top, b=px))


def round_sig_figs(x: NumericT, sig_figs: int) -> NumericT:
"""Round a float value to a fixed number of significant figures."""
cls = type(x)
rounded = float(f"{x:.{sig_figs}g}")
return cls(rounded)


def round_seq(seq: Sequence[NumericT], sig_figs: int) -> Sequence[NumericT]:
rounded_seq = [round_sig_figs(x, sig_figs) for x in seq]
if isinstance(seq, (list, tuple)):
return type(seq)(rounded_seq)
raise TypeError(f"Unsupported sequence type: {type(seq)}")


def round_nested_seq(
seq: Sequence[Sequence[NumericT]], sig_figs: int
) -> Sequence[Sequence[NumericT]]:
rounded_seq = [round_seq(sub_seq, sig_figs) for sub_seq in seq]
if isinstance(seq, (list, tuple)):
return type(seq)(rounded_seq)
raise TypeError(f"Unsupported sequence type: {type(seq)}")


def round_fig_data(fig: go.Figure, sig_figs: int) -> go.Figure:
"""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)):
for attr, round_fn in data_attrs.items():
if hasattr(fig.data[i], attr):
attr_val = getattr(fig.data[i], attr)
if attr_val is None:
continue
rounded_attr = round_fn(attr_val, sig_figs)
setattr(fig.data[i], attr, rounded_attr)
return fig


@dataclass
class Example:
plot_id: str
figure_factory: Callable[[], go.Figure]

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

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

if fig.layout.height is None:
raise ValueError("The Figure's layout.height value must be explicitly set.")
# Overriding the width to None results in a '100%' CSS width.
# This is achieved by setting the `default_width` parameter
# in `fig.to_html()` to "100%" (see below).
fig.layout.width = None

fig = tighten_margins(fig)

# Plotly.js (and MathJax.js if needed) should be included HTML's <head>
# of the Sphinx documentation. This can be done using the `html_js_files`
# configuration option in `conf.py`. For pages with multiple Plotly charts,
# this method is more efficient than including the Plotly.js (+ MathJax.js)
# source code in each HTML artefact. See `include_plotlyjs` below.
html_str = fig.to_html(
include_plotlyjs=False,
include_mathjax=False,
full_html=False,
default_width="100%",
div_id=f"plotly-id-{self.plot_id}",
)

# Wrap the chart's HTML in a <div> tag with a custom class name
# to allow for custom styling in the Sphinx documentation.
# See: https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_css_files
wrapper_div_class = "plotly-graph-wrapper"
html_str = f'<div class="{wrapper_div_class}">{html_str}</div>'
if minify_html:
# Need to set `minify_js` to False to avoid breaking
# the inner HTML set in the `hovertemplate` attribute
# TODO: Investigate if this is still necessary or find a workaround
# (reason: minify_js seems to make a significant difference)
html_str = minify(html_str, minify_js=False)

if not path.exists():
path.mkdir(parents=True)
out_path = path / f"{self.plot_id}.html"
out_path.write_text(html_str, "utf-8")

def to_webp(self, path: Path) -> None:
fig = deepcopy(self.fig)
fig = tighten_margins(fig, px=40)
if not path.exists():
path.mkdir(parents=True)
out_image = path / f"{self.plot_id}.webp"
fig.write_image(
out_image,
format="webp",
width=fig.layout.width,
height=fig.layout.height,
scale=3,
engine="kaleido",
)

def to_jpeg(self, path: Path) -> None:
fig = deepcopy(self.fig)
fig = tighten_margins(fig, px=40)
if not path.exists():
path.mkdir(parents=True)
out_image = path / f"{self.plot_id}.jpeg"
fig.write_image(
out_image,
format="jpeg",
width=fig.layout.width,
height=fig.layout.height,
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"
out_path.write_text(self.fig.to_json(), "utf-8")
Loading
Loading