diff --git a/doc/users/next_whats_new/2015-10-31_polar_limits.rst b/doc/users/next_whats_new/2015-10-31_polar_limits.rst new file mode 100644 index 000000000000..6b1f74fb4028 --- /dev/null +++ b/doc/users/next_whats_new/2015-10-31_polar_limits.rst @@ -0,0 +1,37 @@ +Enhancements to polar plot +-------------------------- + +The polar axes transforms have been greatly re-factored to allow for more +customization of view limits and tick labelling. Additional options for view +limits allow for creating an annulus, a sector, or some combination of the two. + +The :meth:`~matplotlib.axes.projections.polar.PolarAxes.set_rorigin` method may +be used to provide an offset to the minimum plotting radius, producing an +annulus. + +The :meth:`~matplotlib.projections.polar.PolarAxes.set_theta_zero_location` now +has an optional :code:`offset` argument. This argument may be used to further +specify the zero location based on the given anchor point. + +.. figure:: ../../gallery/pie_and_polar_charts/images/sphx_glr_polar_scatter_001.png + :target: ../../gallery/pie_and_polar_charts/polar_scatter.html + :align: center + :scale: 50 + + Polar Offset Demo + +The :meth:`~matplotlib.axes.projections.polar.PolarAxes.set_thetamin` and +:meth:`~matplotlib.axes.projections.polar.PolarAxes.set_thetamax` methods may +be used to limit the range of angles plotted, producing sectors of a circle. + +.. figure:: ../../gallery/pie_and_polar_charts/images/sphx_glr_polar_scatter_002.png + :target: ../../gallery/pie_and_polar_charts/polar_scatter.html + :align: center + :scale: 50 + + Polar Sector Demo + +Previous releases allowed plots containing negative radii for which the +negative values are simply used as labels, and the real radius is shifted by +the configured minimum. This release also allows negative radii to be used for +grids and ticks, which were previously silently ignored. diff --git a/examples/api/radar_chart.py b/examples/api/radar_chart.py index 2660f56f6f2a..2f6fd8ac4e3d 100644 --- a/examples/api/radar_chart.py +++ b/examples/api/radar_chart.py @@ -37,11 +37,10 @@ def radar_factory(num_vars, frame='circle'): """ # calculate evenly-spaced axis angles theta = np.linspace(0, 2*np.pi, num_vars, endpoint=False) - # rotate theta such that the first axis is at the top - theta += np.pi/2 def draw_poly_patch(self): - verts = unit_poly_verts(theta) + # rotate theta such that the first axis is at the top + verts = unit_poly_verts(theta + np.pi / 2) return plt.Polygon(verts, closed=True, edgecolor='k') def draw_circle_patch(self): @@ -60,6 +59,11 @@ class RadarAxes(PolarAxes): # define draw_frame method draw_patch = patch_dict[frame] + def __init__(self, *args, **kwargs): + super(RadarAxes, self).__init__(*args, **kwargs) + # rotate plot such that the first axis is at the top + self.set_theta_zero_location('N') + def fill(self, *args, **kwargs): """Override fill so that line is closed by default""" closed = kwargs.pop('closed', True) @@ -93,7 +97,7 @@ def _gen_axes_spines(self): # spine_type must be 'left', 'right', 'top', 'bottom', or `circle`. spine_type = 'circle' - verts = unit_poly_verts(theta) + verts = unit_poly_verts(theta + np.pi / 2) # close off polygon by repeating first vertex verts.append(verts[0]) path = Path(verts) diff --git a/examples/pie_and_polar_charts/polar_scatter.py b/examples/pie_and_polar_charts/polar_scatter.py index 5c9ca2c35f1c..f3ce26b7eb9e 100644 --- a/examples/pie_and_polar_charts/polar_scatter.py +++ b/examples/pie_and_polar_charts/polar_scatter.py @@ -3,8 +3,6 @@ Scatter plot on polar axis ========================== -Demo of scatter plot on a polar axis. - Size increases radially in this example and color increases with angle (just to verify the symbols are being scattered correctly). """ @@ -22,7 +20,37 @@ area = 200 * r**2 colors = theta -ax = plt.subplot(111, projection='polar') +fig = plt.figure() +ax = fig.add_subplot(111, projection='polar') c = ax.scatter(theta, r, c=colors, s=area, cmap='hsv', alpha=0.75) +############################################################################### +# Scatter plot on polar axis, with offset origin +# ---------------------------------------------- +# +# The main difference with the previous plot is the configuration of the origin +# radius, producing an annulus. Additionally, the theta zero location is set to +# rotate the plot. + +fig = plt.figure() +ax = fig.add_subplot(111, polar=True) +c = ax.scatter(theta, r, c=colors, s=area, cmap='hsv', alpha=0.75) + +ax.set_rorigin(-2.5) +ax.set_theta_zero_location('W', offset=10) + +############################################################################### +# Scatter plot on polar axis confined to a sector +# ----------------------------------------------- +# +# The main difference with the previous plots is the configuration of the +# theta start and end limits, producing a sector instead of a full circle. + +fig = plt.figure() +ax = fig.add_subplot(111, polar=True) +c = ax.scatter(theta, r, c=colors, s=area, cmap='hsv', alpha=0.75) + +ax.set_thetamin(45) +ax.set_thetamax(135) + plt.show() diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index e398969df785..e059c1ee375a 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -1,29 +1,23 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) -import six - -import math -import warnings +import collections import numpy as np -import matplotlib -rcParams = matplotlib.rcParams from matplotlib.axes import Axes import matplotlib.axis as maxis from matplotlib import cbook from matplotlib import docstring -from matplotlib.patches import Circle -from matplotlib.path import Path -from matplotlib.ticker import Formatter, Locator, FormatStrFormatter -from matplotlib.transforms import Affine2D, Affine2DBase, Bbox, \ - BboxTransformTo, IdentityTransform, Transform, TransformWrapper, \ - ScaledTranslation, blended_transform_factory, BboxTransformToMaxOnly +import matplotlib.patches as mpatches +import matplotlib.path as mpath +from matplotlib import rcParams +import matplotlib.ticker as mticker +import matplotlib.transforms as mtransforms import matplotlib.spines as mspines -class PolarTransform(Transform): +class PolarTransform(mtransforms.Transform): """ The base polar transform. This handles projection *theta* and *r* into Cartesian coordinate space *x* and *y*, but does not @@ -34,55 +28,53 @@ class PolarTransform(Transform): output_dims = 2 is_separable = False - def __init__(self, axis=None, use_rmin=True): - Transform.__init__(self) + def __init__(self, axis=None, use_rmin=True, + _apply_theta_transforms=True): + mtransforms.Transform.__init__(self) self._axis = axis self._use_rmin = use_rmin + self._apply_theta_transforms = _apply_theta_transforms def transform_non_affine(self, tr): xy = np.empty(tr.shape, float) - if self._axis is not None: - if self._use_rmin: - rmin = self._axis.viewLim.ymin - else: - rmin = 0 - theta_offset = self._axis.get_theta_offset() - theta_direction = self._axis.get_theta_direction() - else: - rmin = 0 - theta_offset = 0 - theta_direction = 1 t = tr[:, 0:1] r = tr[:, 1:2] x = xy[:, 0:1] y = xy[:, 1:2] - t *= theta_direction - t += theta_offset + # PolarAxes does not use the theta transforms here, but apply them for + # backwards-compatibility if not being used by it. + if self._apply_theta_transforms and self._axis is not None: + t *= self._axis.get_theta_direction() + t += self._axis.get_theta_offset() - r = r - rmin + if self._use_rmin and self._axis is not None: + r = r - self._axis.get_rorigin() mask = r < 0 x[:] = np.where(mask, np.nan, r * np.cos(t)) y[:] = np.where(mask, np.nan, r * np.sin(t)) return xy - transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__ + transform_non_affine.__doc__ = \ + mtransforms.Transform.transform_non_affine.__doc__ def transform_path_non_affine(self, path): vertices = path.vertices if len(vertices) == 2 and vertices[0, 0] == vertices[1, 0]: - return Path(self.transform(vertices), path.codes) + return mpath.Path(self.transform(vertices), path.codes) ipath = path.interpolated(path._interpolation_steps) - return Path(self.transform(ipath.vertices), ipath.codes) - transform_path_non_affine.__doc__ = Transform.transform_path_non_affine.__doc__ + return mpath.Path(self.transform(ipath.vertices), ipath.codes) + transform_path_non_affine.__doc__ = \ + mtransforms.Transform.transform_path_non_affine.__doc__ def inverted(self): - return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin) - inverted.__doc__ = Transform.inverted.__doc__ + return PolarAxes.InvertedPolarTransform(self._axis, self._use_rmin, + self._apply_theta_transforms) + inverted.__doc__ = mtransforms.Transform.inverted.__doc__ -class PolarAffine(Affine2DBase): +class PolarAffine(mtransforms.Affine2DBase): """ The affine part of the polar projection. Scales the output so that maximum radius rests on the edge of the axes circle. @@ -90,10 +82,10 @@ class PolarAffine(Affine2DBase): def __init__(self, scale_transform, limits): """ *limits* is the view limit of the data. The only part of - its bounds that is used is ymax (for the radius maximum). - The theta range is always fixed to (0, 2pi). + its bounds that is used is the y limits (for the radius limits). + The theta range is handled by the non-affine transform. """ - Affine2DBase.__init__(self) + mtransforms.Affine2DBase.__init__(self) self._scale_transform = scale_transform self._limits = limits self.set_children(scale_transform, limits) @@ -103,17 +95,17 @@ def get_matrix(self): if self._invalid: limits_scaled = self._limits.transformed(self._scale_transform) yscale = limits_scaled.ymax - limits_scaled.ymin - affine = Affine2D() \ + affine = mtransforms.Affine2D() \ .scale(0.5 / yscale) \ .translate(0.5, 0.5) self._mtx = affine.get_matrix() self._inverted = None self._invalid = 0 return self._mtx - get_matrix.__doc__ = Affine2DBase.get_matrix.__doc__ + get_matrix.__doc__ = mtransforms.Affine2DBase.get_matrix.__doc__ -class InvertedPolarTransform(Transform): +class InvertedPolarTransform(mtransforms.Transform): """ The inverse of the polar transform, mapping Cartesian coordinate space *x* and *y* back to *theta* and *r*. @@ -122,24 +114,14 @@ class InvertedPolarTransform(Transform): output_dims = 2 is_separable = False - def __init__(self, axis=None, use_rmin=True): - Transform.__init__(self) + def __init__(self, axis=None, use_rmin=True, + _apply_theta_transforms=True): + mtransforms.Transform.__init__(self) self._axis = axis self._use_rmin = use_rmin + self._apply_theta_transforms = _apply_theta_transforms def transform_non_affine(self, xy): - if self._axis is not None: - if self._use_rmin: - rmin = self._axis.viewLim.ymin - else: - rmin = 0 - theta_offset = self._axis.get_theta_offset() - theta_direction = self._axis.get_theta_direction() - else: - rmin = 0 - theta_offset = 0 - theta_direction = 1 - x = xy[:, 0:1] y = xy[:, 1:] r = np.sqrt(x*x + y*y) @@ -152,38 +134,113 @@ def transform_non_affine(self, xy): theta = np.arccos(x / r) theta = np.where(y < 0, 2 * np.pi - theta, theta) - theta -= theta_offset - theta *= theta_direction - theta %= 2 * np.pi + # PolarAxes does not use the theta transforms here, but apply them for + # backwards-compatibility if not being used by it. + if self._apply_theta_transforms and self._axis is not None: + theta -= self._axis.get_theta_offset() + theta *= self._axis.get_theta_direction() + theta %= 2 * np.pi - r += rmin + if self._use_rmin and self._axis is not None: + r += self._axis.get_rorigin() return np.concatenate((theta, r), 1) - transform_non_affine.__doc__ = Transform.transform_non_affine.__doc__ + transform_non_affine.__doc__ = \ + mtransforms.Transform.transform_non_affine.__doc__ def inverted(self): - return PolarAxes.PolarTransform(self._axis, self._use_rmin) - inverted.__doc__ = Transform.inverted.__doc__ + return PolarAxes.PolarTransform(self._axis, self._use_rmin, + self._apply_theta_transforms) + inverted.__doc__ = mtransforms.Transform.inverted.__doc__ -class ThetaFormatter(Formatter): +class ThetaFormatter(mticker.Formatter): """ Used to format the *theta* tick labels. Converts the native unit of radians into degrees and adds a degree symbol. """ def __call__(self, x, pos=None): + vmin, vmax = self.axis.get_view_interval() + d = np.rad2deg(abs(vmax - vmin)) + digits = max(-int(np.log10(d) - 1.5), 0) + if rcParams['text.usetex'] and not rcParams['text.latex.unicode']: - return r"$%0.0f^\circ$" % ((x / np.pi) * 180.0) + format_str = r"${value:0.{digits:d}f}^\circ$" + return format_str.format(value=np.rad2deg(x), digits=digits) else: # we use unicode, rather than mathtext with \circ, so # that it will work correctly with any arbitrary font # (assuming it has a degree sign), whereas $5\circ$ # will only work correctly with one of the supported # math fonts (Computer Modern and STIX) - return "%0.0f\N{DEGREE SIGN}" % ((x / np.pi) * 180.0) + format_str = "{value:0.{digits:d}f}\N{DEGREE SIGN}" + return format_str.format(value=np.rad2deg(x), digits=digits) + + +class _AxisWrapper(object): + def __init__(self, axis): + self._axis = axis + + def get_view_interval(self): + return np.rad2deg(self._axis.get_view_interval()) + + def set_view_interval(self, vmin, vmax): + self._axis.set_view_interval(*np.deg2rad((vmin, vmax))) + + def get_minpos(self): + return np.rad2deg(self._axis.get_minpos()) + + def get_data_interval(self): + return np.rad2deg(self._axis.get_data_interval()) + def set_data_interval(self, vmin, vmax): + self._axis.set_data_interval(*np.deg2rad((vmin, vmax))) -class RadialLocator(Locator): + def get_tick_space(self): + return self._axis.get_tick_space() + + +class ThetaLocator(mticker.Locator): + """ + Used to locate theta ticks. + + This will work the same as the base locator except in the case that the + view spans the entire circle. In such cases, the previously used default + locations of every 45 degrees are returned. + """ + def __init__(self, base): + self.base = base + self.axis = self.base.axis = _AxisWrapper(self.base.axis) + + def set_axis(self, axis): + self.axis = _AxisWrapper(axis) + self.base.set_axis(self.axis) + + def __call__(self): + lim = self.axis.get_view_interval() + if _is_full_circle_deg(lim[0], lim[1]): + return np.arange(8) * 2 * np.pi / 8 + else: + return np.deg2rad(self.base()) + + def autoscale(self): + return self.base.autoscale() + + def pan(self, numsteps): + return self.base.pan(numsteps) + + def refresh(self): + return self.base.refresh() + + def view_limits(self, vmin, vmax): + vmin, vmax = np.rad2deg((vmin, vmax)) + return np.deg2rad(self.base.view_limits(vmin, vmax)) + + def zoom(self, direction): + return self.base.zoom(direction) + + +class RadialLocator(mticker.Locator): """ Used to locate radius ticks. @@ -192,12 +249,23 @@ class RadialLocator(Locator): :class:`~matplotlib.ticker.Locator` (which may be different depending on the scale of the *r*-axis. """ - def __init__(self, base): + def __init__(self, base, axes=None): self.base = base + self._axes = axes def __call__(self): - ticks = self.base() - return [x for x in ticks if x > 0] + show_all = True + # Ensure previous behaviour with full circle non-annular views. + if self._axes: + if _is_full_circle_rad(*self._axes.viewLim.intervalx): + rorigin = self._axes.get_rorigin() + if self._axes.get_rmin() <= rorigin: + show_all = False + + if show_all: + return self.base() + else: + return [tick for tick in self.base() if tick > rorigin] def autoscale(self): return self.base.autoscale() @@ -213,7 +281,93 @@ def refresh(self): def view_limits(self, vmin, vmax): vmin, vmax = self.base.view_limits(vmin, vmax) - return 0, vmax + return mtransforms.nonsingular(min(0, vmin), vmax) + + +def _is_full_circle_deg(thetamin, thetamax): + """ + Determine if a wedge (in degrees) spans the full circle. + + The condition is derived from :class:`~matplotlib.patches.Wedge`. + """ + return abs(abs(thetamax - thetamin) - 360.0) < 1e-12 + + +def _is_full_circle_rad(thetamin, thetamax): + """ + Determine if a wedge (in radians) spans the full circle. + + The condition is derived from :class:`~matplotlib.patches.Wedge`. + """ + return abs(abs(thetamax - thetamin) - 2 * np.pi) < 1.74e-14 + + +class _WedgeBbox(mtransforms.Bbox): + """ + Transform (theta,r) wedge Bbox into axes bounding box. + + Parameters + ---------- + center : tuple of float + Center of the wedge + viewLim : `~matplotlib.transforms.Bbox` + Bbox determining the boundaries of the wedge + originLim : `~matplotlib.transforms.Bbox` + Bbox determining the origin for the wedge, if different from *viewLim* + """ + def __init__(self, center, viewLim, originLim, **kwargs): + mtransforms.Bbox.__init__(self, + np.array([[0.0, 0.0], [1.0, 1.0]], np.float), + **kwargs) + self._center = center + self._viewLim = viewLim + self._originLim = originLim + self.set_children(viewLim, originLim) + + def __repr__(self): + return "_WedgeBbox(%r, %r, %r)" % (self._center, self._viewLim, + self._originLim) + + def get_points(self): + if self._invalid: + points = self._viewLim.get_points().copy() + + # Scale angular limits to work with Wedge. + points[:, 0] *= 180 / np.pi + if points[0, 0] > points[1, 0]: + points[:, 0] = points[::-1, 0] + + # Scale radial limits based on origin radius. + points[:, 1] -= self._originLim.y0 + + # Scale radial limits to match axes limits. + rscale = 0.5 / points[1, 1] + points[:, 1] *= rscale + width = min(points[1, 1] - points[0, 1], 0.5) + + # Generate bounding box for wedge. + wedge = mpatches.Wedge(self._center, points[1, 1], + points[0, 0], points[1, 0], + width=width) + self.update_from_path(wedge.get_path()) + + # Ensure equal aspect ratio. + w, h = self._points[1] - self._points[0] + if h < w: + deltah = (w - h) / 2.0 + deltaw = 0.0 + elif w < h: + deltah = 0.0 + deltaw = (h - w) / 2.0 + else: + deltah = 0.0 + deltaw = 0.0 + self._points += np.array([[-deltaw, -deltah], [deltaw, deltah]]) + + self._invalid = 0 + + return self._points + get_points.__doc__ = mtransforms.Bbox.get_points.__doc__ class PolarAxes(Axes): @@ -230,7 +384,8 @@ def __init__(self, *args, **kwargs): """ self._default_theta_offset = kwargs.pop('theta_offset', 0) self._default_theta_direction = kwargs.pop('theta_direction', 1) - self._default_rlabel_position = kwargs.pop('rlabel_position', 22.5) + self._default_rlabel_position = np.deg2rad( + kwargs.pop('rlabel_position', 22.5)) Axes.__init__(self, *args, **kwargs) self.set_aspect('equal', adjustable='box', anchor='C') @@ -244,17 +399,29 @@ def cla(self): self.xaxis.set_major_formatter(self.ThetaFormatter()) self.xaxis.isDefault_majfmt = True - angles = np.arange(0.0, 360.0, 45.0) - self.set_thetagrids(angles) - self.yaxis.set_major_locator(self.RadialLocator(self.yaxis.get_major_locator())) + start = self.spines.get('start', None) + if start: + start.set_visible(False) + end = self.spines.get('end', None) + if end: + end.set_visible(False) + self.set_xlim(0.0, 2 * np.pi) + self.xaxis.set_major_locator( + self.ThetaLocator(self.xaxis.get_major_locator())) self.grid(rcParams['polaraxes.grid']) self.xaxis.set_ticks_position('none') + inner = self.spines.get('inner', None) + if inner: + inner.set_visible(False) self.yaxis.set_ticks_position('none') - self.yaxis.set_tick_params(label1On=True) # Why do we need to turn on yaxis tick labels, but # xaxis tick labels are already on? + self.yaxis.set_tick_params(label1On=True) + self.yaxis.set_major_locator( + self.RadialLocator(self.yaxis.get_major_locator(), self)) + self.set_rorigin(None) self.set_theta_offset(self._default_theta_offset) self.set_theta_direction(self._default_theta_direction) @@ -269,62 +436,98 @@ def _init_axis(self): self._update_transScale() def _set_lim_and_transforms(self): - self.transAxes = BboxTransformTo(self.bbox) + # A view limit where the minimum radius can be locked if the user + # specifies an alternate origin. + self._originViewLim = mtransforms.LockableBbox(self.viewLim) + + # Handle angular offset and direction. + self._direction = mtransforms.Affine2D() \ + .scale(self._default_theta_direction, 1.0) + self._theta_offset = mtransforms.Affine2D() \ + .translate(self._default_theta_offset, 0.0) + self.transShift = mtransforms.composite_transform_factory( + self._direction, + self._theta_offset) + # A view limit shifted to the correct location after accounting for + # orientation and offset. + self._realViewLim = mtransforms.TransformedBbox(self.viewLim, + self.transShift) # Transforms the x and y axis separately by a scale factor # It is assumed that this part will have non-linear components - self.transScale = TransformWrapper(IdentityTransform()) + self.transScale = mtransforms.TransformWrapper( + mtransforms.IdentityTransform()) + + # Scale view limit into a bbox around the selected wedge. This may be + # smaller than the usual unit axes rectangle if not plotting the full + # circle. + self.axesLim = _WedgeBbox((0.5, 0.5), + self._realViewLim, self._originViewLim) + + # Scale the wedge to fill the axes. + self.transWedge = mtransforms.BboxTransformFrom(self.axesLim) + + # Scale the axes to fill the figure. + self.transAxes = mtransforms.BboxTransformTo(self.bbox) # A (possibly non-linear) projection on the (already scaled) # data. This one is aware of rmin - self.transProjection = self.PolarTransform(self) - - # This one is not aware of rmin - self.transPureProjection = self.PolarTransform(self, use_rmin=False) + self.transProjection = self.PolarTransform( + self, + _apply_theta_transforms=False) + # Add dependency on rorigin. + self.transProjection.set_children(self._originViewLim) # An affine transformation on the data, generally to limit the # range of the axes - self.transProjectionAffine = self.PolarAffine(self.transScale, self.viewLim) + self.transProjectionAffine = self.PolarAffine(self.transScale, + self._originViewLim) # The complete data transformation stack -- from data all the # way to display coordinates - self.transData = self.transScale + self.transProjection + \ - (self.transProjectionAffine + self.transAxes) + self.transData = ( + self.transScale + self.transShift + self.transProjection + + (self.transProjectionAffine + self.transWedge + self.transAxes)) # This is the transform for theta-axis ticks. It is - # equivalent to transData, except it always puts r == 1.0 at - # the edge of the axis circle. + # equivalent to transData, except it always puts r == 0.0 and r == 1.0 + # at the edge of the axis circles. self._xaxis_transform = ( - self.transPureProjection + - self.PolarAffine(IdentityTransform(), Bbox.unit()) + - self.transAxes) - # The theta labels are moved from radius == 0.0 to radius == 1.1 - self._theta_label1_position = Affine2D().translate(0.0, 1.1) + mtransforms.blended_transform_factory( + mtransforms.IdentityTransform(), + mtransforms.BboxTransformTo(self.viewLim)) + + self.transData) + # The theta labels are flipped along the radius, so that text 1 is on + # the outside by default. This should work the same as before. + flipr_transform = mtransforms.Affine2D() \ + .translate(0.0, -0.5) \ + .scale(1.0, -1.0) \ + .translate(0.0, 0.5) self._xaxis_text1_transform = ( - self._theta_label1_position + + flipr_transform + + mtransforms.Affine2D().translate(0.0, 0.1) + self._xaxis_transform) - self._theta_label2_position = Affine2D().translate(0.0, 1.0 / 1.1) self._xaxis_text2_transform = ( - self._theta_label2_position + + flipr_transform + + mtransforms.Affine2D().translate(0.0, -0.1) + self._xaxis_transform) # This is the transform for r-axis ticks. It scales the theta - # axis so the gridlines from 0.0 to 1.0, now go from 0.0 to - # 2pi. + # axis so the gridlines from 0.0 to 1.0, now go from thetamin to + # thetamax. self._yaxis_transform = ( - Affine2D().scale(np.pi * 2.0, 1.0) + + mtransforms.blended_transform_factory( + mtransforms.BboxTransformTo(self.viewLim), + mtransforms.IdentityTransform()) + self.transData) # The r-axis labels are put at an angle and padded in the r-direction - self._r_label_position = ScaledTranslation( - self._default_rlabel_position, 0.0, Affine2D()) - self._yaxis_text_transform = ( - self._r_label_position + - Affine2D().scale(1.0 / 360.0, 1.0) + - self._yaxis_transform - ) - - def get_xaxis_transform(self,which='grid'): - if which not in ['tick1','tick2','grid']: + self._r_label_position = mtransforms.Affine2D() \ + .translate(self._default_rlabel_position, 0.0) + self._yaxis_text_transform = mtransforms.TransformWrapper( + self._r_label_position + self.transData) + + def get_xaxis_transform(self, which='grid'): + if which not in ['tick1', 'tick2', 'grid']: msg = "'which' must be one of [ 'tick1' | 'tick2' | 'grid' ]" raise ValueError(msg) return self._xaxis_transform @@ -335,71 +538,173 @@ def get_xaxis_text1_transform(self, pad): def get_xaxis_text2_transform(self, pad): return self._xaxis_text2_transform, 'center', 'center' - def get_yaxis_transform(self,which='grid'): - if which not in ['tick1','tick2','grid']: + def get_yaxis_transform(self, which='grid'): + if which in ('tick1', 'tick2'): + return self._yaxis_text_transform + elif which == 'grid': + return self._yaxis_transform + else: msg = "'which' must be on of [ 'tick1' | 'tick2' | 'grid' ]" raise ValueError(msg) - return self._yaxis_transform def get_yaxis_text1_transform(self, pad): - angle = self.get_rlabel_position() - if angle < 90.: - return self._yaxis_text_transform, 'bottom', 'left' - elif angle < 180.: - return self._yaxis_text_transform, 'bottom', 'right' - elif angle < 270.: - return self._yaxis_text_transform, 'top', 'right' + thetamin, thetamax = self._realViewLim.intervalx + full = _is_full_circle_rad(thetamin, thetamax) + if full: + angle = self.get_rlabel_position() else: - return self._yaxis_text_transform, 'top', 'left' + angle = np.rad2deg(thetamin) + if angle < 0: + angle += 360 + angle %= 360 - def get_yaxis_text2_transform(self, pad): - angle = self.get_rlabel_position() - if angle < 90.: - return self._yaxis_text_transform, 'top', 'right' - elif angle < 180.: - return self._yaxis_text_transform, 'top', 'left' - elif angle < 270.: - return self._yaxis_text_transform, 'bottom', 'left' - else: - return self._yaxis_text_transform, 'bottom', 'right' + # NOTE: Due to a bug, previous code always used bottom left, contrary + # to its original intentions here. + valign = [['top', 'bottom', 'bottom', 'top'], + # ['bottom', 'bottom', 'top', 'top']] + ['bottom', 'bottom', 'bottom', 'bottom']] + halign = [['left', 'left', 'right', 'right'], + # ['left', 'right', 'right', 'left']] + ['left', 'left', 'left', 'left']] - def _gen_axes_patch(self): - return Circle((0.5, 0.5), 0.5) + ind = np.digitize([angle], np.arange(0, 361, 90))[0] - 1 - def _gen_axes_spines(self): - return {'polar':mspines.Spine.circular_spine(self, - (0.5, 0.5), 0.5)} + return self._yaxis_text_transform, valign[full][ind], halign[full][ind] - def set_rmax(self, rmax): - self.viewLim.y1 = rmax + def get_yaxis_text2_transform(self, pad): + thetamin, thetamax = self._realViewLim.intervalx + full = _is_full_circle_rad(thetamin, thetamax) + if full: + angle = self.get_rlabel_position() + else: + angle = np.rad2deg(thetamax) + if angle < 0: + angle += 360 + angle %= 360 + + # NOTE: Due to a bug, previous code always used top right, contrary to + # its original intentions here. + valign = [['bottom', 'top', 'top', 'bottom'], + # ['top', 'top', 'bottom', 'bottom']] + ['top', 'top', 'top', 'top']] + halign = [['right', 'right', 'left', 'left'], + # ['right', 'left', 'left', 'right']] + ['right', 'right', 'right', 'right']] + + ind = np.digitize([angle], np.arange(0, 361, 90))[0] - 1 + + return self._yaxis_text_transform, valign[full][ind], halign[full][ind] + + def draw(self, *args, **kwargs): + thetamin, thetamax = self._realViewLim.intervalx + thetamin *= 180 / np.pi + thetamax *= 180 / np.pi + if thetamin > thetamax: + thetamin, thetamax = thetamax, thetamin + rmin, rmax = self._realViewLim.intervaly - self.get_rorigin() + + if isinstance(self.patch, mpatches.Wedge): + # Backwards-compatibility: Any subclassed Axes might override the + # patch to not be the Wedge that PolarAxes uses. + center = self.transWedge.transform_point((0.5, 0.5)) + self.patch.set_center(center) + self.patch.set_theta1(thetamin) + self.patch.set_theta2(thetamax) + + edge, _ = self.transWedge.transform_point((1, 0)) + radius = edge - center[0] + width = min(radius * (rmax - rmin) / rmax, radius) + self.patch.set_radius(radius) + self.patch.set_width(width) + + inner_width = radius - width + inner = self.spines.get('inner', None) + if inner: + inner.set_visible(inner_width != 0.0) + + visible = not _is_full_circle_deg(thetamin, thetamax) + # For backwards compatibility, any subclassed Axes might override the + # spines to not include start/end that PolarAxes uses. + start = self.spines.get('start', None) + end = self.spines.get('end', None) + if start: + start.set_visible(visible) + if end: + end.set_visible(visible) + if visible: + yaxis_text_transform = self._yaxis_transform + else: + yaxis_text_transform = self._r_label_position + self.transData + if self._yaxis_text_transform != yaxis_text_transform: + self._yaxis_text_transform.set(yaxis_text_transform) + self.yaxis.reset_ticks() + self.yaxis.set_clip_path(self.patch) - def get_rmax(self): - return self.viewLim.ymax + Axes.draw(self, *args, **kwargs) - def set_rmin(self, rmin): - self.viewLim.y0 = rmin + def _gen_axes_patch(self): + return mpatches.Wedge((0.5, 0.5), 0.5, 0.0, 360.0) - def get_rmin(self): - return self.viewLim.ymin + def _gen_axes_spines(self): + spines = collections.OrderedDict([ + ('polar', mspines.Spine.arc_spine(self, 'top', + (0.5, 0.5), 0.5, 0.0, 360.0)), + ('start', mspines.Spine.linear_spine(self, 'left')), + ('end', mspines.Spine.linear_spine(self, 'right')), + ('inner', mspines.Spine.arc_spine(self, 'bottom', + (0.5, 0.5), 0.0, 0.0, 360.0)) + ]) + spines['polar'].set_transform(self.transWedge + self.transAxes) + spines['inner'].set_transform(self.transWedge + self.transAxes) + spines['start'].set_transform(self._yaxis_transform) + spines['end'].set_transform(self._yaxis_transform) + return spines + + def set_thetamax(self, thetamax): + self.viewLim.x1 = np.deg2rad(thetamax) + + def get_thetamax(self): + return np.rad2deg(self.viewLim.xmax) + + def set_thetamin(self, thetamin): + self.viewLim.x0 = np.deg2rad(thetamin) + + def get_thetamin(self): + return np.rad2deg(self.viewLim.xmin) + + def set_thetalim(self, *args, **kwargs): + if 'thetamin' in kwargs: + kwargs['xmin'] = np.deg2rad(kwargs.pop('thetamin')) + if 'thetamax' in kwargs: + kwargs['xmax'] = np.deg2rad(kwargs.pop('thetamax')) + return tuple(np.rad2deg(self.set_xlim(*args, **kwargs))) def set_theta_offset(self, offset): """ Set the offset for the location of 0 in radians. """ - self._theta_offset = offset + mtx = self._theta_offset.get_matrix() + mtx[0, 2] = offset + self._theta_offset.invalidate() def get_theta_offset(self): """ Get the offset for the location of 0 in radians. """ - return self._theta_offset + return self._theta_offset.get_matrix()[0, 2] - def set_theta_zero_location(self, loc): + def set_theta_zero_location(self, loc, offset=0.0): """ Sets the location of theta's zero. (Calls set_theta_offset with the correct value in radians under the hood.) - May be one of "N", "NW", "W", "SW", "S", "SE", "E", or "NE". + loc : str + May be one of "N", "NW", "W", "SW", "S", "SE", "E", or "NE". + + offset : float, optional + An offset in degrees to apply from the specified `loc`. **Note:** + this offset is *always* applied counter-clockwise regardless of + the direction setting. """ mapping = { 'N': np.pi * 0.5, @@ -409,8 +714,8 @@ def set_theta_zero_location(self, loc): 'S': np.pi * 1.5, 'SE': np.pi * 1.75, 'E': 0, - 'NE': np.pi * 0.25 } - return self.set_theta_offset(mapping[loc]) + 'NE': np.pi * 0.25} + return self.set_theta_offset(mapping[loc] + np.deg2rad(offset)) def set_theta_direction(self, direction): """ @@ -422,14 +727,17 @@ def set_theta_direction(self, direction): counterclockwise, anticlockwise, 1: Theta increases in the counterclockwise direction """ + mtx = self._direction.get_matrix() if direction in ('clockwise',): - self._direction = -1 + mtx[0, 0] = -1 elif direction in ('counterclockwise', 'anticlockwise'): - self._direction = 1 + mtx[0, 0] = 1 elif direction in (1, -1): - self._direction = direction + mtx[0, 0] = direction else: - raise ValueError("direction must be 1, -1, clockwise or counterclockwise") + raise ValueError( + "direction must be 1, -1, clockwise or counterclockwise") + self._direction.invalidate() def get_theta_direction(self): """ @@ -441,7 +749,25 @@ def get_theta_direction(self): 1: Theta increases in the counterclockwise direction """ - return self._direction + return self._direction.get_matrix()[0, 0] + + def set_rmax(self, rmax): + self.viewLim.y1 = rmax + + def get_rmax(self): + return self.viewLim.ymax + + def set_rmin(self, rmin): + self.viewLim.y0 = rmin + + def get_rmin(self): + return self.viewLim.ymin + + def set_rorigin(self, rorigin): + self._originViewLim.locked_y0 = rorigin + + def get_rorigin(self): + return self._originViewLim.y0 def set_rlim(self, *args, **kwargs): if 'rmin' in kwargs: @@ -457,7 +783,7 @@ def get_rlabel_position(self): float The theta position of the radius labels in degrees. """ - return self._r_label_position.to_values()[4] + return np.rad2deg(self._r_label_position.get_matrix()[0, 2]) def set_rlabel_position(self, value): """Updates the theta position of the radius labels. @@ -467,16 +793,16 @@ def set_rlabel_position(self, value): value : number The angular position of the radius labels in degrees. """ - self._r_label_position._t = (value, 0.0) - self._r_label_position.invalidate() + self._r_label_position.clear().translate(np.deg2rad(value), 0.0) def set_yscale(self, *args, **kwargs): Axes.set_yscale(self, *args, **kwargs) self.yaxis.set_major_locator( - self.RadialLocator(self.yaxis.get_major_locator())) + self.RadialLocator(self.yaxis.get_major_locator(), self)) def set_rscale(self, *args, **kwargs): return Axes.set_yscale(self, *args, **kwargs) + def set_rticks(self, *args, **kwargs): return Axes.set_yticks(self, *args, **kwargs) @@ -514,10 +840,7 @@ def set_thetagrids(self, angles, labels=None, frac=None, fmt=None, if labels is not None: self.set_xticklabels(labels) elif fmt is not None: - self.xaxis.set_major_formatter(FormatStrFormatter(fmt)) - if frac is not None: - self._theta_label1_position.clear().translate(0.0, frac) - self._theta_label2_position.clear().translate(0.0, 1.0 / frac) + self.xaxis.set_major_formatter(mticker.FormatStrFormatter(fmt)) for t in self.xaxis.get_ticklabels(): t.update(kwargs) return self.xaxis.get_ticklines(), self.xaxis.get_ticklabels() @@ -549,15 +872,12 @@ def set_rgrids(self, radii, labels=None, angle=None, fmt=None, # Make sure we take into account unitized data radii = self.convert_xunits(radii) radii = np.asarray(radii) - rmin = radii.min() - if rmin <= 0: - raise ValueError('radial grids must be strictly positive') self.set_yticks(radii) if labels is not None: self.set_yticklabels(labels) elif fmt is not None: - self.yaxis.set_major_formatter(FormatStrFormatter(fmt)) + self.yaxis.set_major_formatter(mticker.FormatStrFormatter(fmt)) if angle is None: angle = self.get_rlabel_position() self.set_rlabel_position(angle) @@ -567,18 +887,17 @@ def set_rgrids(self, radii, labels=None, angle=None, fmt=None, def set_xscale(self, scale, *args, **kwargs): if scale != 'linear': - raise NotImplementedError("You can not set the xscale on a polar plot.") - - def set_xlim(self, *args, **kargs): - # The xlim is fixed, no matter what you do - self.viewLim.intervalx = (0.0, np.pi * 2.0) + raise NotImplementedError( + "You can not set the xscale on a polar plot.") def format_coord(self, theta, r): """ Return a format string formatting the coordinate using Unicode characters. """ - theta /= math.pi + if theta < 0: + theta += 2 * np.pi + theta /= np.pi return ('\N{GREEK SMALL LETTER THETA}=%0.3f\N{GREEK SMALL LETTER PI} ' '(%0.3f\N{DEGREE SIGN}), r=%0.3f') % (theta, theta * 180.0, r) @@ -589,7 +908,7 @@ def get_data_ratio(self): ''' return 1.0 - ### Interactive panning + # # # Interactive panning def can_zoom(self): """ @@ -599,7 +918,7 @@ def can_zoom(self): """ return False - def can_pan(self) : + def can_pan(self): """ Return *True* if this axes supports the pan/zoom button functionality. @@ -621,14 +940,13 @@ def start_pan(self, x, y, button): mode = 'zoom' self._pan_start = cbook.Bunch( - rmax = self.get_rmax(), - trans = self.transData.frozen(), - trans_inverse = self.transData.inverted().frozen(), - r_label_angle = self.get_rlabel_position(), - x = x, - y = y, - mode = mode - ) + rmax=self.get_rmax(), + trans=self.transData.frozen(), + trans_inverse=self.transData.inverted().frozen(), + r_label_angle=self.get_rlabel_position(), + x=x, + y=y, + mode=mode) def end_pan(self): del self._pan_start @@ -662,8 +980,6 @@ def drag_pan(self, button, key, x, y): startt, startr = p.trans_inverse.transform_point((p.x, p.y)) t, r = p.trans_inverse.transform_point((x, y)) - dr = r - startr - # Deal with r scale = r / startr self.set_rmax(p.rmax / scale) @@ -677,6 +993,7 @@ def drag_pan(self, button, key, x, y): PolarAxes.InvertedPolarTransform = InvertedPolarTransform PolarAxes.ThetaFormatter = ThetaFormatter PolarAxes.RadialLocator = RadialLocator +PolarAxes.ThetaLocator = ThetaLocator # These are a couple of aborted attempts to project a polar plot using @@ -698,7 +1015,8 @@ def drag_pan(self, button, key, x, y): # vertices = self.transform(vertices) # result = np.zeros((len(vertices) * 3 - 2, 2), float) -# codes = mpath.Path.CURVE4 * np.ones((len(vertices) * 3 - 2, ), mpath.Path.code_type) +# codes = mpath.Path.CURVE4 * np.ones((len(vertices) * 3 - 2, ), +# mpath.Path.code_type) # result[0] = vertices[0] # codes[0] = mpath.Path.MOVETO @@ -735,8 +1053,8 @@ def drag_pan(self, button, key, x, y): # result[3::3] = p1 -# print vertices[-2:] -# print result[-2:] +# print(vertices[-2:]) +# print(result[-2:]) # return mpath.Path(result, codes) @@ -750,12 +1068,13 @@ def drag_pan(self, button, key, x, y): # maxtd = td.max() # interpolate = np.ceil(maxtd / halfpi) -# print "interpolate", interpolate +# print("interpolate", interpolate) # if interpolate > 1.0: # vertices = self.interpolate(vertices, interpolate) # result = np.zeros((len(vertices) * 3 - 2, 2), float) -# codes = mpath.Path.CURVE4 * np.ones((len(vertices) * 3 - 2, ), mpath.Path.code_type) +# codes = mpath.Path.CURVE4 * np.ones((len(vertices) * 3 - 2, ), +# mpath.Path.code_type) # result[0] = vertices[0] # codes[0] = mpath.Path.MOVETO @@ -777,16 +1096,19 @@ def drag_pan(self, button, key, x, y): # result[1::3, 0] = t0 + (tkappa * td_scaled) # result[1::3, 1] = r0*hyp_kappa -# # result[1::3, 1] = r0 / np.cos(tkappa * td_scaled) # np.sqrt(r0*r0 + ravg_kappa*ravg_kappa) +# # result[1::3, 1] = r0 / np.cos(tkappa * td_scaled) +# # np.sqrt(r0*r0 + ravg_kappa*ravg_kappa) # result[2::3, 0] = t1 - (tkappa * td_scaled) # result[2::3, 1] = r1*hyp_kappa -# # result[2::3, 1] = r1 / np.cos(tkappa * td_scaled) # np.sqrt(r1*r1 + ravg_kappa*ravg_kappa) +# # result[2::3, 1] = r1 / np.cos(tkappa * td_scaled) +# # np.sqrt(r1*r1 + ravg_kappa*ravg_kappa) # result[3::3, 0] = t1 # result[3::3, 1] = r1 -# print vertices[:6], result[:6], t0[:6], t1[:6], td[:6], td_scaled[:6], tkappa +# print(vertices[:6], result[:6], t0[:6], t1[:6], td[:6], +# td_scaled[:6], tkappa) # result = self.transform(result) # return mpath.Path(result, codes) # transform_path_non_affine = transform_path diff --git a/lib/matplotlib/spines.py b/lib/matplotlib/spines.py index c6b04fe43dba..b2f0f8d9fca1 100644 --- a/lib/matplotlib/spines.py +++ b/lib/matplotlib/spines.py @@ -32,10 +32,11 @@ class Spine(mpatches.Patch): Spines are subclasses of class:`~matplotlib.patches.Patch`, and inherit much of their behavior. - Spines draw a line or a circle, depending if - function:`~matplotlib.spines.Spine.set_patch_line` or - function:`~matplotlib.spines.Spine.set_patch_circle` has been - called. Line-like is the default. + Spines draw a line, a circle, or an arc depending if + function:`~matplotlib.spines.Spine.set_patch_line`, + function:`~matplotlib.spines.Spine.set_patch_circle`, or + function:`~matplotlib.spines.Spine.set_patch_arc` has been called. + Line-like is the default. """ def __str__(self): @@ -77,10 +78,11 @@ def __init__(self, axes, spine_type, path, **kwargs): self._path = path # To support drawing both linear and circular spines, this - # class implements Patch behavior two ways. If + # class implements Patch behavior three ways. If # self._patch_type == 'line', behave like a mpatches.PathPatch # instance. If self._patch_type == 'circle', behave like a - # mpatches.Ellipse instance. + # mpatches.Ellipse instance. If self._patch_type == 'arc', behave like + # a mpatches.Arc instance. self._patch_type = 'line' # Behavior copied from mpatches.Ellipse: @@ -102,13 +104,25 @@ def get_smart_bounds(self): """get whether the spine has smart bounds""" return self._smart_bounds + def set_patch_arc(self, center, radius, theta1, theta2): + """set the spine to be arc-like""" + self._patch_type = 'arc' + self._center = center + self._width = radius * 2 + self._height = radius * 2 + self._theta1 = theta1 + self._theta2 = theta2 + self._path = mpath.Path.arc(theta1, theta2) + # arc drawn on axes transform + self.set_transform(self.axes.transAxes) + self.stale = True + def set_patch_circle(self, center, radius): """set the spine to be circular""" self._patch_type = 'circle' self._center = center self._width = radius * 2 self._height = radius * 2 - self._angle = 0 # circle drawn on axes transform self.set_transform(self.axes.transAxes) self.stale = True @@ -125,18 +139,17 @@ def _recompute_transform(self): maxes it very important to call the accessor method and not directly access the transformation member variable. """ - assert self._patch_type == 'circle' + assert self._patch_type in ('arc', 'circle') center = (self.convert_xunits(self._center[0]), self.convert_yunits(self._center[1])) width = self.convert_xunits(self._width) height = self.convert_yunits(self._height) self._patch_transform = mtransforms.Affine2D() \ .scale(width * 0.5, height * 0.5) \ - .rotate_deg(self._angle) \ .translate(*center) def get_patch_transform(self): - if self._patch_type == 'circle': + if self._patch_type in ('arc', 'circle'): self._recompute_transform() return self._patch_transform else: @@ -255,17 +268,48 @@ def _adjust_location(self): else: low, high = self._bounds - v1 = self._path.vertices - assert v1.shape == (2, 2), 'unexpected vertices shape' - if self.spine_type in ['left', 'right']: - v1[0, 1] = low - v1[1, 1] = high - elif self.spine_type in ['bottom', 'top']: - v1[0, 0] = low - v1[1, 0] = high + if self._patch_type == 'arc': + if self.spine_type in ('bottom', 'top'): + try: + direction = self.axes.get_theta_direction() + except AttributeError: + direction = 1 + try: + offset = self.axes.get_theta_offset() + except AttributeError: + offset = 0 + low = low * direction + offset + high = high * direction + offset + if low > high: + low, high = high, low + + self._path = mpath.Path.arc(np.rad2deg(low), np.rad2deg(high)) + + if self.spine_type == 'bottom': + rmin, rmax = self.axes.viewLim.intervaly + try: + rorigin = self.axes.get_rorigin() + except AttributeError: + rorigin = rmin + scaled_diameter = (rmin - rorigin) / (rmax - rorigin) + self._height = scaled_diameter + self._width = scaled_diameter + + else: + raise ValueError('unable to set bounds for spine "%s"' % + self.spine_type) else: - raise ValueError('unable to set bounds for spine "%s"' % - self.spine_type) + v1 = self._path.vertices + assert v1.shape == (2, 2), 'unexpected vertices shape' + if self.spine_type in ['left', 'right']: + v1[0, 1] = low + v1[1, 1] = high + elif self.spine_type in ['bottom', 'top']: + v1[0, 0] = low + v1[1, 0] = high + else: + raise ValueError('unable to set bounds for spine "%s"' % + self.spine_type) @allow_rasterization def draw(self, renderer): @@ -463,6 +507,17 @@ def linear_spine(cls, axes, spine_type, **kwargs): return result + @classmethod + def arc_spine(cls, axes, spine_type, center, radius, theta1, theta2, + **kwargs): + """ + (classmethod) Returns an arc :class:`Spine`. + """ + path = mpath.Path.arc(theta1, theta2) + result = cls(axes, spine_type, path, **kwargs) + result.set_patch_arc(center, radius, theta1, theta2) + return result + @classmethod def circular_spine(cls, axes, center, radius, **kwargs): """ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.pdf b/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.pdf index a41459350312..e4496e4d1fa5 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.png b/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.png index 9524d221552b..6682d5c93a2d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.png and b/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg b/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg index 02e717e4593f..14cb8b2720c4 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg @@ -2,7 +2,7 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + + - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.pdf index 3b6519f7d946..e28200a785d6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.png index 8a137d8a64d8..141c5c3cb3ca 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.png and b/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.svg index 6c4ea0f01cd6..3121c5400aab 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_axes.svg @@ -2,7 +2,7 @@ - + - - - - - - - - - - - - - - - - - - - - - + + @@ -375,17 +112,17 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + - - + + @@ -432,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -440,10 +177,10 @@ z - - + + @@ -479,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -487,10 +224,10 @@ z - - + + @@ -541,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -550,10 +287,10 @@ z - - + + @@ -598,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -607,10 +344,10 @@ z - - + + @@ -640,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -649,10 +386,10 @@ z - - + + @@ -667,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -676,14 +413,14 @@ z - - + + - + @@ -694,103 +431,43 @@ L 417.388052 338.188052 - - + + - + - + - + - - + + - - + + - + - - + + + + + + + + + + + + + + + - + - - - + + + - + - + - - - + + + - + - + @@ -1529,19 +1191,282 @@ L 468 216 + + + + + + + + + + + + + + + + + - - + - + @@ -1721,16 +1646,42 @@ z - - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.pdf index be50113a1ed9..c9fa69334e88 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.png index 106b8fdb1cf8..6d5aa60ab3d2 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.png and b/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.svg index 1a3fb1fffc75..919c44c5bf16 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_coords.svg @@ -2,7 +2,7 @@ - + - - - - - - - - - - - - - - - +" style="fill:#ff0000;opacity:0.5;"/> - - - - - - - - +L 0 3.5 +" id="mbdbe0e235e" style="stroke:#000000;stroke-width:0.8;"/> - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - - - - + +L -3.5 0 +" id="ma295a3440f" style="stroke:#000000;stroke-width:0.8;"/> - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + - - - - - - + - + + + + + + + + + + + + + - - + - + - - - - - - + + + + + + - - + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_negative_rmin.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_negative_rmin.pdf new file mode 100644 index 000000000000..b1e2e21d1d0b Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/polar_negative_rmin.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_negative_rmin.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_negative_rmin.png new file mode 100644 index 000000000000..bcebe7a955eb Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/polar_negative_rmin.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_negative_rmin.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_negative_rmin.svg new file mode 100644 index 000000000000..dc37defea1ed --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_negative_rmin.svg @@ -0,0 +1,1663 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.pdf index bb3ea0a2b5c8..c7258af99a41 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.png index 46bc3a796b70..b05d60664d52 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.png and b/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.svg index 2c74df2304cf..be5229d4f7d5 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_rlabel_position.svg @@ -2,7 +2,7 @@ - + - - - - - - + @@ -99,7 +112,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -107,9 +120,9 @@ z - + @@ -156,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -165,9 +178,9 @@ z - + @@ -203,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -212,9 +225,9 @@ z - + @@ -265,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -275,9 +288,9 @@ z - + @@ -322,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -332,9 +345,9 @@ z - + @@ -364,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -374,9 +387,9 @@ z - + @@ -391,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -401,13 +414,13 @@ z - + - + @@ -419,99 +432,69 @@ L 417.388052 338.188052 - + @@ -523,7 +506,7 @@ L 10.6875 0 z " id="DejaVuSans-2e"/> - + @@ -532,103 +515,103 @@ z - + - + @@ -637,189 +620,99 @@ L 364.32 216 - + @@ -855,7 +748,7 @@ Q 48.484375 72.75 52.59375 71.296875 z " id="DejaVuSans-36"/> - + @@ -864,193 +757,193 @@ z - + - + @@ -1059,193 +952,193 @@ L 433.44 216 - + - + @@ -1253,19 +1146,65 @@ L 468 216 + + + - - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.pdf index c9d5247eb6e2..49fdb376a72d 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.png index 2a4234c7a7b7..afd5f3faf57e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.png and b/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.svg index 178d23601ff3..0591300b41d9 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_rmin.svg @@ -2,7 +2,7 @@ - + - - - - - - - - - - + + @@ -336,17 +112,17 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + - - + + @@ -393,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -401,10 +177,10 @@ z - - + + @@ -440,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -448,10 +224,10 @@ z - - + + @@ -502,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -511,10 +287,10 @@ z - - + + @@ -559,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -568,10 +344,10 @@ z - - + + @@ -601,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -610,10 +386,10 @@ z - - + + @@ -628,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -637,14 +413,14 @@ z - - + + - + @@ -655,55 +431,55 @@ L 410.188052 338.188052 - - + + @@ -745,7 +521,7 @@ Q 48.484375 72.75 52.59375 71.296875 z " id="DejaVuSans-36"/> - + @@ -753,104 +529,74 @@ z - - + + - + @@ -858,104 +604,104 @@ L 322.56 216 - - + + - + @@ -963,104 +709,104 @@ L 345.6 216 - - + + - + @@ -1068,194 +814,104 @@ L 368.64 216 - - + + - + @@ -1263,194 +919,194 @@ L 391.68 216 - - + + - + @@ -1458,194 +1114,194 @@ L 414.72 216 - - + + - + @@ -1653,194 +1309,194 @@ L 437.76 216 - - + + - + @@ -1848,19 +1504,302 @@ L 460.8 216 + + + + + + - - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_rorigin.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_rorigin.pdf new file mode 100644 index 000000000000..327b4aad6a6c Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/polar_rorigin.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_rorigin.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_rorigin.png new file mode 100644 index 000000000000..636eab4c6163 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/polar_rorigin.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_rorigin.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_rorigin.svg new file mode 100644 index 000000000000..861919ed4ea8 --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_rorigin.svg @@ -0,0 +1,2034 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.pdf index dbc674c59555..365c252dcadb 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.png index e9755929a197..13af040c6cbc 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.png and b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.svg index 9e1ad9d99aaa..d20989dfdc3d 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_position.svg @@ -2,7 +2,7 @@ - + - - - - - - - - - - + + @@ -383,17 +112,17 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + - - + + @@ -440,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -448,10 +177,10 @@ z - - + + @@ -487,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -495,10 +224,10 @@ z - - + + @@ -549,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -558,10 +287,10 @@ z - - + + @@ -606,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -615,10 +344,10 @@ z - - + + @@ -648,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -657,10 +386,10 @@ z - - + + @@ -675,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -684,14 +413,14 @@ z - - + + - + @@ -702,73 +431,49 @@ L 115.2 216 - - + + - + - + - + - - + + + + + + + + + + + + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1580,19 +1270,346 @@ L 165.811948 93.811948 + + + + + + - - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_wedge.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_wedge.pdf new file mode 100644 index 000000000000..50dff4f92950 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_wedge.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_wedge.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_wedge.png new file mode 100644 index 000000000000..ac952eaf3e7e Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_wedge.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_wedge.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_wedge.svg new file mode 100644 index 000000000000..41760e2897ba --- /dev/null +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_theta_wedge.svg @@ -0,0 +1,3904 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_units.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_units.pdf index 16f1947f10ff..4e4b2533e201 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_units.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/polar_units.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_units.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_units.png index 40a154ed6f5e..fa5be4aa1828 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_units.png and b/lib/matplotlib/tests/baseline_images/test_axes/polar_units.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_units.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_units.svg index c020a277164c..7e18f195c92a 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/polar_units.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_units.svg @@ -2,7 +2,7 @@ - + - - - - - - - - - - + + @@ -106,17 +112,17 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + - - + + @@ -163,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -171,10 +177,10 @@ z - - + + @@ -210,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -218,10 +224,10 @@ z - - + + @@ -272,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -281,10 +287,10 @@ z - - + + @@ -329,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -338,10 +344,10 @@ z - - + + @@ -371,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -380,10 +386,10 @@ z - - + + @@ -398,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -407,14 +413,14 @@ z - - + + - + @@ -509,7 +515,7 @@ L 54.390625 54.6875 z " id="DejaVuSans-67"/> - + @@ -518,70 +524,70 @@ z - - + + @@ -593,7 +599,7 @@ L 10.6875 0 z " id="DejaVuSans-2e"/> - + @@ -601,104 +607,104 @@ z - - + + - + @@ -706,104 +712,104 @@ L 338.4 216 - - + + - + @@ -811,104 +817,104 @@ L 360 216 - - + + - + @@ -916,194 +922,104 @@ L 381.6 216 - - + + - + @@ -1111,194 +1027,194 @@ L 403.2 216 - - + + - + @@ -1306,194 +1222,194 @@ L 424.8 216 - - + + - + @@ -1501,194 +1417,194 @@ L 446.4 216 - - + + - + @@ -1696,19 +1612,72 @@ L 468 216 + + + + + + - - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.pdf index 1fe7e0a9d269..5d459ccde364 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.png index aeb3f5f5aea5..007ad41f51ab 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.png and b/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.svg index 527b738dea2a..332189be1fe1 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_units_2.svg @@ -2,7 +2,7 @@ - + - - - - - - - - - - + + @@ -89,7 +95,7 @@ L 10.6875 0 z " id="DejaVuSans-2e"/> - + @@ -97,10 +103,10 @@ z - - + + @@ -283,7 +289,7 @@ Q 48.484375 72.75 52.59375 71.296875 z " id="DejaVuSans-36"/> - + @@ -302,10 +308,10 @@ z - - + + @@ -335,7 +341,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -353,10 +359,10 @@ z - - + + @@ -379,7 +385,7 @@ L 4.890625 26.703125 z " id="DejaVuSans-34"/> - + @@ -397,14 +403,14 @@ z - - + + - + @@ -422,14 +428,14 @@ L 122.4 216 - - + + - + @@ -447,14 +453,14 @@ L 173.011948 338.188052 - - + + - + @@ -472,14 +478,14 @@ L 295.2 388.8 - - + + - + @@ -574,7 +580,7 @@ Q 14.796875 37.203125 14.796875 27.296875 z " id="DejaVuSans-64"/> - + @@ -583,74 +589,74 @@ z - - + + - + @@ -658,104 +664,104 @@ L 316.8 216 - - + + - + @@ -763,104 +769,104 @@ L 338.4 216 - - + + - + @@ -868,104 +874,104 @@ L 360 216 - - + + - + @@ -973,194 +979,104 @@ L 381.6 216 - - + + - + @@ -1168,194 +1084,194 @@ L 403.2 216 - - + + - + @@ -1363,194 +1279,194 @@ L 424.8 216 - - + + - + @@ -1558,194 +1474,194 @@ L 446.4 216 - - + + - + @@ -1799,25 +1715,78 @@ Q 50 49.953125 52 44.1875 z " id="DejaVuSans-6d"/> - + + + + + + + - - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.pdf index a62f4899e22f..3e874e9159a1 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.png index 63003da54b0e..7ab1a71dcaac 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.png and b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.svg index 93758e566310..7c68e64eef27 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_180.svg @@ -2,7 +2,7 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -143,17 +112,17 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + - - + + @@ -200,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -208,10 +177,10 @@ z - - + + @@ -247,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -255,10 +224,10 @@ z - - + + @@ -309,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -318,10 +287,10 @@ z - - + + @@ -366,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -375,10 +344,10 @@ z - - + + @@ -408,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -417,10 +386,10 @@ z - - + + @@ -435,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -444,14 +413,14 @@ z - - + + - + @@ -462,70 +431,70 @@ L 417.388052 338.188052 - - + + @@ -537,7 +506,7 @@ L 10.6875 0 z " id="DejaVuSans-2e"/> - + @@ -546,104 +515,104 @@ z - - + + - + @@ -652,104 +621,104 @@ L 352.8 216 - - + + - + @@ -758,194 +727,104 @@ L 381.6 216 - - + + - + @@ -954,194 +833,194 @@ L 410.4 216 - - + + - + @@ -1150,194 +1029,194 @@ L 439.2 216 - - + + - + @@ -1346,19 +1225,109 @@ L 468 216 + + + + + + + + + + + + + + + + + + + + + + + - - + diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.pdf b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.pdf index e0961e0ba821..b35d1edc6fa3 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.pdf and b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.pdf differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.png b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.png index f4a32906aaf0..6b281167ce2e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.png and b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.svg b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.svg index 3ea6ac5f1f2c..0b43965b0365 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/polar_wrap_360.svg @@ -2,7 +2,7 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -165,17 +112,17 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + - - + + @@ -222,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -230,10 +177,10 @@ z - - + + @@ -269,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -277,10 +224,10 @@ z - - + + @@ -331,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -340,10 +287,10 @@ z - - + + @@ -388,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -397,10 +344,10 @@ z - - + + @@ -430,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -439,10 +386,10 @@ z - - + + @@ -457,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -466,14 +413,14 @@ z - - + + - + @@ -484,70 +431,70 @@ L 417.388052 338.188052 - - + + @@ -559,7 +506,7 @@ L 10.6875 0 z " id="DejaVuSans-2e"/> - + @@ -568,104 +515,104 @@ z - - + + - + @@ -674,104 +621,104 @@ L 352.8 216 - - + + - + @@ -780,194 +727,104 @@ L 381.6 216 - - + + - + @@ -976,194 +833,194 @@ L 410.4 216 - - + + - + @@ -1172,194 +1029,194 @@ L 439.2 216 - - + + - + @@ -1368,19 +1225,131 @@ L 468 216 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + diff --git a/lib/matplotlib/tests/baseline_images/test_patches/polar_proj.png b/lib/matplotlib/tests/baseline_images/test_patches/polar_proj.png index 7999ff1684b1..207585aa4d5e 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_patches/polar_proj.png and b/lib/matplotlib/tests/baseline_images/test_patches/polar_proj.png differ diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 3d340a955cf5..f7e5ffe87cb2 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -349,7 +349,7 @@ def test_annotate_default_arrow(): assert ann.arrow_patch is not None -@image_comparison(baseline_images=['polar_axes']) +@image_comparison(baseline_images=['polar_axes'], style='default') def test_polar_annotations(): # you can specify the xypoint and the xytext in different # positions and coordinate systems, and optionally turn on a @@ -383,7 +383,7 @@ def test_polar_annotations(): ) -@image_comparison(baseline_images=['polar_coords'], +@image_comparison(baseline_images=['polar_coords'], style='default', remove_text=True) def test_polar_coord_annotations(): # You can also use polar notation on a catesian axes. Here the @@ -558,9 +558,8 @@ def test_const_xy(): plt.plot(np.ones((10,)), np.ones((10,)), 'o') -@image_comparison(baseline_images=['polar_wrap_180', - 'polar_wrap_360', - ]) +@image_comparison(baseline_images=['polar_wrap_180', 'polar_wrap_360'], + style='default') def test_polar_wrap(): D2R = np.pi / 180.0 @@ -581,7 +580,8 @@ def test_polar_wrap(): plt.rgrids([0.05, 0.1, 0.15, 0.2, 0.25, 0.3]) -@image_comparison(baseline_images=['polar_units', 'polar_units_2']) +@image_comparison(baseline_images=['polar_units', 'polar_units_2'], + style='default') def test_polar_units(): import matplotlib.testing.jpl_units as units units.register() @@ -612,7 +612,7 @@ def test_polar_units(): units.UnitDblFormatter) -@image_comparison(baseline_images=['polar_rmin']) +@image_comparison(baseline_images=['polar_rmin'], style='default') def test_polar_rmin(): r = np.arange(0, 3.0, 0.01) theta = 2*np.pi*r @@ -624,7 +624,32 @@ def test_polar_rmin(): ax.set_rmin(0.5) -@image_comparison(baseline_images=['polar_theta_position']) +@image_comparison(baseline_images=['polar_negative_rmin'], style='default') +def test_polar_negative_rmin(): + r = np.arange(-3.0, 0.0, 0.01) + theta = 2*np.pi*r + + fig = plt.figure() + ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) + ax.plot(theta, r) + ax.set_rmax(0.0) + ax.set_rmin(-3.0) + + +@image_comparison(baseline_images=['polar_rorigin'], style='default') +def test_polar_rorigin(): + r = np.arange(0, 3.0, 0.01) + theta = 2*np.pi*r + + fig = plt.figure() + ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) + ax.plot(theta, r) + ax.set_rmax(2.0) + ax.set_rmin(0.5) + ax.set_rorigin(0.0) + + +@image_comparison(baseline_images=['polar_theta_position'], style='default') def test_polar_theta_position(): r = np.arange(0, 3.0, 0.01) theta = 2*np.pi*r @@ -632,17 +657,42 @@ def test_polar_theta_position(): fig = plt.figure() ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True) ax.plot(theta, r) - ax.set_theta_zero_location("NW") + ax.set_theta_zero_location("NW", 30) ax.set_theta_direction('clockwise') -@image_comparison(baseline_images=['polar_rlabel_position']) +@image_comparison(baseline_images=['polar_rlabel_position'], style='default') def test_polar_rlabel_position(): fig = plt.figure() ax = fig.add_subplot(111, projection='polar') ax.set_rlabel_position(315) +@image_comparison(baseline_images=['polar_theta_wedge'], style='default', + tol=0.01 if six.PY2 else 0) +def test_polar_theta_limits(): + r = np.arange(0, 3.0, 0.01) + theta = 2*np.pi*r + + theta_mins = np.arange(15.0, 361.0, 90.0) + theta_maxs = np.arange(50.0, 361.0, 90.0) + + fig, axes = plt.subplots(len(theta_mins), len(theta_maxs), + subplot_kw={'polar': True}, + figsize=(8, 6)) + + for i, start in enumerate(theta_mins): + for j, end in enumerate(theta_maxs): + ax = axes[i, j] + if start < end: + ax.plot(theta, r) + ax.yaxis.set_tick_params(label2On=True) + ax.set_thetamin(start) + ax.set_thetamax(end) + else: + ax.set_visible(False) + + @image_comparison(baseline_images=['axvspan_epoch']) def test_axvspan_epoch(): from datetime import datetime @@ -1258,7 +1308,7 @@ def test_markevery_log_scales(): plt.plot(x, y, 'o', ls='-', ms=4, markevery=case) -@image_comparison(baseline_images=['markevery_polar'], +@image_comparison(baseline_images=['markevery_polar'], style='default', remove_text=True) def test_markevery_polar(): cases = [None, diff --git a/lib/matplotlib/tests/test_patches.py b/lib/matplotlib/tests/test_patches.py index 44d8b3915bcf..5edfa10f5644 100644 --- a/lib/matplotlib/tests/test_patches.py +++ b/lib/matplotlib/tests/test_patches.py @@ -339,7 +339,8 @@ def test_multi_color_hatch(): ax.add_patch(r) -@image_comparison(baseline_images=['polar_proj'], extensions=['png']) +@image_comparison(baseline_images=['polar_proj'], extensions=['png'], + style='default') def test_adding_rectangle_patch_with_polar_projection(): fig = plt.figure() ax = fig.add_subplot(111, projection='polar') diff --git a/pytest.ini b/pytest.ini index 0928004accd6..5ca19fee86a7 100644 --- a/pytest.ini +++ b/pytest.ini @@ -42,7 +42,6 @@ pep8ignore = matplotlib/backend_bases.py E225 E501 E712 matplotlib/projections/__init__.py E302 matplotlib/projections/geo.py E203 E221 E231 E261 E302 E402 E501 E502 - matplotlib/projections/polar.py E202 E203 E221 E231 E251 E301 E402 E501 matplotlib/sphinxext/mathmpl.py E302 matplotlib/sphinxext/only_directives.py E302 matplotlib/sphinxext/plot_directive.py E261 E301 E302 E401 E402 E501