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
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
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:
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:
draw requires them to have .shape:
Behaviour by input and version:
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