FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Non-sensical negative radial scale minimum autoset in polar plot · Issue #13292 · matplotlib/matplotlib · GitHub

Repository navigation

Non-sensical negative radial scale minimum autoset in polar plot #13292

Closed

Description

When plotting a set of data on a polar plot, the default bottom y_limit might not be zero unexpectedly from the perspective of the user, resulting in confusion about the meaning of the plot, especially for a person (like me) unfamiliar with the concept of a polar plot where r=0 is not at the very center point of the plot.

In a Jupyter Notebook

%pylab inline
npoints = 10_000
theta = 360 * random.random(npoints)
r = random.random(npoints)

fig, (ax1, ax2) = subplots(1, 2, figsize=(8, 4), dpi=120, facecolor='white', subplot_kw=dict(projection='polar'))
ax1.plot(radians(theta), r, 'o', markersize=1)
ax1.set_title('expected', pad=12)
ax2.plot(radians(theta), r, 'o', markersize=1)
ax2.set_title('unexpected', pad=12)
ax1.set_ylim(bottom=0)
# ax2.set_ylim(bottom=0)
print(ax2.get_ylim())
>>> (-0.04989219852580686, 1.0497180912808268)

I ran across this when plotting data and wondering if I had a bug in my analysis somewhere that was giving me a hole around the origin. It took me some time to figure out that the problem was simply in the axis scaling as I expected the version on the left (which seems sensible to me as the default) and not the version on the right which has a hole in the middle.

Matplotlib version

  • Operating system: Windows 10, also Ubuntu Linux
  • Matplotlib version: 3.0.2 from pip
  • Matplotlib backend (print(matplotlib.get_backend())): inline
  • Python version: 3.7, 3.6
  • Jupyter version (if applicable): JupyterLab 0.35.4

Activity

  1. anntzer commented on Jan 26, 2019

    Contributor

    I agree the behavior is less than optimal. Perhaps polar plots should just be created with 0 as explicit lower r-lim? (I think negative r-lims should be explicitly requested.)
    (And then #10101 could be reverted as unnecessary anymore.)

  2. dstansby commented on Jan 29, 2019

    Member

    I think the issue here is the autoscaling is including a bit of the plot with r<0, I would guess because the markers overlap into that area. @dvincentwest if you want a workaround in the meantime using scatter instead of plot might work.

  3. anntzer commented on Feb 11, 2019

    Contributor

    Unfortunately, I think right now there's no mechanism to disable autoscaling on one of the two bounds (r=0) while keeping it on the other (upper r bound) :/

  4. efiring commented on Feb 18, 2019

    Member

    Can it be handled in projections.polar.RadialLocator.autoscale()?

  5. anntzer commented on Feb 18, 2019

    Contributor

    I doubt so. In fact, Locator.autoscale() seems completely unused right now (since 5964da2, hey you wrote that :)).

    (Aside re: autoscale(): it seems like it got superseded by view_limits() (for "round" autoscaling mode), except that dates.py has never been updated so date locators still define the unused autoscale() but don't define view_limits()?)

    However, the use of sticky_edges in #13444 gave me another idea: we can slightly change the semantics of sticky edges to mean "an autoscale call cannot move a limit beyond a sticky edge through margins application" (including when the limit is already touching the sticky edge, which is the case in all use cases so far), then keep the sticky edge at zero and set the lower datalim to zero.

  6. anntzer commented on Feb 18, 2019

    Contributor

    Looks like the following patch implements the strategy above (of slightly changing the semantics of sticky_edges to fix this issue):

    diff --git i/lib/matplotlib/axes/_base.py w/lib/matplotlib/axes/_base.py
    index 9515e03e5..8d02a0e68 100644
    --- i/lib/matplotlib/axes/_base.py
    +++ w/lib/matplotlib/axes/_base.py
    @@ -2387,8 +2387,8 @@ class _AxesBase(martist.Artist):
                     (self._xmargin and scalex and self._autoscaleXon) or
                     (self._ymargin and scaley and self._autoscaleYon)):
                 stickies = [artist.sticky_edges for artist in self.get_children()]
    -            x_stickies = np.array([x for sticky in stickies for x in sticky.x])
    -            y_stickies = np.array([y for sticky in stickies for y in sticky.y])
    +            x_stickies = np.sort([x for sticky in stickies for x in sticky.x])
    +            y_stickies = np.sort([y for sticky in stickies for y in sticky.y])
                 if self.get_xscale().lower() == 'log':
                     x_stickies = x_stickies[x_stickies > 0]
                 if self.get_yscale().lower() == 'log':
    @@ -2421,7 +2421,7 @@ class _AxesBase(martist.Artist):
                     dl.extend(y_finite)
     
                 bb = mtransforms.BboxBase.union(dl)
    -            x0, x1 = getattr(bb, interval)
    +            x0orig, x1orig = x0, x1 = getattr(bb, interval)
                 locator = axis.get_major_locator()
                 x0, x1 = locator.nonsingular(x0, x1)
     
    @@ -2430,10 +2430,6 @@ class _AxesBase(martist.Artist):
                 minpos = getattr(bb, minpos)
                 transform = axis.get_transform()
                 inverse_trans = transform.inverted()
    -            # We cannot use exact equality due to floating point issues e.g.
    -            # with streamplot.
    -            do_lower_margin = not np.any(np.isclose(x0, stickies))
    -            do_upper_margin = not np.any(np.isclose(x1, stickies))
                 x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minpos)
                 x0t, x1t = transform.transform([x0, x1])
     
    @@ -2443,12 +2439,23 @@ class _AxesBase(martist.Artist):
                     # If at least one bound isn't finite, set margin to zero
                     delta = 0
     
    -            if do_lower_margin:
    -                x0t -= delta
    -            if do_upper_margin:
    -                x1t += delta
    +            x0t -= delta
    +            x1t += delta
    +
                 x0, x1 = inverse_trans.transform([x0t, x1t])
     
    +            # We cannot use exact equality due to floating point issues e.g.
    +            # with streamplot.  The tolerances come from isclose().
    +            stickies_minus_tol = stickies - 1e-5 * np.abs(stickies) - 1e-8
    +            stickies_plus_tol = stickies + 1e-5 * np.abs(stickies) + 1e-8
    +
    +            i0orig, i0 = stickies_minus_tol.searchsorted([x0orig, x0])
    +            if i0orig != i0:  # Crossed a sticky boundary.
    +                x0 = stickies[i0orig - 1]  # Go back to sticky boundary.
    +            i1orig, i1 = stickies_plus_tol.searchsorted([x1orig, x1])
    +            if i1orig != i1:
    +                x1 = stickies[i1orig]
    +
                 if not self._tight:
                     x0, x1 = locator.view_limits(x0, x1)
                 set_bound(x0, x1)

    Haven't tested if this breaks other stuff. Also needs changelog.

  7. added this to the v3.2.0 milestone on May 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL