Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/matplotlib/mpl-data/stylelib/classic.mplstyle
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,9 @@ axes.prop_cycle : cycler('color', 'bgrcmyk')
# single letter, long name, or
# web-style hex
axes.autolimit_mode : round_numbers
axes.xmargin : 0 # x margin. See `axes.Axes.margins`
axes.ymargin : 0 # y margin See `axes.Axes.margins`
axes.xmargin : 0 # x margin. See `axes.Axes.margins`
axes.ymargin : 0 # y margin. See `axes.Axes.margins`
axes.zmargin : 0 # z margin. See `axes.Axes.margins`
axes.spines.bottom : True
axes.spines.left : True
axes.spines.right : True
Expand Down
55 changes: 25 additions & 30 deletions lib/mpl_toolkits/mplot3d/axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,10 @@ def __init__(

self.xy_viewLim = Bbox.unit()
self.zz_viewLim = Bbox.unit()
xymargin = 0.05 * 10/11 # match mpl3.8 appearance
self.xy_dataLim = Bbox([[xymargin, xymargin],
[1 - xymargin, 1 - xymargin]])
# z-limits are encoded in the x-component of the Bbox, y is un-used
self.zz_dataLim = Bbox.unit()
self._xmargin = mpl.rcParams['axes.xmargin']
self._ymargin = mpl.rcParams['axes.ymargin']
self._zmargin = mpl.rcParams['axes.zmargin']
self._init_dataLims()

# inhibit autoscale_view until the axes are defined
# they can't be defined until Axes.__init__ has been called
Expand Down Expand Up @@ -602,6 +601,25 @@ def margins(self, *margins, x=None, y=None, z=None, tight=True):
scalez=(z is not None)
)

def _init_dataLims(self):
"""Reset dataLim bboxes for empty-plot defaults.

The per-axis padding ensures that the margin expansion in autoscale_view
cancels out exactly, landing on [0, 1] for empty plots.
"""
def pad(margin):
# Note margin is validated to be > -0.5 in rcParams and set_x/y/zmargin
return margin / (1 + 2 * margin) if margin > 0 else 0

xpad = pad(self._xmargin)
ypad = pad(self._ymargin)
zpad = pad(self._zmargin)
self.xy_dataLim = Bbox([[xpad, ypad],
[1 - xpad, 1 - ypad]])
# z-limits are encoded in the x-component of the Bbox, y is un-used
self.zz_dataLim = Bbox([[zpad, 0],
[1 - zpad, 1]])

def autoscale(self, enable=True, axis='both', tight=None):
"""
Convenience method for simple axis view autoscaling.
Expand Down Expand Up @@ -1142,18 +1160,6 @@ def _set_axis_scale(self, axis, value, **kwargs):
**kwargs
Forwarded to scale constructor.
"""
# For non-linear scales on the z-axis, switch from the [0, 1] +
# margin=0 representation to the same xymargin + margin=0.05
# representation that x/y use. Both produce identical linear limits,
# but only the xymargin form has valid positive lower bounds for log
# etc. This must happen before _set_axes_scale because that triggers
# autoscale_view internally.
if (axis is self.zaxis and value != 'linear'
and np.array_equal(self.zz_dataLim.get_points(), [[0, 0], [1, 1]])):
xymargin = 0.05 * 10/11
self.zz_dataLim = Bbox([[xymargin, xymargin],
[1 - xymargin, 1 - xymargin]])
self._zmargin = self._xmargin
axis._set_axes_scale(value, **kwargs)

def set_xscale(self, value, **kwargs):
Expand Down Expand Up @@ -1556,16 +1562,8 @@ def shareview(self, other):
def clear(self):
# docstring inherited.
super().clear()
if self._focal_length == np.inf:
self._zmargin = mpl.rcParams['axes.zmargin']
else:
self._zmargin = 0.

xymargin = 0.05 * 10/11 # match mpl3.8 appearance
self.xy_dataLim = Bbox([[xymargin, xymargin],
[1 - xymargin, 1 - xymargin]])
# z-limits are encoded in the x-component of the Bbox, y is un-used
self.zz_dataLim = Bbox.unit()
self._zmargin = mpl.rcParams['axes.zmargin'] # x, y are set in super().clear()
self._init_dataLims()
self._view_margin = 1/48 # default value to match mpl3.8
self.autoscale_view()

Expand Down Expand Up @@ -3235,9 +3233,6 @@ def scatter(self, xs, ys, zs=0, zdir='z', s=20, c=None, depthshade=None,
depthshade_minalpha=depthshade_minalpha,
axlim_clip=axlim_clip,
)
if self._zmargin < 0.05 and xs.size > 0:
self.set_zmargin(0.05)

self.auto_scale_xyz(xs, ys, zs, had_data)

return patches
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 70 additions & 7 deletions lib/mpl_toolkits/mplot3d/tests/test_axes3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def test_bar3d_colors():
ax.bar3d(xs, ys, zs, 1, 1, 1, color=c)


@mpl3d_image_comparison(['bar3d_shaded.png'], style='mpl20')
@mpl3d_image_comparison(['bar3d_shaded.png'], tol=0.035, style='mpl20')
def test_bar3d_shaded():
x = np.arange(4)
y = np.arange(5)
Expand Down Expand Up @@ -299,7 +299,6 @@ def test_contourf3d_extend(fig_test, fig_ref, extend, levels):

