diff --git a/doc/api/next_api_changes/2019-02-EW.rst b/doc/api/next_api_changes/2019-02-EW.rst new file mode 100644 index 000000000000..14c7d933f384 --- /dev/null +++ b/doc/api/next_api_changes/2019-02-EW.rst @@ -0,0 +1,5 @@ +``Axes3D.voxels`` now shades the resulting voxels +------------------------------------------------- + +See Whats new for details. The previous behavior can be achieved by passing +``shade=False``. diff --git a/doc/users/next_whats_new/2019-01-06-shaded-voxels.rst b/doc/users/next_whats_new/2019-01-06-shaded-voxels.rst new file mode 100644 index 000000000000..6a12b673007d --- /dev/null +++ b/doc/users/next_whats_new/2019-01-06-shaded-voxels.rst @@ -0,0 +1,42 @@ +``Axes3D.voxels`` now shades the resulting voxels +------------------------------------------------- + +The :meth:`~mpl_toolkits.mplot3d.Axes3D.voxels` method now takes a ``shade`` +parameter that defaults to ``True``. This shades faces based on their +orientation, behaving just like the matching parameters to +:meth:`~mpl_toolkits.mplot3d.Axes3D.trisurf` and +:meth:`~mpl_toolkits.mplot3d.Axes3D.bar3d`. +The plot below shows how this affects the output. + +.. plot:: + + import matplotlib.pyplot as plt + import numpy as np + from mpl_toolkits.mplot3d import Axes3D # noqa: F401 unused import + + # prepare some coordinates + x, y, z = np.indices((8, 8, 8)) + + # draw cuboids in the top left and bottom right corners, and a link between them + cube1 = (x < 3) & (y < 3) & (z < 3) + cube2 = (x >= 5) & (y >= 5) & (z >= 5) + link = abs(x - y) + abs(y - z) + abs(z - x) <= 2 + + # combine the objects into a single boolean array + voxels = cube1 | cube2 | link + + # set the colors of each object + colors = np.empty(voxels.shape, dtype=object) + colors[link] = 'red' + colors[cube1] = 'blue' + colors[cube2] = 'green' + + # and plot everything + fig = plt.figure(figsize=plt.figaspect(0.5)) + ax, ax_shaded = fig.subplots(1, 2, subplot_kw=dict(projection='3d')) + ax.voxels(voxels, facecolors=colors, edgecolor='k', shade=False) + ax.set_title("Unshaded") + ax_shaded.voxels(voxels, facecolors=colors, edgecolor='k', shade=True) + ax_shaded.set_title("Shaded (default)") + + plt.show() diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 43b5897b4229..763e44c50220 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -2729,7 +2729,8 @@ def calc_arrow(uvw, angle=15): quiver3D = quiver - def voxels(self, *args, facecolors=None, edgecolors=None, **kwargs): + def voxels(self, *args, facecolors=None, edgecolors=None, shade=True, + lightsource=None, **kwargs): """ ax.voxels([x, y, z,] /, filled, **kwargs) @@ -2776,6 +2777,17 @@ def voxels(self, *args, facecolors=None, edgecolors=None, **kwargs): - A 4D ndarray of rgb/rgba data, with the components along the last axis. + shade : bool + Whether to shade the facecolors. Defaults to True. Shading is + always disabled when *cmap* is specified. + + .. versionadded:: 3.1 + + lightsource : `~matplotlib.colors.LightSource` + The lightsource to use when *shade* is True. + + .. versionadded:: 3.1 + **kwargs Additional keyword arguments to pass onto :func:`~mpl_toolkits.mplot3d.art3d.Poly3DCollection` @@ -2849,9 +2861,9 @@ def _broadcast_color_arg(color, name): # points lying on corners of a square square = np.array([ [0, 0, 0], - [0, 1, 0], + [1, 0, 0], [1, 1, 0], - [1, 0, 0] + [0, 1, 0], ], dtype=np.intp) voxel_faces = defaultdict(list) @@ -2872,7 +2884,8 @@ def permutation_matrices(n): qinds = np.arange(qc) rinds = np.arange(rc) - square_rot = square.dot(permute.T) + square_rot_pos = square.dot(permute.T) + square_rot_neg = square_rot_pos[::-1] # iterate within the current plane for p in pinds: @@ -2885,7 +2898,7 @@ def permutation_matrices(n): p0 = permute.dot([p, q, 0]) i0 = tuple(p0) if filled[i0]: - voxel_faces[i0].append(p0 + square_rot) + voxel_faces[i0].append(p0 + square_rot_neg) # draw middle faces for r1, r2 in zip(rinds[:-1], rinds[1:]): @@ -2896,16 +2909,16 @@ def permutation_matrices(n): i2 = tuple(p2) if filled[i1] and not filled[i2]: - voxel_faces[i1].append(p2 + square_rot) + voxel_faces[i1].append(p2 + square_rot_pos) elif not filled[i1] and filled[i2]: - voxel_faces[i2].append(p2 + square_rot) + voxel_faces[i2].append(p2 + square_rot_neg) # draw upper faces pk = permute.dot([p, q, rc-1]) pk2 = permute.dot([p, q, rc]) ik = tuple(pk) if filled[ik]: - voxel_faces[ik].append(pk2 + square_rot) + voxel_faces[ik].append(pk2 + square_rot_pos) # iterate over the faces, and generate a Poly3DCollection for each # voxel @@ -2924,9 +2937,20 @@ def permutation_matrices(n): face[:, 2] = z[ind] faces.append(face) + # shade the faces + facecolor = facecolors[coord] + edgecolor = edgecolors[coord] + if shade: + normals = self._generate_normals(faces) + facecolor = self._shade_colors(facecolor, normals, lightsource) + if edgecolor is not None: + edgecolor = self._shade_colors( + edgecolor, normals, lightsource + ) + poly = art3d.Poly3DCollection(faces, - facecolors=facecolors[coord], - edgecolors=edgecolors[coord], + facecolors=facecolor, + edgecolors=edgecolor, **kwargs ) self.add_collection3d(poly) diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png index ed84237b5450..1b1348d0ab40 100644 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-alpha.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png index c4dc28a988de..fddbf5fe7353 100644 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-edge-style.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png index 6a139acc5e2d..16758aae1428 100644 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-named-colors.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png index ab8308cead26..2fa5ceb28398 100644 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-rgb-data.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png index 9889a775bfed..b23da1a36784 100644 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-simple.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png index 4e2ecc61462e..c69443628aaa 100644 Binary files a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/voxels-xyz.png differ