Skip to content

Use Path.arc() to interpolate polar arcs. #14852

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 13, 2020

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Jul 18, 2019

Currently, in polar plots, arcs are rendered by linearly interpolating
in (theta, r) space before conversion to (x, y) space (if their
_interpolation_steps attribute is >1 -- this attribute is described in
the docs as "an implementation detail not intended for public use").

When _interpolation_steps == 1, this PR doesn't change anything -- there
is an example (specialty_plots/radar_chart) that explicitly relies on
being able to set _interpolation_steps to 1 and get a "polygonal" polar
axes rather than a circular one.

When _interpolation_steps > 1, however, this PR looks for paths segments
that are either at a constant theta or a constant r. It transforms
constant-theta segments to a single segment (because interpolation
doesn't help there) and transforms constant-r segments (circular arcs)
to a Bezier approximation of a circle which is already implemented in
Path.arc()).

This greatly decreases the number of control points for such plots in
vector outputs (which are thus smaller -- note the line counts changes
in the svg files...), and also improves rasterization by mplcairo (cairo
appears to be not so good at filling areas with a lot of close vertices).

The many changes in baseline images are due to, well, the change of
approximation for drawing circles. (Note that if we agree with the
approach of this PR, I could perhaps do a first PR to decrease the
number of polar tests so that I don't have to commit so many new
images...)

Example: polar(); bar([0], [0.9])

matplotlib svg before, showing control points in inkscape:
oldsvg

matplotlib svg after, showing control points in inkscape:
newsvg

mplcairo before:
old

mplcairo after:
new

PR Summary

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@jklymak
Copy link
Member

jklymak commented Jul 18, 2019

This looks really useful. Can the old images be kept by setting interpolation_steps=1? OTOH I think if a default changes we should consider buying the bullet and changing the test images. Same for the image antialiasing PR

@anntzer
Copy link
Contributor Author

anntzer commented Jul 18, 2019

No, interpolation_steps=1 results in something like https://matplotlib.org/3.1.0/gallery/specialty_plots/radar_chart.html.

I'm fine with modifying a few baseline images but I think it's also a good opportunity to see whether we can combine some tests...

Copy link
Member

@QuLogic QuLogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I like the idea, but I won't approve since you want to work on thinning out the test images. Also, should try and get these new bits below to be tested.

* self._axis.get_rsign())
if last_td <= td:
while td - last_td > 360:
arc = Path.arc(last_td, last_td + 360)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block seems untested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test added, although it's not particularly interesting (I can't actually think of a very interesting test for that...)

# The reverse version also relies on the fact that all
# codes but the first one are the same.
while last_td - td > 360:
arc = Path.arc(last_td - 360, last_td)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As does this block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

xys.extend(arc.vertices[::-1][1:] * r)
codes.extend(arc.codes[1:])
else: # Interpolate.
trs = cbook.simple_linear_interpolation(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And this block too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test added

np.column_stack([(last_t, last_r), trs]),
path._interpolation_steps)[1:]
xys.extend(self.transform_non_affine(trs))
codes.extend(Path.LINETO for _ in trs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really better than codes.extend([Path.LINETO] * len(trs))?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either way works for me, switched to your preference.

@anntzer
Copy link
Contributor Author

anntzer commented Jul 25, 2019

Rebased on top of #14888 which deletes a few baseline images (replacing them by check_figures_equal).

@anntzer
Copy link
Contributor Author

anntzer commented Jul 25, 2019

Also, note that with this patch, one cannot anymore set e.g. GRIDLINE_INTERPOLATION_STEPS to a small but >1 number (e.g. 3) and see 3-point interpolation on circular arcs, but I'm not too worried about that...

@jklymak jklymak requested a review from QuLogic July 26, 2019 23:15
@timhoffm
Copy link
Member

I'm positive on the idea of this change. Ping when you think it's ready for review.

@anntzer
Copy link
Contributor Author

anntzer commented Jul 27, 2019

This PR includes #14888 so it can already be reviewed (at worst this one gets merged before #14888 and both commits go in at the same time). (I don't think the other polar baseline images can "easily" be removed.)

@anntzer anntzer force-pushed the polarinterp branch 2 times, most recently from 2701391 to 3e74404 Compare July 28, 2019 21:45
@tacaswell tacaswell added this to the v3.3.0 milestone Aug 12, 2019
@anntzer anntzer force-pushed the polarinterp branch 2 times, most recently from 0842a13 to 851d81a Compare August 12, 2019 21:25
@QuLogic
Copy link
Member

QuLogic commented Sep 8, 2019

Docs fail, might need a rebase.

@anntzer
Copy link
Contributor Author

anntzer commented Sep 8, 2019

rebased

Currently, in polar plots, arcs are rendered by linearly interpolating
in (theta, r) space before conversion to (x, y) space (if their
_interpolation_steps attribute is >1 -- this attribute is described in
the docs as "an implementation detail not intended for public use").

When _interpolation_steps == 1, this PR doesn't change anything -- there
is an example (specialty_plots/radar_chart) that explicitly relies on
being able to set _interpolation_steps to 1 and get a "polygonal" polar
axes rather than a circular one.

When _interpolation_steps > 1, however, this PR looks for paths segments
that are either at a constant theta or a constant r.  It transforms
constant-theta segments to a single segment (because interpolation
doesn't help there) and transforms constant-r segments (circular arcs)
to a Bezier approximation of a circle which is already implemented in
Path.arc()).

This greatly decreases the number of control points for such plots in
vector outputs (which are thus smaller), and also improves rasterization
by mplcairo (cairo appears to be not so good at filling areas with a lot
of close vertices).

The many changes in baseline images are due to, well, the change of
approximation for drawing circles.
Copy link
Member

@QuLogic QuLogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit unfortunate about all the test images, I guess.

@anntzer
Copy link
Contributor Author

anntzer commented Mar 13, 2020

I don't mind waiting to see at least whether the test-images-infrastructure-rewrite GSoC will happen or not (after all this has already been there for 8 months, an extra couple of them isn't too bad...).

@tacaswell tacaswell merged commit 4331c53 into matplotlib:master Mar 13, 2020
@tacaswell
Copy link
Member

Decided to merge as-is as this is a pretty big improvement for SVG and mplcairo.

@anntzer anntzer deleted the polarinterp branch March 13, 2020 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants