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

Skip to content

Commit f0da0cf

Browse files
committed
Don't let margins expand polar plots to negative radii by default.
This is implemented by altering the semantics of sticky edges as documented in the changelog. As it turns out, this change also improves consistency for streamplot(): - in test_streamplot.py::test_{linewidth,mask_and_nans}, the change is necessary because the axes limits would now be (-3, 3) (matching the vector field limits), whereas they were previously xlim=(-3.0, 2.9999999999999947), ylim=(-3.0000000000000004, 2.9999999999999947) (they can be inspected with `plt.gcf().canvas.draw(); print(plt.gca().get_xlim(), plt.gca().get_ylim())`). - in test_streamplot.py::test_maxlength, note that the previous version expanded the axes limits *beyond* (-3, 3), whereas the current doesn't do so anymore. It doesn't actually make much sense if the vector field limits are applied if the streamplot goes all the way to the edges, but are ignored otherwise.
1 parent 76db501 commit f0da0cf

File tree

5 files changed

+62
-20
lines changed

5 files changed

+62
-20
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Change in the application of ``Artist.sticky_edges``
2+
````````````````````````````````````````````````````
3+
4+
Previously, the ``sticky_edges`` attribute of artists was a list of values such
5+
that if an axis limit coincides with a sticky edge, it would not be expanded by
6+
the axes margins (this is the mechanism that e.g. prevents margins from being
7+
added around images).
8+
9+
``sticky_edges`` now have an additional effect on margins application: even if
10+
an axis limit did not coincide with a sticky edge, it cannot *cross* a sticky
11+
edge through margin application -- instead, the margins will only expand the
12+
axis limit until it bumps against the sticky edge.
13+
14+
This change improves the margins of axes displaying a `~Axes.streamplot`:
15+
16+
- if the streamplot goes all the way to the edges of the vector field, then the
17+
axis limits are set to match exactly the vector field limits (whereas they
18+
would be sometimes be off by a small floating point error previously).
19+
- if the streamplot does not reach the edges of the vector field (e.g., due to
20+
the use of ``start_points`` and ``maxlength``), then margins expansion will
21+
not cross the the vector field limits anymore.
22+
23+
This change is also used internally to ensure that polar plots don't display
24+
negative *r* values unless the user really passes in a negative value.

lib/matplotlib/axes/_base.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2402,14 +2402,14 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True):
24022402
(self._xmargin and scalex and self._autoscaleXon) or
24032403
(self._ymargin and scaley and self._autoscaleYon)):
24042404
stickies = [artist.sticky_edges for artist in self.get_children()]
2405-
x_stickies = np.array([x for sticky in stickies for x in sticky.x])
2406-
y_stickies = np.array([y for sticky in stickies for y in sticky.y])
2405+
x_stickies = np.sort([x for sticky in stickies for x in sticky.x])
2406+
y_stickies = np.sort([y for sticky in stickies for y in sticky.y])
24072407
if self.get_xscale().lower() == 'log':
24082408
x_stickies = x_stickies[x_stickies > 0]
24092409
if self.get_yscale().lower() == 'log':
24102410
y_stickies = y_stickies[y_stickies > 0]
24112411
else: # Small optimization.
2412-
x_stickies, y_stickies = [], []
2412+
x_stickies = y_stickies = np.array([])
24132413

24142414
def handle_single_axis(scale, autoscaleon, shared_axes, interval,
24152415
minpos, axis, margin, stickies, set_bound):
@@ -2450,29 +2450,33 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval,
24502450
locator = axis.get_major_locator()
24512451
x0, x1 = locator.nonsingular(x0, x1)
24522452

