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

Skip to content

Commit cce31fa

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 79ccf53 commit cce31fa

File tree

5 files changed

+66
-24
lines changed

5 files changed

+66
-24
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: 29 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,35 @@ 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+
rtol = 1e-5
2457+
atol = 1e-8
2458+
# Index of largest element < x0 + tol, if any.
2459+
i0 = stickies.searchsorted(x0 + rtol * abs(x0) + atol) - 1
2460+
x0bound = stickies[i0] if i0 != -1 else None
2461+
# Index of smallest element > x1 - tol, if any.
2462+
i1 = stickies.searchsorted(x1 - rtol * abs(x1) - atol)
2463+
x1bound = stickies[i1] if i1 != len(stickies) else None
2464+
24532465
# Add the margin in figure space and then transform back, to handle
24542466
# non-linear scales.
24552467
minpos = getattr(bb, minpos)
24562468
transform = axis.get_transform()
24572469
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))
24622470
x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minpos)
24632471
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])
2472+
delta = (x1t - x0t) * margin
2473+
if not np.isfinite(delta):
2474+
delta = 0 # If a bound isn't finite, set margin to zero.
2475+
x0, x1 = inverse_trans.transform([x0t - delta, x1t + delta])
2476+
2477+
# Apply sticky bounds.
2478+
if x0bound is not None:
2479+
x0 = max(x0, x0bound)
2480+
if x1bound is not None:
2481+
x1 = min(x1, x1bound)
24762482

24772483
if not self._tight:
24782484
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: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ 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
58+
# Compatibility for old test image
59+
df = 25 / 30
60+
plt.gca().set(xlim=(-3.0, 2.9999999999999947),
61+
ylim=(-3.0000000000000004, 2.9999999999999947))
5962
plt.streamplot(X, Y, U, V, density=[0.5 * df, 1. * df], color='k',
6063
linewidth=lw)
6164

@@ -69,6 +72,9 @@ def test_masks_and_nans():
6972
mask[40:60, 40:60] = 1
7073
U[:20, :20] = np.nan
7174
U = np.ma.array(U, mask=mask)
75+
# Compatibility for old test image
76+
plt.gca().set(xlim=(-3.0, 2.9999999999999947),
77+
ylim=(-3.0000000000000004, 2.9999999999999947))
7278
with np.errstate(invalid='ignore'):
7379
plt.streamplot(X, Y, U, V, color=U, cmap=plt.cm.Blues)
7480

0 commit comments

Comments
 (0)