Skip to content

Commit 71cf4b5

Browse files
committed
Use Path.arc() to interpolate polar arcs.
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.
1 parent 0851204 commit 71cf4b5

40 files changed

+3395
-11093
lines changed

lib/matplotlib/projections/polar.py

+52-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import matplotlib.axis as maxis
99
import matplotlib.markers as mmarkers
1010
import matplotlib.patches as mpatches
11-
import matplotlib.path as mpath
11+
from matplotlib.path import Path
1212
import matplotlib.ticker as mticker
1313
import matplotlib.transforms as mtransforms
1414
import matplotlib.spines as mspines
@@ -57,11 +57,57 @@ def transform_non_affine(self, tr):
5757

5858
def transform_path_non_affine(self, path):
5959
# docstring inherited
60-
vertices = path.vertices
61-
if len(vertices) == 2 and vertices[0, 0] == vertices[1, 0]:
62-
return mpath.Path(self.transform(vertices), path.codes)
63-
ipath = path.interpolated(path._interpolation_steps)
64-
return mpath.Path(self.transform(ipath.vertices), ipath.codes)
60+
if not len(path) or path._interpolation_steps == 1:
61+
return Path(self.transform_non_affine(path.vertices), path.codes)
62+
xys = []
63+
codes = []
64+
last_t = last_r = None
65+
for trs, c in path.iter_segments():
66+
trs = trs.reshape((-1, 2))
67+
if c == Path.LINETO:
68+
(t, r), = trs
69+
if t == last_t: # Same angle: draw a straight line.
70+
xys.extend(self.transform_non_affine(trs))
71+
codes.append(Path.LINETO)
72+
elif r == last_r: # Same radius: draw an arc.
73+
# The following is complicated by Path.arc() being
74+
# "helpful" and unwrapping the angles, but we don't want
75+
# that behavior here.
76+
last_td, td = np.rad2deg([last_t, t])
77+
if self._use_rmin and self._axis is not None:
78+
r = ((r - self._axis.get_rorigin())
79+
* self._axis.get_rsign())
80+
if last_td <= td:
81+
while td - last_td > 360:
82+
arc = Path.arc(last_td, last_td + 360)
83+
xys.extend(arc.vertices[1:] * r)
84+
codes.extend(arc.codes[1:])
85+
last_td += 360
86+
arc = Path.arc(last_td, td)
87+
xys.extend(arc.vertices[1:] * r)
88+
codes.extend(arc.codes[1:])
89+
else:
90+
# The reverse version also relies on the fact that all
91+
# codes but the first one are the same.
92+
while last_td - td > 360:
93+
arc = Path.arc(last_td - 360, last_td)
94+
xys.extend(arc.vertices[::-1][1:] * r)
95+
codes.extend(arc.codes[1:])
96+
last_td -= 360
97+
arc = Path.arc(td, last_td)
98+
xys.extend(arc.vertices[::-1][1:] * r)
99+
codes.extend(arc.codes[1:])
100+
else: # Interpolate.
101+
trs = cbook.simple_linear_interpolation(
102+
np.column_stack([(last_t, last_r), trs]),
103+
path._interpolation_steps)[1:]
104+
xys.extend(self.transform_non_affine(trs))
105+
codes.extend(Path.LINETO for _ in trs)
106+
else: # Not a straight line.
107+
xys.extend(self.transform_non_affine(trs))
108+
codes.extend(c for _ in trs)
109+
last_t, last_r = trs[-1]
110+
return Path(xys, codes)
65111

66112
def inverted(self):
67113
# docstring inherited
Binary file not shown.

lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg

+704-1,199
Loading
Binary file not shown.

0 commit comments

Comments
 (0)