Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 829a9cc

Browse files
authored
Merge pull request #30980 from scottshambaugh/log3d
Fix 3D axes to properly support non-linear scales (log, symlog, etc.)
2 parents d3f5e11 + 3050aae commit 829a9cc

12 files changed

Lines changed: 815 additions & 142 deletions

File tree

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Non-linear scales on 3D axes
2+
----------------------------
3+
4+
Resolving a long-standing issue, 3D axes now support non-linear axis scales
5+
such as 'log', 'symlog', 'logit', 'asinh', and custom 'function' scales, just
6+
like 2D axes. Use `~.Axes3D.set_xscale`, `~.Axes3D.set_yscale`, and
7+
`~.Axes3D.set_zscale` to set the scale for each axis independently.
8+
9+
.. plot::
10+
:include-source: true
11+
:alt: A 3D plot with a linear x-axis, logarithmic y-axis, and symlog z-axis.
12+
13+
import matplotlib.pyplot as plt
14+
import numpy as np
15+
16+
# A sine chirp with increasing frequency and amplitude
17+
x = np.linspace(0, 1, 400) # time
18+
y = 10 ** (2 * x) # frequency, growing exponentially from 1 to 100 Hz
19+
phase = 2 * np.pi * (10 ** (2 * x) - 1) / (2 * np.log(10))
20+
z = np.sin(phase) * x ** 2 * 10 # amplitude, growing quadratically
21+
22+
fig = plt.figure()
23+
ax = fig.add_subplot(projection='3d')
24+
ax.plot(x, y, z)
25+
26+
ax.set_xlabel('Time (linear)')
27+
ax.set_ylabel('Frequency, Hz (log)')
28+
ax.set_zlabel('Amplitude (symlog)')
29+
30+
ax.set_yscale('log')
31+
ax.set_zscale('symlog')
32+
33+
plt.show()
34+
35+
See `matplotlib.scale` for details on all available scales and their parameters.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""
2+
================================
3+
Scales on 3D (Log, Symlog, etc.)
4+
================================
5+
6+
Demonstrate how to use non-linear scales such as logarithmic scales on 3D axes.
7+
8+
3D axes support the same axis scales as 2D plots: 'linear', 'log', 'symlog',
9+
'logit', 'asinh', and custom 'function' scales. This example shows a mix of
10+
scales: linear on X, log on Y, and symlog on Z.
11+
12+
For a complete list of built-in scales, see `matplotlib.scale`. For an overview
13+
of scale transformations, see :doc:`/gallery/scales/scales`.
14+
"""
15+
16+
import matplotlib.pyplot as plt
17+
import numpy as np
18+
19+
# A sine chirp with increasing frequency and amplitude
20+
x = np.linspace(0, 1, 400) # time
21+
y = 10 ** (2 * x) # frequency, growing exponentially from 1 to 100 Hz
22+
phase = 2 * np.pi * (10 ** (2 * x) - 1) / (2 * np.log(10))
23+
z = np.sin(phase) * x **2 * 10 # amplitude, growing quadratically
24+
25+
fig = plt.figure()
26+
ax = fig.add_subplot(projection='3d')
27+
ax.plot(x, y, z)
28+
29+
ax.set_xlabel('Time (linear)')
30+
ax.set_ylabel('Frequency, Hz (log)')
31+
ax.set_zlabel('Amplitude (symlog)')
32+
33+
ax.set_yscale('log')
34+
ax.set_zscale('symlog')
35+
36+
plt.show()
37+
38+
# %%
39+
#
40+
# .. admonition:: References
41+
#
42+
# The use of the following functions, methods, classes and modules is shown
43+
# in this example:
44+
#
45+
# - `mpl_toolkits.mplot3d.axes3d.Axes3D.set_xscale`
46+
# - `mpl_toolkits.mplot3d.axes3d.Axes3D.set_yscale`
47+
# - `mpl_toolkits.mplot3d.axes3d.Axes3D.set_zscale`
48+
# - `matplotlib.scale`
49+
#
50+
# .. tags::
51+
# plot-type: 3D,
52+
# level: beginner

galleries/examples/scales/scales.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
66
Illustrate the scale transformations applied to axes, e.g. log, symlog, logit.
77
8-
See `matplotlib.scale` for a full list of built-in scales, and
9-
:doc:`/gallery/scales/custom_scale` for how to create your own scale.
8+
See `matplotlib.scale` for a full list of built-in scales,
9+
:doc:`/gallery/scales/custom_scale` for how to create your own scale, and
10+
:doc:`/gallery/mplot3d/scales3d` for using scales on 3D axes.
1011
"""
1112

1213
import matplotlib.pyplot as plt

lib/mpl_toolkits/mplot3d/art3d.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def _viewlim_mask(xs, ys, zs, axes):
7979
Parameters
8080
----------
8181
xs, ys, zs : array-like
82-
The points to mask.
82+
The points to mask. These should be in data coordinates.
8383
axes : Axes3D
8484
The axes to use for the view limits.
8585
@@ -198,7 +198,10 @@ def draw(self, renderer):
198198
else:
199199
pos3d = np.array([self._x, self._y, self._z], dtype=float)
200200

