Skip to content

Commit 474f6c5

Browse files
authored
Merge pull request matplotlib#13980 from anntzer/polarzero
Don't let margins expand polar plots to negative radii by default.
2 parents 4236b57 + 0911f8a commit 474f6c5

File tree

4 files changed

+76
-29
lines changed

4 files changed

+76
-29
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: 28 additions & 23 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])
2407-
if self.get_xscale().lower() == 'log':
2408-
x_stickies = x_stickies[x_stickies > 0]
2409-
if self.get_yscale().lower() == 'log':
2410-
y_stickies = y_stickies[y_stickies > 0]
24112405
else: # Small optimization.
2412-
x_stickies, y_stickies = [], []
2406+
stickies = []
2407+
x_stickies = np.sort([x for sticky in stickies for x in sticky.x])
2408+
y_stickies = np.sort([y for sticky in stickies for y in sticky.y])
2409+
if self.get_xscale().lower() == 'log':
2410+
x_stickies = x_stickies[x_stickies > 0]
2411+
if self.get_yscale().lower() == 'log':
2412+
y_stickies = y_stickies[y_stickies > 0]
24132413

24142414
def handle_single_axis(scale, autoscaleon, shared_axes, interval,
24152415
minpos, axis, margin, stickies, set_bound):
@@ -2450,29 +2450,34 @@ 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 values come from isclose()) must be used due to
2455+
# floating point issues with streamplot.
2456+
def tol(x): return 1e-5 * abs(x) + 1e-8
2457+
# Index of largest element < x0 + tol, if any.
2458+
i0 = stickies.searchsorted(x0 + tol(x0)) - 1
2459+
x0bound = stickies[i0] if i0 != -1 else None
2460+
# Index of smallest element > x1 - tol, if any.
2461+
i1 = stickies.searchsorted(x1 - tol(x1))
2462+
x1bound = stickies[i1] if i1 != len(stickies) else None
2463+
24532464
# Add the margin in figure space and then transform back, to handle
24542465
# non-linear scales.
24552466
minpos = getattr(bb, minpos)
24562467
transform = axis.get_transform()
24572468
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))
24622469
x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minpos)
24632470
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])
2471+
delta = (x1t - x0t) * margin
2472+
if not np.isfinite(delta):
2473+
delta = 0 # If a bound isn't finite, set margin to zero.
2474+
x0, x1 = inverse_trans.transform([x0t - delta, x1t + delta])
2475+
2476+
# Apply sticky bounds.
2477+
if x0bound is not None:
2478+
x0 = max(x0, x0bound)
2479+
if x1bound is not None:
2480+
x1 = min(x1, x1bound)
24762481

24772482
if not self._tight:
24782483
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
@@ -797,6 +797,12 @@ def test_polar_rlim_bottom(fig_test, fig_ref):
797797
ax.set_rmin(.5)
798798

799799

800+
def test_polar_rlim_zero():
801+
ax = plt.figure().add_subplot(projection='polar')
802+
ax.plot(np.arange(10), np.arange(10) + .01)
803+
assert ax.get_ylim()[0] == 0
804+
805+
800806
@image_comparison(baseline_images=['axvspan_epoch'])
801807
def test_axvspan_epoch():
802808
from datetime import datetime

lib/matplotlib/tests/test_streamplot.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,13 @@ def test_linewidth():
5555
X, Y, U, V = velocity_field()
5656
speed = np.hypot(U, V)
5757
lw = 5 * speed / speed.max()
58-
df = 25 / 30 # Compatibility factor for old test image
59-
plt.streamplot(X, Y, U, V, density=[0.5 * df, 1. * df], color='k',
60-
linewidth=lw)
58+
# Compatibility for old test image
59+
df = 25 / 30
60+
ax = plt.figure().subplots()
61+
ax.set(xlim=(-3.0, 2.9999999999999947),
62+
ylim=(-3.0000000000000004, 2.9999999999999947))
63+
ax.streamplot(X, Y, U, V, density=[0.5 * df, 1. * df], color='k',
64+
linewidth=lw)
6165

6266

6367
@image_comparison(baseline_images=['streamplot_masks_and_nans'],
@@ -69,16 +73,24 @@ def test_masks_and_nans():
6973
mask[40:60, 40:60] = 1
7074
U[:20, :20] = np.nan
7175
U = np.ma.array(U, mask=mask)
76+
# Compatibility for old test image
77+
ax = plt.figure().subplots()
78+
ax.set(xlim=(-3.0, 2.9999999999999947),
79+
ylim=(-3.0000000000000004, 2.9999999999999947))
7280
with np.errstate(invalid='ignore'):
73-
plt.streamplot(X, Y, U, V, color=U, cmap=plt.cm.Blues)
81+
ax.streamplot(X, Y, U, V, color=U, cmap=plt.cm.Blues)
7482

7583

7684
@image_comparison(baseline_images=['streamplot_maxlength'],
7785
extensions=['png'], remove_text=True, style='mpl20')
7886
def test_maxlength():
7987
x, y, U, V = swirl_velocity_field()
80-
plt.streamplot(x, y, U, V, maxlength=10., start_points=[[0., 1.5]],
81-
linewidth=2, density=2)
88+
ax = plt.figure().subplots()
89+
ax.streamplot(x, y, U, V, maxlength=10., start_points=[[0., 1.5]],
90+
linewidth=2, density=2)
91+
assert ax.get_xlim()[-1] == ax.get_ylim()[-1] == 3
92+
# Compatibility for old test image
93+
ax.set(xlim=(None, 3.2555988021882305), ylim=(None, 3.078326760195413))
8294

8395

8496
@image_comparison(baseline_images=['streamplot_direction'],

0 commit comments

Comments
 (0)