2453+
# Prevent margin addition from crossing a sticky value. Small
2454+
# tolerances (whose value come from isclose()) must be used due to
2455+
# floating point issues with streamplot.
2456+
# Index of largest element < x0 + tol, if any.
2457+
i0 = stickies.searchsorted(x0 + 1e-5 * abs(x0) + 1e-8) - 1
2458+
x0bound = stickies[i0] if i0 != -1 else None
2459+
# Index of smallest element > x1 - tol, if any.
2460+
i1 = stickies.searchsorted(x1 - 1e-5 * abs(x1) - 1e-8)
2461+
x1bound = stickies[i1] if i1 != len(stickies) else None
2462+
24532463
# Add the margin in figure space and then transform back, to handle
24542464
# non-linear scales.
24552465
minpos = getattr(bb, minpos)
24562466
transform = axis.get_transform()
24572467
inverse_trans = transform.inverted()
2458-
# We cannot use exact equality due to floating point issues e.g.
2459-
# with streamplot.
2460-
do_lower_margin = not np.any(np.isclose(x0, stickies))
2461-
do_upper_margin = not np.any(np.isclose(x1, stickies))
24622468
x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minpos)
24632469
x0t, x1t = transform.transform([x0, x1])
2464-
2465-
if np.isfinite(x1t) and np.isfinite(x0t):
2466-
delta = (x1t - x0t) * margin
2467-
else:
2468-
# If at least one bound isn't finite, set margin to zero
2469-
delta = 0
2470-
2471-
if do_lower_margin:
2472-
x0t -= delta
2473-
if do_upper_margin:
2474-
x1t += delta
2475-
x0, x1 = inverse_trans.transform([x0t, x1t])
2470+
delta = (x1t - x0t) * margin
2471+
if not np.isfinite(delta):
2472+
delta = 0 # If a bound isn't finite, set margin to zero.
2473+
x0, x1 = inverse_trans.transform([x0t - delta, x1t + delta])
2474+
2475+
# Apply sticky bounds.
2476+
if x0bound is not None:
2477+
x0 = max(x0, x0bound)
2478+
if x1bound is not None:
2479+
x1 = min(x1, x1bound)
24762480

24772481
if not self._tight:
24782482
x0, x1 = locator.view_limits(x0, x1)

lib/matplotlib/tests/test_axes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,12 @@ def test_polar_rlim_bottom(fig_test, fig_ref):
824824
ax.set_rmin(.5)
825825

826826

827+
def test_polar_rlim_zero():
828+
ax = plt.figure().add_subplot(projection='polar')
829+
ax.plot(np.arange(10), np.arange(10) + .01)
830+
assert ax.get_ylim()[0] == 0
831+
832+
827833
@image_comparison(baseline_images=['axvspan_epoch'])
828834
def test_axvspan_epoch():
829835
from datetime import datetime

lib/matplotlib/tests/test_streamplot.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,23 @@ def test_colormap():
4848
plt.colorbar()
4949

5050

51+
# Tolerance due to change in sticky-edges logic.
5152
@image_comparison(baseline_images=['streamplot_linewidth'],
5253
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
5354
remove_text=True, style='mpl20')
5455
def test_linewidth():
5556
X, Y, U, V = velocity_field()
5657
speed = np.hypot(U, V)
5758
lw = 5 * speed / speed.max()
58-
df = 25 / 30 # Compatibility factor for old test image
59+
# Compatibility for old test image
60+
df = 25 / 30
61+
plt.gca().set(xlim=(-3.0, 2.9999999999999947),
62+
ylim=(-3.0000000000000004, 2.9999999999999947))
5963
plt.streamplot(X, Y, U, V, density=[0.5 * df, 1. * df], color='k',
6064
linewidth=lw)
6165

6266

67+
# Tolerance due to change in sticky-edges logic.
6368
@image_comparison(baseline_images=['streamplot_masks_and_nans'],
6469
tol=0.04 if on_win else 0,
6570
remove_text=True, style='mpl20')
@@ -69,6 +74,9 @@ def test_masks_and_nans():
6974
mask[40:60, 40:60] = 1
7075
U[:20, :20] = np.nan
7176
U = np.ma.array(U, mask=mask)
77+
# Compatibility for old test image
78+
plt.gca().set(xlim=(-3.0, 2.9999999999999947),
79+
ylim=(-3.0000000000000004, 2.9999999999999947))
7280
with np.errstate(invalid='ignore'):
7381
plt.streamplot(X, Y, U, V, color=U, cmap=plt.cm.Blues)
7482

0 commit comments

Comments
 (0)