The figure above was produced by this code
%matplotlib
import numpy as np
import matplotlib.pyplot as plt
x = y = np.array([1, 2])
fig = plt.figure(figsize=(5, 3))
ax1 = fig.add_subplot(111, projection='3d')
ax1.bar3d(x, y, [0,0], 0.5, 0.5, [1,1], shade=True, label='a')
ax1.bar3d(x, y, [1,1], 0.5, 0.5, [1,1], shade=True, label='b')
ax1.legend()
that asked also for a legend. As you can see, no legend but I've got this Traceback
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[1], line 12
10 ax1.bar3d(x, y, [0,0], 0.5, 0.5, [1,1], shade=True, label='a')
11 ax1.bar3d(x, y, [1,1], 0.5, 0.5, [1,1], shade=True, label='b')
---> 12 plt.legend()
File /usr/lib64/python3.11/site-packages/matplotlib/pyplot.py:2646, in legend(*args, **kwargs)
2644 @_copy_docstring_and_deprecators(Axes.legend)
2645 def legend(*args, **kwargs):
-> 2646 return gca().legend(*args, **kwargs)
File /usr/lib64/python3.11/site-packages/matplotlib/axes/_axes.py:313, in Axes.legend(self, *args, **kwargs)
311 if len(extra_args):
312 raise TypeError('legend only accepts two non-keyword arguments')
--> 313 self.legend_ = mlegend.Legend(self, handles, labels, **kwargs)
314 self.legend_._remove_method = self._remove_legend
315 return self.legend_
File /usr/lib64/python3.11/site-packages/matplotlib/_api/deprecation.py:454, in make_keyword_only.<locals>.wrapper(*args, **kwargs)
448 if len(args) > name_idx:
449 warn_deprecated(
450 since, message="Passing the %(name)s %(obj_type)s "
451 "positionally is deprecated since Matplotlib %(since)s; the "
452 "parameter will become keyword-only %(removal)s.",
453 name=name, obj_type=f"parameter of {func.__name__}()")
--> 454 return func(*args, **kwargs)
File /usr/lib64/python3.11/site-packages/matplotlib/legend.py:517, in Legend.__init__(self, parent, handles, labels, loc, numpoints, markerscale, markerfirst, scatterpoints, scatteryoffsets, prop, fontsize, labelcolor, borderpad, labelspacing, handlelength, handleheight, handletextpad, borderaxespad, columnspacing, ncols, mode, fancybox, shadow, title, title_fontsize, framealpha, edgecolor, facecolor, bbox_to_anchor, bbox_transform, frameon, handler_map, title_fontproperties, alignment, ncol)
514 self._alignment = alignment
516 # init with null renderer
--> 517 self._init_legend_box(handles, labels, markerfirst)
519 tmp = self._loc_used_default
520 self._set_loc(loc)
File /usr/lib64/python3.11/site-packages/matplotlib/legend.py:782, in Legend._init_legend_box(self, handles, labels, markerfirst)
779 text_list.append(textbox._text)
780 # Create the artist for the legend which represents the
781 # original artist/handle.
--> 782 handle_list.append(handler.legend_artist(self, orig_handle,
783 fontsize, handlebox))
784 handles_and_labels.append((handlebox, textbox))
786 columnbox = []
File /usr/lib64/python3.11/site-packages/matplotlib/legend_handler.py:119, in HandlerBase.legend_artist(self, legend, orig_handle, fontsize, handlebox)
95 """
96 Return the artist that this HandlerBase generates for the given
97 original artist/handle.
(...)
112
113 """
114 xdescent, ydescent, width, height = self.adjust_drawing_area(
115 legend, orig_handle,
116 handlebox.xdescent, handlebox.ydescent,
117 handlebox.width, handlebox.height,
118 fontsize)
--> 119 artists = self.create_artists(legend, orig_handle,
120 xdescent, ydescent, width, height,
121 fontsize, handlebox.get_transform())
123 if isinstance(artists, _Line2DHandleList):
124 artists = [artists[0]]
File /usr/lib64/python3.11/site-packages/matplotlib/legend_handler.py:806, in HandlerPolyCollection.create_artists(self, legend, orig_handle, xdescent, ydescent, width, height, fontsize, trans)
802 def create_artists(self, legend, orig_handle,
803 xdescent, ydescent, width, height, fontsize, trans):
804 p = Rectangle(xy=(-xdescent, -ydescent),
805 width=width, height=height)
--> 806 self.update_prop(p, orig_handle, legend)
807 p.set_transform(trans)
808 return [p]
File /usr/lib64/python3.11/site-packages/matplotlib/legend_handler.py:78, in HandlerBase.update_prop(self, legend_handle, orig_handle, legend)
76 def update_prop(self, legend_handle, orig_handle, legend):
---> 78 self._update_prop(legend_handle, orig_handle)
80 legend._set_artist_props(legend_handle)
81 legend_handle.set_clip_box(None)
File /usr/lib64/python3.11/site-packages/matplotlib/legend_handler.py:787, in HandlerPolyCollection._update_prop(self, legend_handle, orig_handle)
783 return None
785 # orig_handle is a PolyCollection and legend_handle is a Patch.
786 # Directly set Patch color attributes (must be RGBA tuples).
--> 787 legend_handle._facecolor = first_color(orig_handle.get_facecolor())
788 legend_handle._edgecolor = first_color(orig_handle.get_edgecolor())
789 legend_handle._original_facecolor = orig_handle._original_facecolor
File /usr/lib64/python3.11/site-packages/matplotlib/legend_handler.py:775, in HandlerPolyCollection._update_prop.<locals>.first_color(colors)
774 def first_color(colors):
--> 775 if colors.size == 0:
776 return (0, 0, 0, 0)
777 return tuple(colors[0])
AttributeError: 'tuple' object has no attribute 'size'
Can you help me at understanding what happened?
legend_handler.py
anaconda3\lib\site-packages\matplotlib\legend_handler.py
, line 89
orig_handle
is Poly3DCollection
, self._update_prop
occurs.
anaconda3\lib\site-packages\matplotlib\legend_handler.py
, line 795 calls def first_color
python 3.11.3
, matplotlib 3.7.1
colors
from bar3d
colors
is a tuple
, of np.arrays
, which doesn't have .size
method.legend_handle → Rectangle(xy=(-0, -0), width=20, height=7, angle=0)
orig_handle → <mpl_toolkits.mplot3d.art3d.Poly3DCollection object at 0x00000253CF287AD0>
# colors tuple of arrays, which causes AttributeError: 'tuple' object has no attribute 'size'
colors =\
(np.array([0.05065359, 0.19444443, 0.29411763, 1.]), np.array([0.06483662, 0.24888894, 0.37647067, 1.]), np.array([0.10738562, 0.41222224, 0.62352943, 1.]), np.array([0.05065359, 0.19444443, 0.29411763, 1.]), np.array([0.05065359, 0.19444443, 0.29411763, 1.]), np.array([0.0932026 , 0.35777772, 0.54117639, 1.]), np.array([0.06483662, 0.24888894, 0.37647067, 1.]), np.array([0.10738562, 0.41222224, 0.62352943, 1.]), np.array([0.10738562, 0.41222224, 0.62352943, 1.]), np.array([0.05065359, 0.19444443, 0.29411763, 1.]), np.array([0.0932026 , 0.35777772, 0.54117639, 1.]), np.array([0.10738562, 0.41222224, 0.62352943, 1.]))
legend_handler.py
colors = np.array(colors)
allows the legend to be created, however, I don't know what issues may arise.class HandlerPolyCollection(HandlerBase):
def _update_prop(self, legend_handle, orig_handle):
def first_color(colors):
print(colors) # added to check
colors = np.array(colors) # added to fix
if colors.size == 0:
return (0, 0, 0, 0)
return tuple(colors[0])
How to create a legend for 3D bar seems like a safer option.
x = y = np.array([1, 2])
fig = plt.figure(figsize=(5, 3))
ax1 = fig.add_subplot(111, projection='3d')
ax1.bar3d(x, y, [0,0], 0.5, 0.5, [1,1], shade=True, label='a', color='tab:blue')
blue_proxy = plt.Rectangle((0, 0), 1, 1, fc="tab:blue")
ax1.bar3d(x, y, [1,1], 0.5, 0.5, [1,1], shade=True, label='b', color='tab:orange')
orange_proxy = plt.Rectangle((0, 0), 1, 1, fc="tab:orange")
ax1.legend([blue_proxy, orange_proxy], ['a', 'b'], bbox_to_anchor=(1.1, 0.5), loc='center left', frameon=False)