Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
prisae opened this issue Nov 20, 2021 · 10 comments
Closed

Comments

@prisae
Copy link

prisae commented Nov 20, 2021

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

@jklymak
Copy link
Member

jklymak commented Nov 20, 2021

Do you know what version of Matplotlib this last worked with? Thanks!

@prisae
Copy link
Author

prisae commented Nov 20, 2021

It starts to fail with v3.5.0. v3.4.3 is still fine.

@tacaswell
Copy link
Member

tacaswell commented Nov 21, 2021

I think this is another bit of fallout from merging the internal representation of axes children from any lists to 1 list. Previously, the Arrow3D here would be in a list that was did not participate in the z-order sorting, and hence that it was missing one of the methods from the 3D api was missed. However, now that we have them merged, we reconstruct the old lists (which were nominally sorted by type) by picking out all of the children of the right type.

The fix here is to correct Arrow3D to have the missing method:

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):
        super().__init__((0,0), (0,0), *args, **kwargs)
        self._verts3d = xs, ys, zs

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

        return np.min(zs)

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)

plt.show()

@prisae
Copy link
Author

prisae commented Nov 22, 2021

This works fine, thanks @tacaswell !

@prisae prisae closed this as completed Nov 22, 2021
@QuLogic
Copy link
Member

QuLogic commented Nov 22, 2021

    def do_3d_projection(self, render=None):

That should be renderer, not render, though I don't believe anything calls it with the name explicitly.

phyy-nx added a commit to cctbx/dxtbx that referenced this issue Feb 3, 2022
Fixes #475

Thanks @ndevenish for the report and initial starting point at matplotlib/matplotlib#21688.  That implementation didn't give us the nice Z zoom that we want, so after some digging, set_zlim gets us there.  The call to nonsingular is what set_zlim does if zmin == zmax, as is the case for a flat detector.  It seems to expand out the Z a bit. Calling it directly avoids a UserWarning.

Also fix orthographic=True (not sure when this broke)
qwedso added a commit to qwedso/Understanding_Optics_with_Python that referenced this issue Aug 9, 2022
Since the initial release of the book there have been updates of Matplotlib. So the code for Listing 3-1 isn't working anymore. Changes are made with respect to matplotlib/matplotlib#21688 (comment)
@Siddhartha082
Copy link

Exact Code .. here

%pylab inline

from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
from matplotlib.patches import FancyArrowPatch
%matplotlib inline

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  do_3d_projection(self, renderer):
    xs3d, ys3d, zs3d = self._verts3d
    xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)
    self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))
    FancyArrowPatch.draw(self, renderer)

    return np.min(zs)

fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(111, projection='3d')

ax.plot(df['feature1'], df['feature2'], df['feature3'], 'o', markersize=8, color='blue', alpha=0.2)
ax.plot([df['feature1'].mean()], [df['feature2'].mean()], [df['feature3'].mean()], 'o', markersize=10, color='red', alpha=0.5)
for v in eigen_vectors.T:
a = Arrow3D([df['feature1'].mean(), v[0]], [df['feature2'].mean(), v[1]], [df['feature3'].mean(), v[2]], mutation_scale=20, lw=3, arrowstyle="-|>", color="r")
ax.add_artist(a)
ax.set_xlabel('x_values')
ax.set_ylabel('y_values')
ax.set_zlabel('z_values')

plt.title('Eigenvectors')

plt.show()

@tacaswell
Copy link
Member

@Siddhartha082 I do not understand what you are trying to say.

@aimlwithgautam
Copy link

Thanks you @tacaswell !

@Neombpure
Copy link

I think this is another bit of fallout from merging the internal representation of axes children from any lists to 1 list. Previously, the Arrow3D here would be in a list that was did not participate in the z-order sorting, and hence that it was missing one of the methods from the 3D api was missed. However, now that we have them merged, we reconstruct the old lists (which were nominally sorted by type) by picking out all of the children of the right type.

The fix here is to correct Arrow3D to have the missing method:

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):
        super().__init__((0,0), (0,0), *args, **kwargs)
        self._verts3d = xs, ys, zs

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

        return np.min(zs)

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)

plt.show()

NB,NB,NB

@gabemorris12
Copy link

Can this class just be added to the library?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants