diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index b0d7312bff3d..315ace83f306 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -358,9 +358,12 @@ def draw(self, renderer): if self._axlim_clip: scale_mask |= _viewlim_mask(*self._verts3d, self.axes) if np.any(scale_mask): + # np.shape rather than .shape: set_data_3d documents its + # parameters as array-like and stores whatever it is given, so + # _verts3d can hold lists or tuples. mask = np.broadcast_to( scale_mask, - (len(self._verts3d), *self._verts3d[0].shape) + (len(self._verts3d), *np.shape(self._verts3d[0])) ) xs3d, ys3d, zs3d = np.ma.array(self._verts3d, dtype=float, mask=mask).filled(np.nan) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py index aca943f9e0c0..f8f47dad806d 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py @@ -13,6 +13,42 @@ ) +@pytest.mark.parametrize("container", [list, tuple]) +@pytest.mark.parametrize( + "case", ["nan", "inf", "log_domain", "axlim_clip", "one_of_many"]) +def test_line3d_draws_array_like_coordinates(container, case): + # set_data_3d documents its parameters as array-like and stores whatever it + # is given, so _verts3d can hold lists or tuples. Drawing must not require + # an ndarray. + # + # The masking branch that reads a shape is entered when a coordinate is + # non-finite, outside the active scale's domain, or outside the view limits + # with axlim_clip enabled, so each of those is a way in. + fig = plt.figure() + ax = fig.add_subplot(projection="3d") + line, = ax.plot([1.], [1.], [1.], + axlim_clip=(case == "axlim_clip")) + + if case == "nan": + xs, ys, zs = [np.nan], [1.], [1.] + elif case == "inf": + xs, ys, zs = [np.inf], [1.], [1.] + elif case == "log_domain": + # Finite, and invalid only because of the scale. + ax.set_xscale("log") + xs, ys, zs = [0.], [1.], [1.] + elif case == "axlim_clip": + # Finite and valid for the scale; masked for being out of view. + xs, ys, zs = [1e6], [1e6], [1e6] + else: + # A line where only one vertex is masked. + xs, ys, zs = [1., np.nan], [1., 2.], [1., 2.] + + line.set_data_3d(container(xs), container(ys), container(zs)) + + fig.canvas.draw() + + @pytest.mark.parametrize("zdir, expected", [ ("x", (1, 0, 0)), ("y", (0, 1, 0)),