@mpl3d_image_comparison(['tricontour.png'], tol=0.02, style='mpl20')
def test_tricontour():
plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated
fig = plt.figure()

np.random.seed(19680801)
Expand Down Expand Up @@ -587,7 +586,7 @@ def test_marker_draw_order_view_rotated(fig_test, fig_ref):
ax.view_init(elev=0, azim=azim - 180, roll=0) # view rotated by 180 deg


@mpl3d_image_comparison(['plot_3d_from_2d.png'], tol=0.019, style='mpl20')
@mpl3d_image_comparison(['plot_3d_from_2d.png'], tol=0.03, style='mpl20')
def test_plot_3d_from_2d():
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
Expand Down Expand Up @@ -1509,7 +1508,7 @@ def test_alpha(self):
assert isinstance(poly, art3d.Poly3DCollection)

@mpl3d_image_comparison(['voxels-xyz.png'], remove_text=False, style='mpl20',
tol=0.002 if sys.platform == 'win32' else 0)
tol=0 if platform.machine() == 'x86_64' else 0.008)
def test_xyz(self):
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})

Expand Down Expand Up @@ -1739,7 +1738,7 @@ def test_errorbar3d():


@image_comparison(['stem3d.png'], style='mpl20',
tol=0 if platform.machine() == 'x86_64' else 0.008)
tol=0 if platform.machine() == 'x86_64' else 0.01)
def test_stem3d():
fig, axs = plt.subplots(2, 3, figsize=(8, 6),
constrained_layout=True,
Expand Down Expand Up @@ -2209,7 +2208,6 @@ def test_subfigure_simple():

@image_comparison(['computed_zorder.png'], remove_text=True, style='mpl20')
def test_computed_zorder():
plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated
fig = plt.figure()
ax1 = fig.add_subplot(221, projection='3d')
ax2 = fig.add_subplot(222, projection='3d')
Expand Down Expand Up @@ -2664,7 +2662,6 @@ def test_scatter_masked_color():

@mpl3d_image_comparison(['surface3d_zsort_inf.png'], style='mpl20')
def test_surface3d_zsort_inf():
plt.rcParams['axes3d.automargin'] = True # Remove when image is regenerated
fig = plt.figure()
ax = fig.add_subplot(projection='3d')

Expand Down Expand Up @@ -2994,6 +2991,72 @@ def test_scale3d_default_limits(scale, expected_lims):
np.testing.assert_allclose(get_lim(), expected_lims)


@pytest.mark.parametrize("scale, expected_lims", [
("linear", (0.041666666666666664, 0.9583333333333334)),
("log", (0.08519612008506527, 1.056386134839687)),
("symlog", (0.04166666666666668, 0.9583333333333334)),
("logit", (0.0746298566901612, 0.9253701433098389)),
("asinh", (0.04815235510959675, 0.9707897299792895)),
])
@mpl.style.context("default")
def test_scale3d_automargin_limits(scale, expected_lims):
"""Axis limits with data and automargin should be correct for each scale."""
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_xscale(scale)
ax.set_yscale(scale)
ax.set_zscale(scale)
ax.plot([0.1, 0.9], [0.1, 0.9], [0.1, 0.9])
fig.canvas.draw()

for get_lim in (ax.get_xlim, ax.get_ylim, ax.get_zlim):
np.testing.assert_allclose(get_lim(), expected_lims)


@pytest.mark.parametrize("scale, expected_lims", [
("linear", (0.08333333333333334, 0.9166666666666667)),
("log", (0.09552563816905349, 0.9421554435545909)),
("symlog", (0.08333333333333334, 0.9166666666666667)),
("logit", (0.09205683697189532, 0.9079431630281046)),
("asinh", (0.08516517850312533, 0.9199719582595468)),
])
@mpl.style.context("default")
def test_scale3d_zero_margin_limits(scale, expected_lims):
"""Axis limits with data and zero margins should be correct for each scale.

Limits are not exactly (0.1, 0.9) because view_margin still applies.
"""
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_xscale(scale)
ax.set_yscale(scale)
ax.set_zscale(scale)
ax.margins(0)
ax.plot([0.1, 0.9], [0.1, 0.9], [0.1, 0.9])
fig.canvas.draw()

for get_lim in (ax.get_xlim, ax.get_ylim, ax.get_zlim):
np.testing.assert_allclose(get_lim(), expected_lims)


@pytest.mark.parametrize("scale", ["linear", "log", "symlog", "logit", "asinh"])
@mpl.style.context("default")
def test_scale3d_explicit_limits(scale):
"""Explicitly set limits should be honored exactly for each scale."""
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_xscale(scale)
ax.set_yscale(scale)
ax.set_zscale(scale)
ax.set_xlim(0.1, 0.9)
ax.set_ylim(0.1, 0.9)
ax.set_zlim(0.1, 0.9)
fig.canvas.draw()

for get_lim in (ax.get_xlim, ax.get_ylim, ax.get_zlim):
np.testing.assert_allclose(get_lim(), (0.1, 0.9))


@check_figures_equal()
@pytest.mark.filterwarnings("ignore:Data has no positive values")
def test_scale3d_all_clipped(fig_test, fig_ref):
Expand Down
Loading