From 63cc0b28a7758ba1b528ab11058c4c74737dac33 Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 1 Jul 2026 18:13:25 +0200 Subject: [PATCH 1/4] return empty nan range instead of raising when trying to plot data with all nans --- src/plopp/core/limits.py | 4 ++- src/plopp/graphics/bbox.py | 7 ++++- tests/core/limits_test.py | 9 ++++-- tests/graphics/artists_test.py | 50 ++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/plopp/core/limits.py b/src/plopp/core/limits.py index 689770c6e..4378bab05 100644 --- a/src/plopp/core/limits.py +++ b/src/plopp/core/limits.py @@ -59,7 +59,7 @@ def find_limits( v = x.values finite_inds = np.isfinite(v) if np.sum(finite_inds) == 0: - raise ValueError("No finite values were found in array. Cannot compute limits.") + return (sc.scalar(np.nan, unit=x.unit), sc.scalar(np.nan, unit=x.unit)) finite_vals = v[finite_inds] finite_max = None if scale == "log": @@ -98,6 +98,8 @@ def fix_empty_range( """ Range correction in case xmin == xmax """ + if np.isnan(lims[0].value) or np.isnan(lims[1].value): + lims = (sc.scalar(0.0, unit=lims[0].unit), sc.scalar(0.0, unit=lims[1].unit)) if lims[0].value != lims[1].value: return lims if lims[0].value == 0.0: diff --git a/src/plopp/graphics/bbox.py b/src/plopp/graphics/bbox.py index b4e16481d..1b9a6bab4 100644 --- a/src/plopp/graphics/bbox.py +++ b/src/plopp/graphics/bbox.py @@ -7,6 +7,7 @@ from dataclasses import dataclass from typing import Literal +import numpy as np import scipp as sc from ..core.limits import find_limits, fix_empty_range @@ -108,7 +109,11 @@ def axis_bounds( Whether to pad the limits. """ try: - values = fix_empty_range(find_limits(x, scale=scale, pad=pad)) + values = find_limits(x, scale=scale, pad=pad) + if np.isnan(values[0].value) or np.isnan(values[1].value): + return dict.fromkeys(keys, None) + else: + values = fix_empty_range(values) except ValueError: return dict.fromkeys(keys, None) bounds = dict(zip(keys, (val.value for val in values), strict=True)) diff --git a/tests/core/limits_test.py b/tests/core/limits_test.py index c864d4564..1a6899cd9 100644 --- a/tests/core/limits_test.py +++ b/tests/core/limits_test.py @@ -53,12 +53,15 @@ def test_find_limits_with_ninf(): assert sc.identical(lims[1], sc.scalar(10.0, unit='m')) -def test_find_limits_no_finite_values_raises(): +def test_find_limits_no_finite_values_returns_nan_range(): da = sc.DataArray( data=sc.array(dims=['x'], values=[np.nan, np.inf, -np.inf, np.nan], unit='m') ) - with pytest.raises(ValueError, match="No finite values were found in array"): - _ = find_limits(da) + lims = find_limits(da) + assert np.isnan(lims[0].value) + assert np.isnan(lims[1].value) + assert lims[0].unit == 'm' + assert lims[1].unit == 'm' def test_find_limits_all_zeros(): diff --git a/tests/graphics/artists_test.py b/tests/graphics/artists_test.py index b2641b0a6..c3e1db2c1 100644 --- a/tests/graphics/artists_test.py +++ b/tests/graphics/artists_test.py @@ -3,7 +3,10 @@ from functools import partial +import numpy as np import pytest +import scipp as sc +from scipp.testing import assert_allclose import plopp as pp from plopp.data import examples @@ -84,3 +87,50 @@ def test_opacity(self, set_backend, backend, func, data): assert artist.opacity in (None, 1.0) artist.opacity = 0.5 assert artist.opacity == 0.5 + + +LINE_CASES = {k: v for k, v in CASES.items() if k.startswith("line")} + + +@pytest.mark.parametrize( + ("backend", "func", "data"), LINE_CASES.values(), ids=LINE_CASES.keys() +) +class TestLineBBox: + def test_line_bbox(self, set_backend, backend, func, data): + fig = func(**data) + [artist] = fig.artists.values() + bbox = artist.bbox(xscale='linear', yscale='linear') + # Tolerance of 3 because there is padding around the line + assert np.isclose(bbox.xmin, data['obj'].coords['x'].min().value, atol=3.0) + assert np.isclose(bbox.xmax, data['obj'].coords['x'].max().value, atol=3.0) + + if data['obj'].variances is not None: + ymin = (data['obj'] - sc.stddevs(data['obj'])).min() + else: + ymin = data['obj'].min() + # Tolerance of 0.2 because there is padding below the line + assert np.isclose(bbox.ymin, ymin.value, atol=0.2) + + if data['obj'].variances is not None: + ymax = (data['obj'] + sc.stddevs(data['obj'])).max() + else: + ymax = data['obj'].max() + # Tolerance of 0.2 because there is padding above the line + assert np.isclose(bbox.ymax, ymax.value, atol=0.2) + + +LINE_BACKENDS = {k: v[0] for k, v in CASES.items() if k.startswith("line")} + + +@pytest.mark.parametrize(("backend"), LINE_BACKENDS.values(), ids=LINE_BACKENDS.keys()) +class TestLineBBoxAllNan: + def test_line_bbox_all_nan(self, set_backend, backend): + a = pp.data.data1d() + a.values[...] = np.nan + fig = pp.plot(obj=a) + [artist] = fig.artists.values() + bbox = artist.bbox(xscale='linear', yscale='linear') + assert bbox.xmin is not None + assert bbox.xmax is not None + assert bbox.ymin is None + assert bbox.ymax is None From 43838cd48e26f456db881f022ed8085f71ee94cd Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 1 Jul 2026 18:14:28 +0200 Subject: [PATCH 2/4] add couple more tests --- tests/plotting/plot_1d_test.py | 6 ++++++ tests/plotting/plot_2d_test.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/tests/plotting/plot_1d_test.py b/tests/plotting/plot_1d_test.py index a3ccf4100..7eeb87532 100644 --- a/tests/plotting/plot_1d_test.py +++ b/tests/plotting/plot_1d_test.py @@ -703,3 +703,9 @@ def test_plot_multiple_inputs_custom_type_keys(): fig = pp.plot({A: a, B: b}) assert len(fig.view.artists) == 2 + + +def test_plot_data_with_all_nans_does_not_raise(): + da = data_array(ndim=1) + da.values[...] = np.nan + _ = da.plot() diff --git a/tests/plotting/plot_2d_test.py b/tests/plotting/plot_2d_test.py index 72ebee44b..ade08a496 100644 --- a/tests/plotting/plot_2d_test.py +++ b/tests/plotting/plot_2d_test.py @@ -516,3 +516,9 @@ def test_clabel(): da = data_array(ndim=2) fig = da.plot(clabel='MyColorLabel') assert fig.view.colormapper.clabel == 'MyColorLabel' + + +def test_plot_data_with_all_nans_does_not_raise(): + da = data_array(ndim=2) + da.values[...] = np.nan + _ = da.plot() From ebae2fda32d68409d9b382fbad8910c9c11746bc Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 1 Jul 2026 18:17:24 +0200 Subject: [PATCH 3/4] add slicer tests --- tests/plotting/slicer_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/plotting/slicer_test.py b/tests/plotting/slicer_test.py index 241d6aecd..72d8267d7 100644 --- a/tests/plotting/slicer_test.py +++ b/tests/plotting/slicer_test.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) +import numpy as np import pytest import scipp as sc from scipp.testing import assert_allclose, assert_identical @@ -240,6 +241,12 @@ def test_different_operations(self, operation, mode): da = data_array(ndim=2) SlicerPlot(da, keep=['xx'], mode=mode, operation=operation) + @pytest.mark.parametrize("mode", ["single", "range", "combined"]) + def test_slicer_with_first_all_nan_slice_does_not_raise(self, mode): + da = data_array(ndim=2) + da['yy', 0].values[...] = np.nan + SlicerPlot(da, keep=['xx'], mode=mode) + @pytest.mark.usefixtures("_parametrize_interactive_2d_backends") class TestSlicer2d: @@ -407,3 +414,9 @@ def test_create_with_non_dimension_coord(self): def test_different_operations(self, operation, mode): da = data_array(ndim=3) SlicerPlot(da, keep=['xx', 'yy'], mode=mode, operation=operation) + + @pytest.mark.parametrize("mode", ["single", "range", "combined"]) + def test_slicer_with_first_all_nan_slice_does_not_raise(self, mode): + da = data_array(ndim=3) + da['zz', 0].values[...] = np.nan + SlicerPlot(da, keep=['xx'], mode=mode) From ed348bca95f34749676c054e8ea2b9bc77cb57ce Mon Sep 17 00:00:00 2001 From: Neil Vaytet Date: Wed, 1 Jul 2026 18:21:26 +0200 Subject: [PATCH 4/4] remove unused imports --- tests/core/limits_test.py | 1 - tests/graphics/artists_test.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/core/limits_test.py b/tests/core/limits_test.py index 1a6899cd9..4af86e5d6 100644 --- a/tests/core/limits_test.py +++ b/tests/core/limits_test.py @@ -2,7 +2,6 @@ # Copyright (c) 2023 Scipp contributors (https://github.com/scipp) import numpy as np -import pytest import scipp as sc from plopp.core.limits import find_limits, fix_empty_range diff --git a/tests/graphics/artists_test.py b/tests/graphics/artists_test.py index c3e1db2c1..1a3b1ed82 100644 --- a/tests/graphics/artists_test.py +++ b/tests/graphics/artists_test.py @@ -6,7 +6,6 @@ import numpy as np import pytest import scipp as sc -from scipp.testing import assert_allclose import plopp as pp from plopp.data import examples