201-
proj = proj3d._proj_trans_points([pos3d, pos3d + self._dir_vec], self.axes.M)
201+
dir_end = pos3d + self._dir_vec
202+
points = np.asarray([pos3d, dir_end])
203+
proj = proj3d._scale_proj_transform(
204+
points[:, 0], points[:, 1], points[:, 2], self.axes)
202205
dx = proj[0][1] - proj[0][0]
203206
dy = proj[1][1] - proj[1][0]
204207
angle = math.degrees(math.atan2(dy, dx))
@@ -334,9 +337,7 @@ def draw(self, renderer):
334337
dtype=float, mask=mask).filled(np.nan)
335338
else:
336339
xs3d, ys3d, zs3d = self._verts3d
337-
xs, ys, zs, tis = proj3d._proj_transform_clip(xs3d, ys3d, zs3d,
338-
self.axes.M,
339-
self.axes._focal_length)
340+
xs, ys, zs, tis = proj3d._scale_proj_transform_clip(xs3d, ys3d, zs3d, self.axes)
340341
self.set_data(xs, ys)
341342
super().draw(renderer)
342343
self.stale = False
@@ -427,7 +428,8 @@ def do_3d_projection(self):
427428
vs_list = [np.ma.array(vs, mask=np.broadcast_to(
428429
_viewlim_mask(*vs.T, self.axes), vs.shape))
429430
for vs in vs_list]
430-
xyzs_list = [proj3d.proj_transform(*vs.T, self.axes.M) for vs in vs_list]
431+
xyzs_list = [proj3d._scale_proj_transform(
432+
vs[:, 0], vs[:, 1], vs[:, 2], self.axes) for vs in vs_list]
431433
self._paths = [mpath.Path(np.ma.column_stack([xs, ys]), cs)
432434
for (xs, ys, _), (_, cs) in zip(xyzs_list, self._3dverts_codes)]
433435
zs = np.concatenate([zs for _, _, zs in xyzs_list])
@@ -497,6 +499,11 @@ def do_3d_projection(self):
497499
"""
498500
segments = np.asanyarray(self._segments3d)
499501

502+
# Handle empty segments
503+
if segments.size == 0:
504+
LineCollection.set_segments(self, [])
505+
return np.nan
506+
500507
mask = False
501508
if np.ma.isMA(segments):
502509
mask = segments.mask
@@ -511,8 +518,9 @@ def do_3d_projection(self):
511518
viewlim_mask = np.broadcast_to(viewlim_mask[..., np.newaxis],
512519
(*viewlim_mask.shape, 3))
513520
mask = mask | viewlim_mask
514-
xyzs = np.ma.array(proj3d._proj_transform_vectors(segments, self.axes.M),
515-
mask=mask)
521+
522+
xyzs = np.ma.array(
523+
proj3d._scale_proj_transform_vectors(segments, self.axes), mask=mask)
516524
segments_2d = xyzs[..., 0:2]
517525
LineCollection.set_segments(self, segments_2d)
518526

@@ -595,9 +603,7 @@ def do_3d_projection(self):
595603
dtype=float, mask=mask).filled(np.nan)
596604
else:
597605
xs, ys, zs = zip(*s)
598-
vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs,
599-
self.axes.M,
600-
self.axes._focal_length)
606+
vxs, vys, vzs, vis = proj3d._scale_proj_transform_clip(xs, ys, zs, self.axes)
601607
self._path2d = mpath.Path(np.ma.column_stack([vxs, vys]))
602608
return min(vzs)
603609

@@ -657,9 +663,7 @@ def do_3d_projection(self):
657663
dtype=float, mask=mask).filled(np.nan)
658664
else:
659665
xs, ys, zs = zip(*s)
660-
vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs,
661-
self.axes.M,
662-
self.axes._focal_length)
666+
vxs, vys, vzs, vis = proj3d._scale_proj_transform_clip(xs, ys, zs, self.axes)
663667
self._path2d = mpath.Path(np.ma.column_stack([vxs, vys]), self._code3d)
664668
return min(vzs)
665669

@@ -802,9 +806,7 @@ def do_3d_projection(self):
802806
xs, ys, zs = np.ma.array(self._offsets3d, mask=mask)
803807
else:
804808
xs, ys, zs = self._offsets3d
805-
vxs, vys, vzs, vis = proj3d._proj_transform_clip(xs, ys, zs,
806-
self.axes.M,
807-
self.axes._focal_length)
809+
vxs, vys, vzs, vis = proj3d._scale_proj_transform_clip(xs, ys, zs, self.axes)
808810
self._vzs = vzs
809811
if np.ma.isMA(vxs):
810812
super().set_offsets(np.ma.column_stack([vxs, vys]))
@@ -1020,9 +1022,7 @@ def do_3d_projection(self):
10201022
xyzs = np.ma.array(self._offsets3d, mask=mask)
10211023
else:
10221024
xyzs = self._offsets3d
1023-
vxs, vys, vzs, vis = proj3d._proj_transform_clip(*xyzs,
1024-
self.axes.M,
1025-
self.axes._focal_length)
1025+
vxs, vys, vzs, vis = proj3d._scale_proj_transform_clip(*xyzs, self.axes)
10261026
self._data_scale = _get_data_scale(vxs, vys, vzs)
10271027
# Sort the points based on z coordinates
10281028
# Performance optimization: Create a sorted index array and reorder
@@ -1356,7 +1356,7 @@ def do_3d_projection(self):
13561356
# Some faces might contain masked vertices, so we want to ignore any
13571357
# errors that those might cause
13581358
with np.errstate(invalid='ignore', divide='ignore'):
1359-
pfaces = proj3d._proj_transform_vectors(self._faces, self.axes.M)
1359+
pfaces = proj3d._scale_proj_transform_vectors(self._faces, self.axes)
13601360

13611361
if self._axlim_clip:
13621362
viewlim_mask = _viewlim_mask(self._faces[..., 0], self._faces[..., 1],

0 commit comments

Comments
 (0)