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_3ddocuments its parameters asarray-likeand accepts anything iterable, storing the arguments verbatim in_verts3d.Line3D.drawthen readsself._verts3d[0].shape, so a list, which is array-like and passes the setter's own check, raisesAttributeErrorduring draw.The
.shapeaccess lives insideif 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
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
_verts3dholds.set_data_3dchecks only that each argument is iterable and stores them as given:drawrequires them to have.shape:Behaviour by input and version:
_verts3dholdsAttributeErrorThe narrowest fix is
np.shape(self._verts3d[0])indraw, which leaves whatget_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 byset_3d_properties([c])as the way to update aLine3D, 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