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

Skip to content

Commit 02d4fdf

Browse files
Docstring cleanup and coordinates explainer
1 parent 6b3de8f commit 02d4fdf

3 files changed

Lines changed: 73 additions & 59 deletions

File tree

lib/mpl_toolkits/mplot3d/art3d.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ def _apply_scale_transforms(xs, ys, zs, axes):
7676
"""
7777
Apply axis scale transforms to 3D coordinates.
7878
79-
Transforms data coordinates through scale transforms (log, symlog, etc.)
80-
to scaled coordinates for proper 3D projection. Preserves masked arrays.
79+
Transforms data coordinates to transformed coordinates (applying log,
80+
symlog, etc.) for 3D projection. Preserves masked arrays.
8181
"""
8282
def transform_coord(coord, axis):
8383
coord = np.asanyarray(coord)

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 70 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@
88
99
Module containing Axes3D, an object which can plot 3D objects on a
1010
2D matplotlib figure.
11+
12+
Coordinate Systems
13+
------------------
14+
3D plotting involves several coordinate transformations:
15+
16+
1. **Data coordinates**: The user's raw x, y, z values.
17+
18+
2. **Transformed coordinates**: Data coordinates after applying axis scale
19+
transforms (log, symlog, etc.). For linear scales, these equal data
20+
coordinates. Zoom/pan operations work in this space to ensure uniform
21+
behavior with non-linear scales.
22+
23+
3. **Normalized coordinates**: Transformed coordinates mapped to a [0, 1]
24+
unit cube based on the current axis limits.
25+
26+
4. **Projected coordinates**: 2D coordinates after applying the 3D to 2D
27+
projection matrix, ready for display.
28+
29+
Artists receive data in data coordinates, apply scale transforms internally
30+
via ``do_3d_projection()``, then project to 2D for rendering.
1131
"""
1232

1333
from collections import defaultdict
@@ -239,13 +259,10 @@ def _transformed_cube(self, vals):
239259
axis scale transforms before being projected.
240260
"""
241261
minx, maxx, miny, maxy, minz, maxz = vals
242-
# Transform from data space to scaled space
243-
x_trans = self.xaxis.get_transform()
244-
y_trans = self.yaxis.get_transform()
245-
z_trans = self.zaxis.get_transform()
246-
minx, maxx = x_trans.transform([minx, maxx])
247-
miny, maxy = y_trans.transform([miny, maxy])
248-
minz, maxz = z_trans.transform([minz, maxz])
262+
# Transform from data space to transformed coordinates
263+
minx, maxx = self.xaxis.get_transform().transform([minx, maxx])
264+
miny, maxy = self.yaxis.get_transform().transform([miny, maxy])
265+
minz, maxz = self.zaxis.get_transform().transform([minz, maxz])
249266
xyzs = [(minx, miny, minz),
250267
(maxx, miny, minz),
251268
(maxx, maxy, minz),
@@ -258,17 +275,14 @@ def _transformed_cube(self, vals):
258275

259276
def _update_transScale(self):
260277
"""
261-
Override transScale to always use identity/linear transforms.
278+
Override transScale to always use identity transforms.
262279
263-
In 3D axes, scale transforms (log, symlog, etc.) are applied during the
264-
3D projection in do_3d_projection() methods. The resulting 2D coordinates
265-
are already in normalized display space and should NOT go through
266-
additional log transforms. Using linear transforms here ensures that
267-
negative projected coordinates (which are valid in 3D projection) are
268-
not clipped by log transforms.
280+
In 2D axes, transScale applies scale transforms (log, symlog, etc.) to
281+
convert data coordinates to display coordinates. In 3D axes, scale
282+
transforms are applied to data coordinates before 3D projection via
283+
each axis's transform. The projected 2D coordinates are already in
284+
display space, so transScale must be identity to avoid double-scaling.
269285
"""
270-
# Always use identity transforms for 2D display, since 3D projection
271-
# already handles scale transforms internally
272286
self.transScale.set(
273287
mtransforms.blended_transform_factory(
274288
mtransforms.IdentityTransform(),
@@ -1344,39 +1358,42 @@ def _get_scaled_limits(self):
13441358
zmin, zmax = self.zaxis.get_transform().transform(self.get_zlim3d())
13451359
return xmin, xmax, ymin, ymax, zmin, zmax
13461360

1347-
def _inverse_scale_transform(self, x, y, z):
1361+
def _untransform_point(self, x, y, z):
13481362
"""
1349-
Apply inverse scale transforms to a point.
1363+
Convert a point from transformed coordinates to data coordinates.
13501364
1351-
Converts from scaled space back to data space.
1365+
Parameters
1366+
----------
1367+
x, y, z : float
1368+
A single point in transformed coordinates.
1369+
1370+
Returns
1371+
-------
1372+
x_data, y_data, z_data : float
1373+
The point in data coordinates.
13521374
"""
13531375
x_data = self.xaxis.get_transform().inverted().transform([x])[0]
13541376
y_data = self.yaxis.get_transform().inverted().transform([y])[0]
13551377
z_data = self.zaxis.get_transform().inverted().transform([z])[0]
13561378
return x_data, y_data, z_data
13571379

1358-
def _set_lims_from_scaled(self, xmin_s, xmax_s, ymin_s, ymax_s,
1359-
zmin_s, zmax_s):
1380+
def _set_lims_from_transformed(self, xmin_t, xmax_t, ymin_t, ymax_t,
1381+
zmin_t, zmax_t):
13601382
"""
1361-
Transform scaled limits back to data space, validate, and set.
1383+
Set axis limits from transformed coordinates.
13621384
1363-
Takes limits in scaled (transformed) space, converts back to data
1364-
space, applies limit_range_for_scale validation, and sets the axis
1365-
limits.
1385+
Converts limits from transformed coordinates back to data coordinates,
1386+
applies limit_range_for_scale validation, and sets the axis limits.
13661387
13671388
Parameters
13681389
----------
1369-
xmin_s, xmax_s, ymin_s, ymax_s, zmin_s, zmax_s : float
1370-
Axis limits in scaled space.
1390+
xmin_t, xmax_t, ymin_t, ymax_t, zmin_t, zmax_t : float
1391+
Axis limits in transformed coordinates.
13711392
"""
13721393
# Transform back to data space
1373-
x_inv = self.xaxis.get_transform().inverted()
1374-
y_inv = self.yaxis.get_transform().inverted()
1375-
z_inv = self.zaxis.get_transform().inverted()
1376-
1377-
xmin, xmax = x_inv.transform([xmin_s, xmax_s])
1378-
ymin, ymax = y_inv.transform([ymin_s, ymax_s])
1379-
zmin, zmax = z_inv.transform([zmin_s, zmax_s])
1394+
xmin, xmax = self.xaxis.get_transform().inverted().transform([xmin_t, xmax_t])
1395+
ymin, ymax = self.yaxis.get_transform().inverted().transform([ymin_t, ymax_t])
1396+
zmin, zmax = self.zaxis.get_transform().inverted().transform([zmin_t, zmax_t])
13801397

13811398
# Validate limits for scale constraints (e.g., positive for log scale)
13821399
xmin, xmax = self.xaxis._scale.limit_range_for_scale(
@@ -1395,13 +1412,10 @@ def get_proj(self):
13951412
"""Create the projection matrix from the current viewing position."""
13961413

13971414
# Transform to uniform world coordinates 0-1, 0-1, 0-1
1415+
box_aspect = self._roll_to_vertical(self._box_aspect)
13981416
# For non-linear scales, we use the scaled limits so the world
1399-
# transformation maps scaled coordinates (not data coordinates)
1417+
# transformation maps transformed coordinates (not data coordinates)
14001418
# to the unit cube
1401-
box_aspect = self._roll_to_vertical(self._box_aspect)
1402-
# Use scaled limits for the world transformation. This ensures that
1403-
# for non-linear scales (log, symlog, etc.), the world transformation
1404-
# maps scaled coordinates to the unit cube.
14051419
scaled_limits = self._get_scaled_limits()
14061420
worldM = proj3d.world_transformation(
14071421
*scaled_limits,
@@ -1663,19 +1677,19 @@ def _calc_coord(self, xv, yv, renderer=None):
16631677
else: # perspective projection
16641678
zv = -1 / self._focal_length
16651679

1666-
# Convert point on view plane to scaled coordinates
1680+
# Convert point on view plane to transformed coordinates
16671681
# (inv_transform returns scaled coords because M was built with scaled limits)
16681682
p1 = np.array(proj3d.inv_transform(xv, yv, zv, self.invM)).ravel()
16691683

16701684
# Get the vector from the camera to the point on the view plane
1671-
# Camera location is in data space, so transform it to scaled space
1685+
# Camera location is in data space, so transform it to transformed coordinates
16721686
cam_data = self._get_camera_loc()
16731687
cam_scaled = np.array(art3d._apply_scale_transforms(
16741688
cam_data[0], cam_data[1], cam_data[2], self)).ravel()
16751689
vec = cam_scaled - p1
16761690

1677-
# Get the pane locations for each of the axes (in data space)
1678-
# and transform to scaled space
1691+
# Get the pane locations for each of the axes in data space
1692+
# and transform to transformed coordinates
16791693
pane_locs_data = []
16801694
for axis in self._axis_map.values():
16811695
xys, loc = axis.active_pane()
@@ -1693,11 +1707,11 @@ def _calc_coord(self, xv, yv, renderer=None):
16931707
pane_idx = np.argmin(abs(scales))
16941708
scale = scales[pane_idx]
16951709

1696-
# Calculate the point on the closest pane (in scaled space)
1710+
# Calculate the point on the closest pane in transformed coordinates
16971711
p2_scaled = p1 - scale*vec
16981712

16991713
# Convert back to data coordinates
1700-
p2 = np.array(self._inverse_scale_transform(
1714+
p2 = np.array(self._untransform_point(
17011715
p2_scaled[0], p2_scaled[1], p2_scaled[2]))
17021716
return p2, pane_idx
17031717

@@ -1858,14 +1872,14 @@ def drag_pan(self, button, key, x, y):
18581872
R = -R / self._box_aspect * self._dist
18591873
duvw_projected = R.T @ np.array([du, dv, dw])
18601874

1861-
# Calculate pan distance in scaled space for proper non-linear scale handling
1875+
# Calculate pan distance in transformed coordinates for non-linear scale handling
18621876
minx, maxx, miny, maxy, minz, maxz = self._get_scaled_limits()
18631877
dx = (maxx - minx) * duvw_projected[0]
18641878
dy = (maxy - miny) * duvw_projected[1]
18651879
dz = (maxz - minz) * duvw_projected[2]
18661880

1867-
# Compute new limits in scaled space
1868-
self._set_lims_from_scaled(
1881+
# Compute new limits in transformed coordinates
1882+
self._set_lims_from_transformed(
18691883
minx + dx, maxx + dx,
18701884
miny + dy, maxy + dy,
18711885
minz + dz, maxz + dz)
@@ -1984,7 +1998,7 @@ def _scale_axis_limits(self, scale_x, scale_y, scale_z):
19841998
limits by scale factors. A scale factor > 1 zooms out and a scale
19851999
factor < 1 zooms in.
19862000
1987-
For non-linear scales, the scaling happens in scaled space to ensure
2001+
For non-linear scales, the scaling happens in transformed coordinates to ensure
19882002
uniform zoom behavior.
19892003
19902004
Parameters
@@ -1996,29 +2010,29 @@ def _scale_axis_limits(self, scale_x, scale_y, scale_z):
19962010
scale_z : float
19972011
Scale factor for the z data axis.
19982012
"""
1999-
# Get the axis centers and ranges (in scaled space for non-linear scales)
2013+
# Get the axis centers and ranges in transformed coordinates
20002014
cx, cy, cz, dx, dy, dz = self._get_w_centers_ranges()
20012015

2002-
# Compute new limits in scaled space and set
2003-
self._set_lims_from_scaled(
2016+
# Compute new limits in transformed coordinates and set
2017+
self._set_lims_from_transformed(
20042018
cx - dx*scale_x/2, cx + dx*scale_x/2,
20052019
cy - dy*scale_y/2, cy + dy*scale_y/2,
20062020
cz - dz*scale_z/2, cz + dz*scale_z/2)
20072021

20082022
def _get_w_centers_ranges(self):
20092023
"""
2010-
Get 3D world centers and axis ranges in scaled space.
2024+
Get 3D world centers and axis ranges in transformed coordinates.
20112025
20122026
For non-linear scales (log, symlog, etc.), centers and ranges are
2013-
computed in scaled coordinates to ensure uniform zoom/pan behavior.
2027+
computed in transformed coordinates to ensure uniform zoom/pan behavior.
20142028
"""
2015-
# Get limits in scaled space for proper zoom/pan with non-linear scales
2029+
# Get limits in transformed coordinates for proper zoom/pan with non-linear scales
20162030
minx, maxx, miny, maxy, minz, maxz = self._get_scaled_limits()
20172031
cx = (maxx + minx)/2
20182032
cy = (maxy + miny)/2
20192033
cz = (maxz + minz)/2
20202034

2021-
# Calculate range of axis limits in scaled space
2035+
# Calculate range of axis limits in transformed coordinates
20222036
dx = (maxx - minx)
20232037
dy = (maxy - miny)
20242038
dz = (maxz - minz)

lib/mpl_toolkits/mplot3d/axis3d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ def _draw_ticks(self, renderer, edgep1, centers, deltas, highs,
462462

463463
default_label_offset = 8. # A rough estimate
464464
points = deltas_per_point * deltas
465-
# All coordinates below are in scaled space for proper projection
465+
# All coordinates below are in transformed coordinates for proper projection
466466
for tick in ticks:
467467
# Get tick line positions
468468
pos = edgep1.copy()

0 commit comments

Comments
 (0)