Skip to content

[Bug]: AttributeError: 'Arrow3D' object has no attribute 'do_3d_projection' #21688

Closed
@prisae

Description

@prisae

Bug summary

New (appears in matplotlib=3.5) an AttributeError is thrown stating 'Arrow3D' object has no attribute 'do_3d_projection'.

The error appears when running, e.g., the nice example https://stackoverflow.com/a/29188796 by @tacaswell . Below I extracted a MWE from the stackoverflow code.

Code for reproduction

# MWE extracted from https://stackoverflow.com/a/29188796 by @tacaswell 

import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import proj3d

class Arrow3D(FancyArrowPatch):
    def __init__(self, xs, ys, zs, *args, **kwargs):
        FancyArrowPatch.__init__(self, (0,0), (0,0), *args, **kwargs)
        self._verts3d = xs, ys, zs

    def draw(self, renderer):
        xs3d, ys3d, zs3d = self._verts3d
        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
        self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))
        FancyArrowPatch.draw(self, renderer)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
arrow_prop_dict = dict(mutation_scale=20, arrowstyle='-|>', color='k', shrinkA=0, shrinkB=0)
a = Arrow3D([0, 10], [0, 0], [0, 0], **arrow_prop_dict)
ax.add_artist(a)

Actual outcome

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/anaconda3/lib/python3.8/site-packages/IPython/core/formatters.py in __call__(self, obj)
    339                 pass
    340             else:
--> 341                 return printer(obj)
    342             # Finally look for special method names
    343             method = get_real_method(obj, self.print_method)

~/anaconda3/lib/python3.8/site-packages/IPython/core/pylabtools.py in <lambda>(fig)
    248 
    249     if 'png' in formats:
--> 250         png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
    251     if 'retina' in formats or 'png2x' in formats:
    252         png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))

~/anaconda3/lib/python3.8/site-packages/IPython/core/pylabtools.py in print_figure(fig, fmt, bbox_inches, **kwargs)
    132         FigureCanvasBase(fig)
    133 
--> 134     fig.canvas.print_figure(bytes_io, **kw)
    135     data = bytes_io.getvalue()
    136     if fmt == 'svg':

~/anaconda3/lib/python3.8/site-packages/matplotlib/backend_bases.py in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2288                 )
   2289                 with getattr(renderer, "_draw_disabled", nullcontext)():
-> 2290                     self.figure.draw(renderer)
   2291 
   2292             if bbox_inches:

~/anaconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer, *args, **kwargs)
     71     @wraps(draw)
     72     def draw_wrapper(artist, renderer, *args, **kwargs):
---> 73         result = draw(artist, renderer, *args, **kwargs)
     74         if renderer._rasterizing:
     75             renderer.stop_rasterizing()

~/anaconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/lib/python3.8/site-packages/matplotlib/figure.py in draw(self, renderer)
   2801 
   2802             self.patch.draw(renderer)
-> 2803             mimage._draw_list_compositing_images(
   2804                 renderer, self, artists, self.suppressComposite)
   2805 

~/anaconda3/lib/python3.8/site-packages/matplotlib/image.py in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    130     if not_composite or not has_images:
    131         for a in artists:
--> 132             a.draw(renderer)
    133     else:
    134         # Composite any adjacent images together

~/anaconda3/lib/python3.8/site-packages/matplotlib/artist.py in draw_wrapper(artist, renderer)
     48                 renderer.start_filter()
     49 
---> 50             return draw(artist, renderer)
     51         finally:
     52             if artist.get_agg_filter() is not None:

~/anaconda3/lib/python3.8/site-packages/mpl_toolkits/mplot3d/axes3d.py in draw(self, renderer)
    445                                     for axis in self._get_axis_list()) + 1
    446                 collection_zorder = patch_zorder = zorder_offset
--> 447                 for artist in sorted(collections_and_patches,
    448                                      key=do_3d_projection,
    449                                      reverse=True):

~/anaconda3/lib/python3.8/site-packages/mpl_toolkits/mplot3d/axes3d.py in do_3d_projection(artist)
    434                     "do_3d_projection() was deprecated in Matplotlib "
    435                     "%(since)s and will be removed %(removal)s.")
--> 436                 return artist.do_3d_projection(renderer)
    437 
    438             collections_and_patches = (

AttributeError: 'Arrow3D' object has no attribute 'do_3d_projection'

[@tacaswell edited to add markup]

Expected outcome

Should draw the fancy arrow.

Additional information

This worked before matplotlib 3.5; error appears in different Python versions and different backends.

Operating system

Linux

Matplotlib Version

3.5.0

Matplotlib Backend

module://ipykernel.pylab.backend_inline

Python version

No response

Jupyter version

No response

Installation

conda

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