From 02a94c931ec5183c311aac9fe0edd32633649513 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 21 Dec 2023 14:56:29 -0700 Subject: [PATCH 1/8] get_offsets3d and set_offsets3d --- .../3d_collections_offset3d.rst | 8 ++ lib/mpl_toolkits/mplot3d/art3d.py | 114 ++++++++++++++++-- 2 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 doc/users/next_whats_new/3d_collections_offset3d.rst diff --git a/doc/users/next_whats_new/3d_collections_offset3d.rst b/doc/users/next_whats_new/3d_collections_offset3d.rst new file mode 100644 index 000000000000..af63d0bdaf55 --- /dev/null +++ b/doc/users/next_whats_new/3d_collections_offset3d.rst @@ -0,0 +1,8 @@ +3D Collections have `set_offset3d` and `get_offset3d` methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All 3D Collections (`Patch3DCollection`, `Path3DCollection`, +`Poly3DCollection`) now have `set_offset3d` and `get_offset3d` methods +which allow you to set and get the offset of the collection in data +coordinates. In other words, this allows you to set and get the position of the +of the collection points. diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 44585ccd05e7..a73c2b96f144 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -598,19 +598,20 @@ def set_3d_properties(self, zs, zdir): # Force the collection to initialize the face and edgecolors # just in case it is a scalarmappable with a colormap. self.update_scalarmappable() - offsets = self.get_offsets() + offsets = super().get_offsets() if len(offsets) > 0: xs, ys = offsets.T else: xs = [] ys = [] - self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) + self._zdir = zdir + self.set_offsets3d(np.ma.column_stack((xs, ys, np.atleast_1d(zs))), zdir) self._z_markers_idx = slice(-1) self._vzs = None self.stale = True def do_3d_projection(self): - xs, ys, zs = self._offsets3d + xs, ys, zs = self.get_offsets3d() vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, self.axes.M) self._vzs = vzs @@ -642,6 +643,30 @@ def get_edgecolor(self): return self.get_facecolor() return self._maybe_depth_shade_and_sort_colors(super().get_edgecolor()) + def set_offsets3d(self, offsets, zdir='z'): + """ + Set the 3d offsets for the collection. + + Parameters + ---------- + offsets : (N, 3) or (3,) array-like + The offsets to be set. + zdir : {'x', 'y', 'z'} + The axis in which to place the offsets. Default: 'z'. + See `.get_dir_vector` for a description of the values. + """ + return _set_offsets3d(self, offsets, zdir) + + def get_offsets3d(self): + """Return the 3d offsets for the collection. + + Returns + ------- + offsets : (N, 3) array + The offsets for the collection. + """ + return _get_offsets3d(self) + class Path3DCollection(PathCollection): """ @@ -696,13 +721,13 @@ def set_3d_properties(self, zs, zdir): # Force the collection to initialize the face and edgecolors # just in case it is a scalarmappable with a colormap. self.update_scalarmappable() - offsets = self.get_offsets() + offsets = super().get_offsets() if len(offsets) > 0: xs, ys = offsets.T else: xs = [] ys = [] - self._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) + self.set_offsets3d(np.ma.column_stack((xs, ys, np.atleast_1d(zs))), zdir) # In the base draw methods we access the attributes directly which # means we cannot resolve the shuffling in the getter methods like # we do for the edge and face colors. @@ -715,7 +740,6 @@ def set_3d_properties(self, zs, zdir): # Grab the current sizes and linewidths to preserve them. self._sizes3d = self._sizes self._linewidths3d = np.array(self._linewidths) - xs, ys, zs = self._offsets3d # Sort the points based on z coordinates # Performance optimization: Create a sorted index array and reorder @@ -751,7 +775,7 @@ def set_depthshade(self, depthshade): self.stale = True def do_3d_projection(self): - xs, ys, zs = self._offsets3d + xs, ys, zs = self.get_offsets3d() vxs, vys, vzs, vis = proj3d.proj_transform_clip(xs, ys, zs, self.axes.M) # Sort the points based on z coordinates @@ -818,6 +842,30 @@ def get_edgecolor(self): return self.get_facecolor() return self._maybe_depth_shade_and_sort_colors(super().get_edgecolor()) + def set_offsets3d(self, offsets, zdir='z'): + """ + Set the 3d offsets for the collection. + + Parameters + ---------- + offsets : (N, 3) or (3,) array-like + The offsets to be set. + zdir : {'x', 'y', 'z'} + The axis in which to place the offsets. Default: 'z'. + See `.get_dir_vector` for a description of the values. + """ + return _set_offsets3d(self, offsets, zdir) + + def get_offsets3d(self): + """Return the 3d offsets for the collection. + + Returns + ------- + offsets : (N, 3) array + The offsets for the collection. + """ + return _get_offsets3d(self) + def patch_collection_2d_to_3d(col, zs=0, zdir='z', depthshade=True): """ @@ -1113,6 +1161,30 @@ def get_edgecolor(self): self.do_3d_projection() return np.asarray(self._edgecolors2d) + def set_offsets3d(self, offsets, zdir='z'): + """ + Set the 3d offsets for the collection. + + Parameters + ---------- + offsets : (N, 3) or (3,) array-like + The offsets to be set. + zdir : {'x', 'y', 'z'} + The axis in which to place the offsets. Default: 'z'. + See `.get_dir_vector` for a description of the values. + """ + return _set_offsets3d(self, offsets, zdir) + + def get_offsets3d(self): + """Return the 3d offsets for the collection. + + Returns + ------- + offsets : (N, 3) array + The offsets for the collection. + """ + return _get_offsets3d(self) + def poly_collection_2d_to_3d(col, zs=0, zdir='z'): """ @@ -1167,6 +1239,34 @@ def rotate_axes(xs, ys, zs, zdir): return xs, ys, zs +def _set_offsets3d(col_3d, offsets, zdir='z'): + """ + Set the 3d offsets for the collection. + + Parameters + ---------- + offsets : (N, 3) or (3,) array-like + The offsets to be set. + zdir : {'x', 'y', 'z'} + The axis in which to place the offsets. Default: 'z'. + See `.get_dir_vector` for a description of the values. + """ + offsets = np.asanyarray(offsets) + if offsets.shape == (3,): # Broadcast (3,) -> (1, 3) but nothing else. + offsets = offsets[None, :] + xs = np.asanyarray(col_3d.convert_xunits(offsets[:, 0]), float) + ys = np.asanyarray(col_3d.convert_yunits(offsets[:, 1]), float) + zs = np.asanyarray(col_3d.convert_yunits(offsets[:, 2]), float) + col_3d._offsets3d = juggle_axes(xs, ys, np.atleast_1d(zs), zdir) + col_3d.stale = True + + +def _get_offsets3d(col3d): + """Return the offsets for the collection.""" + # Default to zeros in the no-offset (None) case + return np.zeros((1, 3)) if col3d._offsets3d is None else col3d._offsets3d + + 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 From 89e2b7fa8b2b7537651e36428803f800192b5f20 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 21 Dec 2023 18:17:25 -0700 Subject: [PATCH 2/8] Whats new --- doc/users/next_whats_new/3d_collections_offset3d.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/users/next_whats_new/3d_collections_offset3d.rst b/doc/users/next_whats_new/3d_collections_offset3d.rst index af63d0bdaf55..cbbf36b24f26 100644 --- a/doc/users/next_whats_new/3d_collections_offset3d.rst +++ b/doc/users/next_whats_new/3d_collections_offset3d.rst @@ -1,8 +1,8 @@ -3D Collections have `set_offset3d` and `get_offset3d` methods -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +3D Collections have ``set_offset3d`` and ``get_offset3d`` methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -All 3D Collections (`Patch3DCollection`, `Path3DCollection`, -`Poly3DCollection`) now have `set_offset3d` and `get_offset3d` methods +All 3D Collections (``Patch3DCollection``, ``Path3DCollection``, +``Poly3DCollection``) now have ``set_offset3d`` and ``get_offset3d`` methods which allow you to set and get the offset of the collection in data coordinates. In other words, this allows you to set and get the position of the of the collection points. From c6c01ff2985df484e30520c1ef19e38ca8c13d82 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Sun, 10 Mar 2024 21:33:02 +0100 Subject: [PATCH 3/8] Style fixes / improvements --- lib/mpl_toolkits/mplot3d/art3d.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index a73c2b96f144..fa6c4b867120 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -598,9 +598,9 @@ def set_3d_properties(self, zs, zdir): # Force the collection to initialize the face and edgecolors # just in case it is a scalarmappable with a colormap. self.update_scalarmappable() - offsets = super().get_offsets() - if len(offsets) > 0: - xs, ys = offsets.T + offsets2d = super().get_offsets() + if len(offsets2d) > 0: + xs, ys = offsets2d.T else: xs = [] ys = [] @@ -658,7 +658,8 @@ def set_offsets3d(self, offsets, zdir='z'): return _set_offsets3d(self, offsets, zdir) def get_offsets3d(self): - """Return the 3d offsets for the collection. + """ + Return the 3d offsets for the collection. Returns ------- @@ -850,14 +851,15 @@ def set_offsets3d(self, offsets, zdir='z'): ---------- offsets : (N, 3) or (3,) array-like The offsets to be set. - zdir : {'x', 'y', 'z'} - The axis in which to place the offsets. Default: 'z'. + zdir : {'x', 'y', 'z'}, default: 'z' + The axis in which to place the offsets. See `.get_dir_vector` for a description of the values. """ return _set_offsets3d(self, offsets, zdir) def get_offsets3d(self): - """Return the 3d offsets for the collection. + """ + Return the 3d offsets for the collection. Returns ------- @@ -1169,14 +1171,15 @@ def set_offsets3d(self, offsets, zdir='z'): ---------- offsets : (N, 3) or (3,) array-like The offsets to be set. - zdir : {'x', 'y', 'z'} - The axis in which to place the offsets. Default: 'z'. + zdir : {'x', 'y', 'z'}, default: 'z' + The axis in which to place the offsets. See `.get_dir_vector` for a description of the values. """ return _set_offsets3d(self, offsets, zdir) def get_offsets3d(self): - """Return the 3d offsets for the collection. + """ + Return the 3d offsets for the collection. Returns ------- @@ -1247,8 +1250,8 @@ def _set_offsets3d(col_3d, offsets, zdir='z'): ---------- offsets : (N, 3) or (3,) array-like The offsets to be set. - zdir : {'x', 'y', 'z'} - The axis in which to place the offsets. Default: 'z'. + zdir : {'x', 'y', 'z'}, default: 'z' + The axis in which to place the offsets. See `.get_dir_vector` for a description of the values. """ offsets = np.asanyarray(offsets) From 432d0c49380ec4356d224ab70eef989d8e8c276f Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:21:23 -0600 Subject: [PATCH 4/8] Apply suggestions from code review Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/mpl_toolkits/mplot3d/art3d.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index fa6c4b867120..4994cf61dc46 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -1246,6 +1246,11 @@ def _set_offsets3d(col_3d, offsets, zdir='z'): """ Set the 3d offsets for the collection. + .. note:: + + Since 3D collections have no common 3D base class, this function + factors out the common code for ``set_offests3d()`` methods. + Parameters ---------- offsets : (N, 3) or (3,) array-like @@ -1265,7 +1270,20 @@ def _set_offsets3d(col_3d, offsets, zdir='z'): def _get_offsets3d(col3d): - """Return the offsets for the collection.""" + """ + Return the offsets for the collection. + + .. note:: + + Since 3D collections have no common 3D base class, this function + factors out the common code for ``get_offests3d()`` methods. + + Usage pattern:: + + def get_offsets3d(self): + return _get_offsets3d(self) + + """ # Default to zeros in the no-offset (None) case return np.zeros((1, 3)) if col3d._offsets3d is None else col3d._offsets3d From 54bd2005128a7650cde578160a9e1d14b47bab86 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Mon, 11 Mar 2024 11:27:38 -0600 Subject: [PATCH 5/8] Code review modifications --- lib/mpl_toolkits/mplot3d/art3d.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 4994cf61dc46..28c1c13745b5 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -1247,7 +1247,7 @@ def _set_offsets3d(col_3d, offsets, zdir='z'): Set the 3d offsets for the collection. .. note:: - + Since 3D collections have no common 3D base class, this function factors out the common code for ``set_offests3d()`` methods. @@ -1272,20 +1272,19 @@ def _set_offsets3d(col_3d, offsets, zdir='z'): def _get_offsets3d(col3d): """ Return the offsets for the collection. - + .. note:: - + Since 3D collections have no common 3D base class, this function factors out the common code for ``get_offests3d()`` methods. - + Usage pattern:: - + def get_offsets3d(self): return _get_offsets3d(self) """ - # Default to zeros in the no-offset (None) case - return np.zeros((1, 3)) if col3d._offsets3d is None else col3d._offsets3d + return col3d._offsets3d def _zalpha(colors, zs): From ef29b4a54864d76192d26dbae0fa950ebfc3e4ea Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:51:24 -0600 Subject: [PATCH 6/8] Fix docs typos --- lib/mpl_toolkits/mplot3d/art3d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 28c1c13745b5..aa79d7d287cd 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -1249,7 +1249,7 @@ def _set_offsets3d(col_3d, offsets, zdir='z'): .. note:: Since 3D collections have no common 3D base class, this function - factors out the common code for ``set_offests3d()`` methods. + factors out the common code for ``set_offsets3d()`` methods. Parameters ---------- @@ -1276,7 +1276,7 @@ def _get_offsets3d(col3d): .. note:: Since 3D collections have no common 3D base class, this function - factors out the common code for ``get_offests3d()`` methods. + factors out the common code for ``get_offsets3d()`` methods. Usage pattern:: From e13bbbec6dc3f7e1bb19c0a2301a475fa0dcff74 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:22:26 -0600 Subject: [PATCH 7/8] Fix docs build? --- lib/mpl_toolkits/mplot3d/art3d.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index aa79d7d287cd..606b404ef7c6 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -1246,10 +1246,8 @@ def _set_offsets3d(col_3d, offsets, zdir='z'): """ Set the 3d offsets for the collection. - .. note:: - - Since 3D collections have no common 3D base class, this function - factors out the common code for ``set_offsets3d()`` methods. + Note: Since 3D collections have no common 3D base class, this function + factors out the common code for ``set_offsets3d()`` methods. Parameters ---------- @@ -1273,15 +1271,13 @@ def _get_offsets3d(col3d): """ Return the offsets for the collection. - .. note:: - - Since 3D collections have no common 3D base class, this function - factors out the common code for ``get_offsets3d()`` methods. + Note: Since 3D collections have no common 3D base class, this function + factors out the common code for ``get_offsets3d()`` methods. - Usage pattern:: + Usage pattern:: - def get_offsets3d(self): - return _get_offsets3d(self) + def get_offsets3d(self): + return _get_offsets3d(self) """ return col3d._offsets3d From e2b645468a4b1c5f8e3e58cdd54f2c73386644ee Mon Sep 17 00:00:00 2001 From: Scott Shambaugh <14363975+scottshambaugh@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:32:03 -0600 Subject: [PATCH 8/8] Fix docs build? --- lib/mpl_toolkits/mplot3d/art3d.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/art3d.py b/lib/mpl_toolkits/mplot3d/art3d.py index 606b404ef7c6..2b8fc3e0292f 100644 --- a/lib/mpl_toolkits/mplot3d/art3d.py +++ b/lib/mpl_toolkits/mplot3d/art3d.py @@ -1247,7 +1247,8 @@ def _set_offsets3d(col_3d, offsets, zdir='z'): Set the 3d offsets for the collection. Note: Since 3D collections have no common 3D base class, this function - factors out the common code for ``set_offsets3d()`` methods. + factors out the common code for `set_offsets3d` methods of different 3D + collections. Parameters ---------- @@ -1272,7 +1273,8 @@ def _get_offsets3d(col3d): Return the offsets for the collection. Note: Since 3D collections have no common 3D base class, this function - factors out the common code for ``get_offsets3d()`` methods. + factors out the common code for `get_offsets3d` methods of different 3D + collections. Usage pattern::