diff --git a/doc/api/next_api_changes/deprecations/18302-ES.rst b/doc/api/next_api_changes/deprecations/18302-ES.rst new file mode 100644 index 000000000000..98ee8c841c34 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/18302-ES.rst @@ -0,0 +1,20 @@ +3D properties on renderers +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The properties of the 3D Axes that were placed on the Renderer during draw are +now deprecated: + +* ``renderer.M`` +* ``renderer.eye`` +* ``renderer.vvec`` +* ``renderer.get_axis_position`` + +These attributes are all available via `.Axes3D`, which can be accessed via +``self.axes`` on all `.Artist`\s. + +``renderer`` argument of ``do_3d_projection`` method for ``Collection3D``/``Patch3D`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``renderer`` argument for the ``do_3d_projection`` method on +``Collection3D`` and ``Patch3D`` is no longer necessary, and passing it during +draw is deprecated. diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 074e2a76ef80..7b812944cefe 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -135,8 +135,7 @@ def set_3d_properties(self, z=0, zdir='z'): def draw(self, renderer): position3d = np.array((self._x, self._y, self._z)) proj = proj3d.proj_trans_points( - [position3d, position3d + self._dir_vec], - renderer.M) + [position3d, position3d + self._dir_vec], self.axes.M) dx = proj[0][1] - proj[0][0] dy = proj[1][1] - proj[1][0] angle = math.degrees(math.atan2(dy, dx)) @@ -213,7 +212,7 @@ def get_data_3d(self): @artist.allow_rasterization def draw(self, renderer): xs3d, ys3d, zs3d = self._verts3d - xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M) + xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M) self.set_data(xs, ys) super().draw(renderer) self.stale = False @@ -297,13 +296,13 @@ def set_segments(self, segments): self._segments3d = segments super().set_segments([]) - def do_3d_projection(self, renderer): + @cbook._delete_parameter('3.4', 'renderer') + def do_3d_projection(self, renderer=None): """ Project the points according to renderer matrix. """ - xyslist = [ - proj3d.proj_trans_points(points, renderer.M) for points in - self._segments3d] + xyslist = [proj3d.proj_trans_points(points, self.axes.M) + for points in self._segments3d] segments_2d = [np.column_stack([xs, ys]) for xs, ys, zs in xyslist] LineCollection.set_segments(self, segments_2d) @@ -316,7 +315,7 @@ def do_3d_projection(self, renderer): @artist.allow_rasterization def draw(self, renderer, project=False): if project: - self.do_3d_projection(renderer) + self.do_3d_projection() super().draw(renderer) @@ -348,10 +347,12 @@ def get_path(self): def get_facecolor(self): return self._facecolor2d - def do_3d_projection(self, renderer): + @cbook._delete_parameter('3.4', 'renderer') + def do_3d_projection(self, renderer=None): s = self._segment3d xs, ys, zs = zip(*s) - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) + vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, + self.axes.M) self._path2d = mpath.Path(np.column_stack([vxs, vys])) # FIXME: coloring self._facecolor2d = self._facecolor3d @@ -372,10 +373,12 @@ def set_3d_properties(self, path, zs=0, zdir='z'): Patch3D.set_3d_properties(self, path.vertices, zs=zs, zdir=zdir) self._code3d = path.codes - def do_3d_projection(self, renderer): + @cbook._delete_parameter('3.4', 'renderer') + def do_3d_projection(self, renderer=None): s = self._segment3d xs, ys, zs = zip(*s) - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) + vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, + self.axes.M) self._path2d = mpath.Path(np.column_stack([vxs, vys]), self._code3d) # FIXME: coloring self._facecolor2d = self._facecolor3d @@ -481,9 +484,11 @@ def set_3d_properties(self, zs, zdir): self._edgecolor3d = self.get_edgecolor() self.stale = True - def do_3d_projection(self, renderer): + @cbook._delete_parameter('3.4', 'renderer') + def do_3d_projection(self, renderer=None): xs, ys, zs = self._offsets3d - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) + vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, + self.axes.M) fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else self._facecolor3d) @@ -585,9 +590,11 @@ def set_linewidth(self, lw): super().set_linewidth(lw) self._linewidth3d = self.get_linewidth() - def do_3d_projection(self, renderer): + @cbook._delete_parameter('3.4', 'renderer') + def do_3d_projection(self, renderer=None): xs, ys, zs = self._offsets3d - vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, renderer.M) + vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, + self.axes.M) fcs = (_zalpha(self._facecolor3d, vzs) if self._depthshade else self._facecolor3d) @@ -760,7 +767,8 @@ def set_sort_zpos(self, val): self._sort_zpos = val self.stale = True - def do_3d_projection(self, renderer): + @cbook._delete_parameter('3.4', 'renderer') + def do_3d_projection(self, renderer=None): """ Perform the 3D projection for this object. """ @@ -769,7 +777,7 @@ def do_3d_projection(self, renderer): self.update_scalarmappable() self._facecolors3d = self._facecolors - txs, tys, tzs = proj3d._proj_transform_vec(self._vec, renderer.M) + txs, tys, tzs = proj3d._proj_transform_vec(self._vec, self.axes.M) xyzlist = [(txs[sl], tys[sl], tzs[sl]) for sl in self._segslices] # This extra fuss is to re-order face / edge colors @@ -805,7 +813,7 @@ def do_3d_projection(self, renderer): # Return zorder value if self._sort_zpos is not None: zvec = np.array([[0], [0], [self._sort_zpos], [1]]) - ztrans = proj3d._proj_transform_vec(zvec, renderer.M) + ztrans = proj3d._proj_transform_vec(zvec, self.axes.M) return ztrans[2][0] elif tzs.size > 0: # FIXME: Some results still don't look quite right. @@ -892,13 +900,6 @@ def rotate_axes(xs, ys, zs, zdir): return xs, ys, zs -def _get_colors(c, num): - """Stretch the color argument to provide the required number *num*.""" - return np.broadcast_to( - mcolors.to_rgba_array(c) if len(c) else [0, 0, 0, 0], - (num, 4)) - - def _zalpha(colors, zs): """Modify the alphas of the color list according to depth.""" # FIXME: This only works well if the points for *zs* are well-spaced diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index baf15d41bec5..d732631a260e 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -399,36 +399,71 @@ def draw(self, renderer): # add the projection matrix to the renderer self.M = self.get_proj() - renderer.M = self.M - renderer.vvec = self.vvec - renderer.eye = self.eye - renderer.get_axis_position = self.get_axis_position - - # Calculate projection of collections and patches and zorder them. - # Make sure they are drawn above the grids. - zorder_offset = max(axis.get_zorder() - for axis in self._get_axis_list()) + 1 - for i, col in enumerate( - sorted(self.collections, - key=lambda col: col.do_3d_projection(renderer), - reverse=True)): - col.zorder = zorder_offset + i - for i, patch in enumerate( - sorted(self.patches, - key=lambda patch: patch.do_3d_projection(renderer), - reverse=True)): - patch.zorder = zorder_offset + i - - if self._axis3don: - # Draw panes first - for axis in self._get_axis_list(): - axis.draw_pane(renderer) - # Then axes - for axis in self._get_axis_list(): - axis.draw(renderer) + props3d = { + # To raise a deprecation, we need to wrap the attribute in a + # function, but binding that to an instance does not work, as you + # would end up with an instance-specific method. Properties are + # class-level attributes which *are* functions, so we do that + # instead. + # This dictionary comprehension creates deprecated properties for + # the attributes listed below, and they are temporarily attached to + # the _class_ in the `_setattr_cm` call. These can both be removed + # once the deprecation expires + name: cbook.deprecated('3.4', name=name, + alternative=f'self.axes.{name}')( + property(lambda self, _value=getattr(self, name): _value)) + for name in ['M', 'vvec', 'eye', 'get_axis_position'] + } - # Then rest - super().draw(renderer) + with cbook._setattr_cm(type(renderer), **props3d): + def do_3d_projection(artist): + """ + Call `do_3d_projection` on an *artist*, and warn if passing + *renderer*. + + For our Artists, never pass *renderer*. For external Artists, + in lieu of more complicated signature parsing, always pass + *renderer* and raise a warning. + """ + + if artist.__module__ == 'mpl_toolkits.mplot3d.art3d': + # Our 3D Artists have deprecated the renderer parameter, so + # avoid passing it to them; call this directly once the + # deprecation has expired. + return artist.do_3d_projection() + + cbook.warn_deprecated( + "3.4", + message="The 'renderer' parameter of " + "do_3d_projection() was deprecated in Matplotlib " + "%(since)s and will be removed %(removal)s.") + return artist.do_3d_projection(renderer) + + # Calculate projection of collections and patches and zorder them. + # Make sure they are drawn above the grids. + zorder_offset = max(axis.get_zorder() + for axis in self._get_axis_list()) + 1 + for i, col in enumerate( + sorted(self.collections, + key=do_3d_projection, + reverse=True)): + col.zorder = zorder_offset + i + for i, patch in enumerate( + sorted(self.patches, + key=do_3d_projection, + reverse=True)): + patch.zorder = zorder_offset + i + + if self._axis3don: + # Draw panes first + for axis in self._get_axis_list(): + axis.draw_pane(renderer) + # Then axes + for axis in self._get_axis_list(): + axis.draw(renderer) + + # Then rest + super().draw(renderer) def get_axis_position(self): vals = self.get_w_lims() diff --git a/lib/mpl_toolkits/mplot3d/axis3d.py b/lib/mpl_toolkits/mplot3d/axis3d.py index 110c2a9290fb..cfd66e8944b6 100644 --- a/lib/mpl_toolkits/mplot3d/axis3d.py +++ b/lib/mpl_toolkits/mplot3d/axis3d.py @@ -189,7 +189,7 @@ def _get_coord_info(self, renderer): maxs = maxs + deltas / 4. vals = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2] - tc = self.axes.tunit_cube(vals, renderer.M) + tc = self.axes.tunit_cube(vals, self.axes.M) avgz = [tc[p1][2] + tc[p2][2] + tc[p3][2] + tc[p4][2] for p1, p2, p3, p4 in self._PLANES] highs = np.array([avgz[2*i] < avgz[2*i+1] for i in range(3)]) @@ -237,8 +237,8 @@ def draw(self, renderer): edgep2 = edgep1.copy() edgep2[juggled[1]] = maxmin[juggled[1]] pep = np.asarray( - proj3d.proj_trans_points([edgep1, edgep2], renderer.M)) - centpt = proj3d.proj_transform(*centers, renderer.M) + proj3d.proj_trans_points([edgep1, edgep2], self.axes.M)) + centpt = proj3d.proj_transform(*centers, self.axes.M) self.line.set_data(pep[0], pep[1]) self.line.draw(renderer) @@ -270,7 +270,7 @@ def draw(self, renderer): axmask = [True, True, True] axmask[index] = False lxyz = move_from_center(lxyz, centers, labeldeltas, axmask) - tlx, tly, tlz = proj3d.proj_transform(*lxyz, renderer.M) + tlx, tly, tlz = proj3d.proj_transform(*lxyz, self.axes.M) self.label.set_position((tlx, tly)) if self.get_rotate_label(self.label.get_text()): angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx))) @@ -291,7 +291,7 @@ def draw(self, renderer): outerindex = 1 pos = move_from_center(outeredgep, centers, labeldeltas, axmask) - olx, oly, olz = proj3d.proj_transform(*pos, renderer.M) + olx, oly, olz = proj3d.proj_transform(*pos, self.axes.M) self.offsetText.set_text(self.major.formatter.get_offset()) self.offsetText.set_position((olx, oly)) angle = art3d._norm_text_angle(np.rad2deg(np.arctan2(dy, dx))) @@ -374,11 +374,11 @@ def draw(self, renderer): pos[tickdir] = ( edgep1[tickdir] + info['tick']['outward_factor'] * ticksign * tickdelta) - x1, y1, z1 = proj3d.proj_transform(*pos, renderer.M) + x1, y1, z1 = proj3d.proj_transform(*pos, self.axes.M) pos[tickdir] = ( edgep1[tickdir] - info['tick']['inward_factor'] * ticksign * tickdelta) - x2, y2, z2 = proj3d.proj_transform(*pos, renderer.M) + x2, y2, z2 = proj3d.proj_transform(*pos, self.axes.M) # Get position of label default_offset = 8. # A rough estimate @@ -389,7 +389,7 @@ def draw(self, renderer): axmask[index] = False pos[tickdir] = edgep1[tickdir] pos = move_from_center(pos, centers, labeldeltas, axmask) - lx, ly, lz = proj3d.proj_transform(*pos, renderer.M) + lx, ly, lz = proj3d.proj_transform(*pos, self.axes.M) tick_update_position(tick, (x1, x2), (y1, y2), (lx, ly)) tick.tick1line.set_linewidth(