diff --git a/doc/users/whats_new/axes3d_orthographic_projection.rst b/doc/users/whats_new/axes3d_orthographic_projection.rst new file mode 100644 index 000000000000..61242c43b755 --- /dev/null +++ b/doc/users/whats_new/axes3d_orthographic_projection.rst @@ -0,0 +1,3 @@ +Orthographic projection for mplot3d +----------------------------------- +:class:`~mpl_toolkits.mplot3d.axes3d.Axes3D` now accepts ``proj_type`` kwarg and has a method :meth:`~mpl_toolkits.mplot3d.axes3d.Axes3D.set_proj_type`. The default option is ``'persp'`` as before, and supplying ``'ortho'`` enables orthographic view. diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 664098934366..fa9080b825a2 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -63,6 +63,7 @@ def __init__(self, fig, rect=None, *args, **kwargs): *elev* Elevation viewing angle (default 30) *zscale* [%(scale)s] *sharez* Other axes to share z-limits with + *proj_type* 'persp' or 'ortho' (default 'persp') ================ ========================================= .. versionadded :: 1.2.1 @@ -78,6 +79,7 @@ def __init__(self, fig, rect=None, *args, **kwargs): self.initial_elev = kwargs.pop('elev', 30) zscale = kwargs.pop('zscale', None) sharez = kwargs.pop('sharez', None) + self.set_proj_type(kwargs.pop('proj_type', 'persp')) self.xy_viewLim = unit_bbox() self.zz_viewLim = unit_bbox() @@ -959,6 +961,23 @@ def view_init(self, elev=None, azim=None): else: self.azim = azim + def set_proj_type(self, proj_type): + """ + Set the projection type. + + Parameters + ---------- + proj_type : str + Type of projection, accepts 'persp' and 'ortho'. + + """ + if proj_type == 'persp': + self._projection = proj3d.persp_transformation + elif proj_type == 'ortho': + self._projection = proj3d.ortho_transformation + else: + raise ValueError("unrecognized projection: %s" % proj_type) + def get_proj(self): """ Create the projection matrix from the current viewing position. @@ -1001,9 +1020,9 @@ def get_proj(self): zfront, zback = -self.dist, self.dist viewM = proj3d.view_transformation(E, R, V) - perspM = proj3d.persp_transformation(zfront, zback) + projM = self._projection(zfront, zback) M0 = np.dot(viewM, worldM) - M = np.dot(perspM, M0) + M = np.dot(projM, M0) return M def mouse_init(self, rotate_btn=1, zoom_btn=3): diff --git a/lib/mpl_toolkits/mplot3d/proj3d.py b/lib/mpl_toolkits/mplot3d/proj3d.py index a24d44aa045b..eeb669ccc7f5 100644 --- a/lib/mpl_toolkits/mplot3d/proj3d.py +++ b/lib/mpl_toolkits/mplot3d/proj3d.py @@ -124,6 +124,16 @@ def persp_transformation(zfront, zback): [0,0,-1,0] ]) +def ortho_transformation(zfront, zback): + # note: w component in the resulting vector will be (zback-zfront), not 1 + a = -(zfront + zback) + b = -(zfront - zback) + return np.array([[2,0,0,0], + [0,2,0,0], + [0,0,-2,0], + [0,0,a,b] + ]) + def proj_transform_vec(vec, M): vecw = np.dot(M, vec) w = vecw[3] diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.pdf b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.pdf new file mode 100644 index 000000000000..080026b24eaf Binary files /dev/null and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.pdf differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png new file mode 100644 index 000000000000..d52add3671a9 Binary files /dev/null and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.png differ diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.svg b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.svg new file mode 100644 index 000000000000..0276e4bef94d --- /dev/null +++ b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/axes3d_ortho.svg @@ -0,0 +1,580 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube_ortho.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube_ortho.png new file mode 100644 index 000000000000..205ac97158aa Binary files /dev/null and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/proj3d_axes_cube_ortho.png differ diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index 2796a67b2993..b6a0692491c4 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -458,6 +458,33 @@ def test_proj_axes_cube(): ax.set_ylim(-0.2, 0.2) +@image_comparison(baseline_images=['proj3d_axes_cube_ortho'], + extensions=['png'], remove_text=True, style='default') +def test_proj_axes_cube_ortho(): + E = np.array([200, 100, 100]) + R = np.array([0, 0, 0]) + V = np.array([0, 0, 1]) + viewM = proj3d.view_transformation(E, R, V) + orthoM = proj3d.ortho_transformation(-1, 1) + M = np.dot(orthoM, viewM) + + ts = '0 1 2 3 0 4 5 6 7 4'.split() + xs = np.array([0, 1, 1, 0, 0, 0, 1, 1, 0, 0]) * 100 + ys = np.array([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) * 100 + zs = np.array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) * 100 + + txs, tys, tzs = proj3d.proj_transform(xs, ys, zs, M) + + fig, ax = _test_proj_draw_axes(M, s=150) + + ax.scatter(txs, tys, s=300-tzs) + ax.plot(txs, tys, c='r') + for x, y, t in zip(txs, tys, ts): + ax.text(x, y, t) + + ax.set_xlim(-200, 200) + ax.set_ylim(-200, 200) + def test_rot(): V = [1, 0, 0, 1] rotated_V = proj3d.rot_x(V, np.pi / 6) @@ -513,3 +540,10 @@ def test_autoscale(): ax.set_autoscalez_on(True) ax.plot([0, 2], [0, 2], [0, 2]) assert ax.get_w_lims() == (0, 1, -.1, 1.1, -.4, 2.4) + + +@image_comparison(baseline_images=['axes3d_ortho'], style='default') +def test_axes3d_ortho(): + fig = plt.figure() + ax = fig.gca(projection='3d') + ax.set_proj_type('ortho')