diff --git a/docs/reference/changelog.md b/docs/reference/changelog.md index e66058ef..55188a98 100644 --- a/docs/reference/changelog.md +++ b/docs/reference/changelog.md @@ -48,6 +48,11 @@ We will make an effort to standardise and document our versioning policy. For no - Rename `coloralpha` to `opacity` for consistently with Plotly Express and deprecate the old parameter name ({gh-pr}`245`) - Rename `linewidth` to `line_width` for consistency with Plotly's API and deprecate the old parameter name ({gh-pr}`253`) +- Deprecated `colorscale='default'` and `list_all_colorscale_names()` in favour or Plotly Express' `px.colors.named_colorscales()` ({gh-pr}`262`) + +### Bug fixes + +- Fixed `ZeroDivisionError` for index-based colormodes when specifying single-trace or single-row plots ({gh-pr}`268`) ### Dependencies @@ -249,7 +254,6 @@ This release contains a number of improvements to the docs, API reference, CI/CD - The `colormode='index'` value has been deprecated in favor of `colormode='row-index'`, which provides the same functionality but is more explicit and allows to distinguish between the `'row-index'` and `'trace-index'` modes ({gh-pr}`114`) - The `show_annotations` argument has been deprecated in favor of `show_yticklabels` ({gh-pr}`114`) - The `get_all_colorscale_names()` function has been deprecated in favor of `list_all_colorscale_names()` ({gh-pr}`114`) -- Deprecated `colorscale='default'` and `list_all_colorscale_names()` in favour of Plotly Express' `px.colors.named_colorscales()` ({gh-pr}`262`) ### Features diff --git a/src/ridgeplot/_color/interpolation.py b/src/ridgeplot/_color/interpolation.py index 7ddaffe0..1980ec92 100644 --- a/src/ridgeplot/_color/interpolation.py +++ b/src/ridgeplot/_color/interpolation.py @@ -115,6 +115,8 @@ def _mul(a: tuple[Numeric, ...], b: tuple[Numeric, ...]) -> tuple[Numeric, ...]: def _interpolate_row_index(ctx: InterpolationContext) -> ColorscaleInterpolants: + if ctx.n_rows == 1: + return [[0.0] * ctx.n_traces] return [ [((ctx.n_rows - 1) - ith_row) / (ctx.n_rows - 1)] * len(row) for ith_row, row in enumerate(ctx.densities) @@ -122,6 +124,8 @@ def _interpolate_row_index(ctx: InterpolationContext) -> ColorscaleInterpolants: def _interpolate_trace_index(ctx: InterpolationContext) -> ColorscaleInterpolants: + if ctx.n_traces == 1: + return [[0.0]] ps = [] ith_trace = 0 for row in ctx.densities: @@ -135,7 +139,10 @@ def _interpolate_trace_index(ctx: InterpolationContext) -> ColorscaleInterpolant def _interpolate_trace_index_row_wise(ctx: InterpolationContext) -> ColorscaleInterpolants: return [ - [((len(row) - 1) - ith_row_trace) / (len(row) - 1) for ith_row_trace in range(len(row))] + [ + ((len(row) - 1) - ith_row_trace) / (len(row) - 1) if len(row) > 1 else 0.0 + for ith_row_trace in range(len(row)) + ] for row in ctx.densities ] diff --git a/tests/unit/color/test_interpolation.py b/tests/unit/color/test_interpolation.py index f3606b3b..5a7ae793 100644 --- a/tests/unit/color/test_interpolation.py +++ b/tests/unit/color/test_interpolation.py @@ -6,7 +6,10 @@ from ridgeplot import ridgeplot from ridgeplot._color.interpolation import ( + SOLID_COLORMODE_MAPS, + ColorscaleInterpolants, InterpolationContext, + SolidColormode, _interpolate_color, _interpolate_mean_means, _interpolate_mean_minmax, @@ -16,7 +19,7 @@ from ridgeplot._color.utils import to_rgb if TYPE_CHECKING: - from ridgeplot._types import ColorScale + from ridgeplot._types import ColorScale, Densities # ============================================================== @@ -94,6 +97,41 @@ def test_interpolate_mean_means() -> None: assert ps == [[0.0], [0.5], [1.0]] +_DENSITY_01 = [(0, 1), (1, 2), (2, 1)] +_DENSITY_02 = [(1, 1), (2, 2), (3, 1)] + +_DENSITIES_ONE_TRACE = [[_DENSITY_01]] +_DENSITIES_ONE_ROW = [[_DENSITY_01, _DENSITY_02]] +_DENSITIES_ONE_TRACE_PER_ROW = [[_DENSITY_01, _DENSITY_02], [_DENSITY_02]] + + +@pytest.mark.parametrize( + ("colormode", "densities", "expected"), + [ + # One trace + ("row-index", _DENSITIES_ONE_TRACE, [[0.0]]), + ("trace-index", _DENSITIES_ONE_TRACE, [[0.0]]), + ("trace-index-row-wise", _DENSITIES_ONE_TRACE, [[0.0]]), + # One row + ("row-index", _DENSITIES_ONE_ROW, [[0.0, 0.0]]), + ("trace-index", _DENSITIES_ONE_ROW, [[1.0, 0.0]]), + ("trace-index-row-wise", _DENSITIES_ONE_ROW, [[1.0, 0.0]]), + # One trace per row + ("row-index", _DENSITIES_ONE_TRACE_PER_ROW, [[1.0, 1.0], [0.0]]), + ("trace-index", _DENSITIES_ONE_TRACE_PER_ROW, [[1.0, 0.5], [0.0]]), + ("trace-index-row-wise", _DENSITIES_ONE_TRACE_PER_ROW, [[1.0, 0.0], [0.0]]), + ], +) +def test_index_based_colormodes( + colormode: SolidColormode, densities: Densities, expected: ColorscaleInterpolants +) -> None: + """ZeroDivisionError should never be raised, even when there is only one + trace, one row, or one trace per row.""" + interpolate_func = SOLID_COLORMODE_MAPS[colormode] + interpolants = interpolate_func(ctx=InterpolationContext.from_densities(densities)) + assert interpolants == expected + + # ============================================================== # --- _slice_colorscale() # ==============================================================