Skip to content

[Bug]: Line3D.draw requires .shape on _verts3d, but set_data_3d accepts and stores any array-like #32127

Description

@thc1006

Bug summary

Line3D.set_data_3d documents its parameters as array-like and accepts anything iterable, storing the arguments verbatim in _verts3d. Line3D.draw then reads self._verts3d[0].shape, so a list, which is array-like and passes the setter's own check, raises AttributeError during draw.

The .shape access lives inside if np.any(scale_mask):, so it is only reached when a coordinate is invalid for the current axis scale. A finite list draws fine, which is why this can sit unnoticed for a long time and then surface the first time a NaN appears.

This is new in 3.11. 3.10.9 has no such branch and draws the same input.

Code for reproduction

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(projection="3d")
(line,) = ax.plot([0.0], [0.0], [0.0])

line.set_data_3d([float("nan")], [float("nan")], [float("nan")])
fig.canvas.draw()

Actual outcome

Traceback (most recent call last):
  ...
  File ".../mpl_toolkits/mplot3d/art3d.py", line 363, in draw
    (len(self._verts3d), *self._verts3d[0].shape)
                          ^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'list' object has no attribute 'shape'

Expected outcome

The figure draws, as it does on 3.10.9 and as it does on 3.11.1 when the same values are passed as arrays:

import numpy as np
line.set_data_3d(np.array([np.nan]), np.array([np.nan]), np.array([np.nan]))
fig.canvas.draw()   # fine

Additional information

The two sides disagree about what _verts3d holds.

set_data_3d checks only that each argument is iterable and stores them as given:

for name, xyz in zip('xyz', args):
    if not np.iterable(xyz):
        raise RuntimeError(f'{name} must be a sequence')
self._verts3d = args

draw requires them to have .shape:

if np.any(scale_mask):
    mask = np.broadcast_to(
        scale_mask,
        (len(self._verts3d), *self._verts3d[0].shape)
    )

Behaviour by input and version:

coordinates _verts3d holds 3.10.9 3.11.1
finite lists draws draws
NaN lists draws AttributeError
NaN arrays draws draws

The narrowest fix is np.shape(self._verts3d[0]) in draw, which leaves what get_data_3d() returns unchanged. Converting in the setter also works but changes the stored type, so a caller that reads back what it passed would see something different. I have both verified locally and am happy to open a PR for the first.

Worth noting because it is how this was found: #22308 recommended set_data([a], [b]) followed by set_3d_properties([c]) as the way to update a Line3D, which produces exactly this state. Code written against that advice draws on 3.10 and raises on 3.11 as soon as a coordinate goes non-finite.

Operating system

Linux (Debian 13, kernel 6.12)

Matplotlib Version

3.11.1 (3.10.9 unaffected; the branch is present on main)

Matplotlib Backend

Agg

Python version

3.14

Jupyter version

N/A

Installation

pip

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions