From 73e4425ce2ef7fbc553f9d1a55c0df8b06309df9 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 4 Aug 2017 00:51:52 -0400 Subject: [PATCH 01/15] Use custom *Axis in polar plots. --- lib/matplotlib/projections/polar.py | 80 ++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 5955ffb5fb1e..d927e8b9f8f8 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -240,6 +240,39 @@ def zoom(self, direction): return self.base.zoom(direction) +class ThetaAxis(maxis.XAxis): + """ + A theta Axis. + + This overrides certain properties of an `XAxis` to provide special-casing + for an angular axis. + """ + __name__ = 'thetaaxis' + axis_name = 'theta' + + def _get_tick(self, major): + if major: + tick_kw = self._major_tick_kw + else: + tick_kw = self._minor_tick_kw + return maxis.XTick(self.axes, 0, '', major=major, **tick_kw) + + def _wrap_locator_formatter(self): + self.set_major_locator(ThetaLocator(self.get_major_locator())) + self.set_major_formatter(ThetaFormatter()) + self.isDefault_majloc = True + self.isDefault_majfmt = True + + def cla(self): + maxis.XAxis.cla(self) + self.set_ticks_position('none') + self._wrap_locator_formatter() + + def _set_scale(self, value, **kwargs): + maxis.XAxis._set_scale(self, value, **kwargs) + self._wrap_locator_formatter() + + class RadialLocator(mticker.Locator): """ Used to locate radius ticks. @@ -284,6 +317,38 @@ def view_limits(self, vmin, vmax): return mtransforms.nonsingular(min(0, vmin), vmax) +class RadialAxis(maxis.YAxis): + """ + A radial Axis. + + This overrides certain properties of a `YAxis` to provide special-casing + for a radial axis. + """ + __name__ = 'radialaxis' + axis_name = 'radius' + + def _get_tick(self, major): + if major: + tick_kw = self._major_tick_kw + else: + tick_kw = self._minor_tick_kw + return maxis.YTick(self.axes, 0, '', major=major, **tick_kw) + + def _wrap_locator_formatter(self): + self.set_major_locator(RadialLocator(self.get_major_locator(), + self.axes)) + self.isDefault_majloc = True + + def cla(self): + maxis.YAxis.cla(self) + self.set_ticks_position('none') + self._wrap_locator_formatter() + + def _set_scale(self, value, **kwargs): + maxis.YAxis._set_scale(self, value, **kwargs) + self._wrap_locator_formatter() + + def _is_full_circle_deg(thetamin, thetamax): """ Determine if a wedge (in degrees) spans the full circle. @@ -397,8 +462,6 @@ def cla(self): self.title.set_y(1.05) - self.xaxis.set_major_formatter(self.ThetaFormatter()) - self.xaxis.isDefault_majfmt = True start = self.spines.get('start', None) if start: start.set_visible(False) @@ -406,20 +469,11 @@ def cla(self): 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') - # 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) @@ -427,8 +481,8 @@ def cla(self): def _init_axis(self): "move this out of __init__ because non-separable axes don't use it" - self.xaxis = maxis.XAxis(self) - self.yaxis = maxis.YAxis(self) + self.xaxis = ThetaAxis(self) + self.yaxis = RadialAxis(self) # Calling polar_axes.xaxis.cla() or polar_axes.xaxis.cla() # results in weird artifacts. Therefore we disable this for # now. From 30b4a6c8c807a17e647cb92531a7aaea3213ecc0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 4 Aug 2017 02:01:09 -0400 Subject: [PATCH 02/15] Add a custom ThetaTick for polar plots. This is able to add a position-dependent rotation to the tick marker, ensuring it is correctly perpendicular to the spine. It also rotates the tick label so that it is parallel to the spine. --- lib/matplotlib/projections/polar.py | 59 +++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index d927e8b9f8f8..48aad3104ef6 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -9,6 +9,7 @@ import matplotlib.axis as maxis from matplotlib import cbook from matplotlib import docstring +import matplotlib.markers as mmarkers import matplotlib.patches as mpatches import matplotlib.path as mpath from matplotlib import rcParams @@ -240,6 +241,58 @@ def zoom(self, direction): return self.base.zoom(direction) +class ThetaTick(maxis.XTick): + """ + A theta-axis tick. + + This subclass of `XTick` provides angular ticks with some small + modification to their re-positioning such that ticks are rotated based on + tick location. This results in ticks that are correctly perpendicular to + the arc spine. Labels are also rotated to be parallel to the spine. + """ + def _get_text1(self): + t = maxis.XTick._get_text1(self) + t.set_rotation_mode('anchor') + return t + + def _get_text2(self): + t = maxis.XTick._get_text2(self) + t.set_rotation_mode('anchor') + return t + + def update_position(self, loc): + maxis.XTick.update_position(self, loc) + axes = self.axes + angle = (loc * axes.get_theta_direction() + + axes.get_theta_offset() - np.pi / 2) + + if self.tick1On: + marker = self.tick1line.get_marker() + if marker in (mmarkers.TICKUP, '|'): + trans = mtransforms.Affine2D().scale(1.0, 1.0).rotate(angle) + elif marker == mmarkers.TICKDOWN: + trans = mtransforms.Affine2D().scale(1.0, -1.0).rotate(angle) + else: + # Don't modify custom tick line markers. + trans = self.tick1line._marker._transform + self.tick1line._marker._transform = trans + if self.tick2On: + marker = self.tick2line.get_marker() + if marker in (mmarkers.TICKUP, '|'): + trans = mtransforms.Affine2D().scale(1.0, 1.0).rotate(angle) + elif marker == mmarkers.TICKDOWN: + trans = mtransforms.Affine2D().scale(1.0, -1.0).rotate(angle) + else: + # Don't modify custom tick line markers. + trans = self.tick2line._marker._transform + self.tick2line._marker._transform = trans + + if self.label1On: + self.label1.set_rotation(np.rad2deg(angle) + self._labelrotation) + if self.label2On: + self.label2.set_rotation(np.rad2deg(angle) + self._labelrotation) + + class ThetaAxis(maxis.XAxis): """ A theta Axis. @@ -255,7 +308,7 @@ def _get_tick(self, major): tick_kw = self._major_tick_kw else: tick_kw = self._minor_tick_kw - return maxis.XTick(self.axes, 0, '', major=major, **tick_kw) + return ThetaTick(self.axes, 0, '', major=major, **tick_kw) def _wrap_locator_formatter(self): self.set_major_locator(ThetaLocator(self.get_major_locator())) @@ -587,10 +640,10 @@ def get_xaxis_transform(self, which='grid'): return self._xaxis_transform def get_xaxis_text1_transform(self, pad): - return self._xaxis_text1_transform, 'center', 'center' + return self._xaxis_text1_transform, 'bottom', 'center' def get_xaxis_text2_transform(self, pad): - return self._xaxis_text2_transform, 'center', 'center' + return self._xaxis_text2_transform, 'top', 'center' def get_yaxis_transform(self, which='grid'): if which in ('tick1', 'tick2'): From 8c430e84863f1a1f7492206ea399215b75a7c4cd Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Fri, 4 Aug 2017 04:06:29 -0400 Subject: [PATCH 03/15] Add a custom RadialTick for polar plots. Just like ThetaTick, these allow for text and tick to be correctly perpendicular to the spine. --- lib/matplotlib/projections/polar.py | 142 ++++++++++++++++++++-------- 1 file changed, 101 insertions(+), 41 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 48aad3104ef6..50f78be9c8b2 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -370,6 +370,96 @@ def view_limits(self, vmin, vmax): return mtransforms.nonsingular(min(0, vmin), vmax) +class RadialTick(maxis.YTick): + """ + A radial-axis tick. + + This subclass of `YTick` provides radial ticks with some small modification + to their re-positioning such that ticks are rotated based on axes limits. + This results in ticks that are correctly perpendicular to the spine. Labels + are also rotated to be perpendicular to the spine. + """ + def _get_text1(self): + t = maxis.YTick._get_text1(self) + t.set_rotation_mode('anchor') + return t + + def _get_text2(self): + t = maxis.YTick._get_text2(self) + t.set_rotation_mode('anchor') + return t + + def update_position(self, loc): + maxis.YTick.update_position(self, loc) + axes = self.axes + thetamin = axes.get_thetamin() + thetamax = axes.get_thetamax() + direction = axes.get_theta_direction() + offset_rad = axes.get_theta_offset() + offset = np.rad2deg(offset_rad) + full = _is_full_circle_deg(thetamin, thetamax) + + if full: + angle = axes.get_rlabel_position() * direction + offset - 90 + tick_angle = np.deg2rad(angle) + else: + angle = thetamin * direction + offset - 90 + if direction > 0: + tick_angle = np.deg2rad(angle) + else: + tick_angle = np.deg2rad(angle + 180) + if self.label1On: + self.label1.set_rotation(angle + self._labelrotation) + if self.tick1On: + marker = self.tick1line.get_marker() + if marker == mmarkers.TICKLEFT: + trans = (mtransforms.Affine2D() + .scale(1.0, 1.0) + .rotate(tick_angle)) + elif marker == '_': + trans = (mtransforms.Affine2D() + .scale(1.0, 1.0) + .rotate(tick_angle + np.pi / 2)) + elif marker == mmarkers.TICKRIGHT: + trans = (mtransforms.Affine2D() + .scale(-1.0, 1.0) + .rotate(tick_angle)) + else: + # Don't modify custom tick line markers. + trans = self.tick1line._marker._transform + self.tick1line._marker._transform = trans + + if full: + self.label2On = False + self.tick2On = False + else: + angle = thetamax * direction + offset - 90 + if direction > 0: + tick_angle = np.deg2rad(angle) + else: + tick_angle = np.deg2rad(angle + 180) + if self.label2On: + self.label2.set_rotation(angle + self._labelrotation) + if self.tick2On: + marker = self.tick2line.get_marker() + if marker == mmarkers.TICKLEFT: + trans = (mtransforms.Affine2D() + .scale(1.0, 1.0) + .rotate(tick_angle)) + elif marker == '_': + trans = (mtransforms.Affine2D() + .scale(1.0, 1.0) + .rotate(tick_angle + np.pi / 2)) + elif marker == mmarkers.TICKRIGHT: + trans = (mtransforms.Affine2D() + .scale(-1.0, 1.0) + .rotate(tick_angle)) + else: + # Don't modify custom tick line markers. + trans = self.tick2line._marker._transform + self.tick2line._marker._transform = trans + + class RadialAxis(maxis.YAxis): """ A radial Axis. @@ -385,7 +475,7 @@ def _get_tick(self, major): tick_kw = self._major_tick_kw else: tick_kw = self._minor_tick_kw - return maxis.YTick(self.axes, 0, '', major=major, **tick_kw) + return RadialTick(self.axes, 0, '', major=major, **tick_kw) def _wrap_locator_formatter(self): self.set_major_locator(RadialLocator(self.get_major_locator(), @@ -657,50 +747,16 @@ def get_yaxis_transform(self, which='grid'): def get_yaxis_text1_transform(self, pad): thetamin, thetamax = self._realViewLim.intervalx full = _is_full_circle_rad(thetamin, thetamax) - if full: - angle = self.get_rlabel_position() + if self.get_theta_direction() > 0 or full: + return self._yaxis_text_transform, 'center', 'left' else: - angle = np.rad2deg(thetamin) - if angle < 0: - angle += 360 - angle %= 360 - - # 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']] - - ind = np.digitize([angle], np.arange(0, 361, 90))[0] - 1 - - return self._yaxis_text_transform, valign[full][ind], halign[full][ind] + return self._yaxis_text_transform, 'center', 'right' 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() + if self.get_theta_direction() > 0: + return self._yaxis_text_transform, 'center', 'right' 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] + return self._yaxis_text_transform, 'center', 'left' def draw(self, *args, **kwargs): thetamin, thetamax = self._realViewLim.intervalx @@ -845,6 +901,9 @@ def set_theta_direction(self, direction): raise ValueError( "direction must be 1, -1, clockwise or counterclockwise") self._direction.invalidate() + # FIXME: Why is this needed? Even though the tick label gets + # re-created, the alignment is not correctly updated without a reset. + self.yaxis.reset_ticks() def get_theta_direction(self): """ @@ -901,6 +960,7 @@ def set_rlabel_position(self, value): The angular position of the radius labels in degrees. """ self._r_label_position.clear().translate(np.deg2rad(value), 0.0) + self.yaxis.reset_ticks() def set_yscale(self, *args, **kwargs): Axes.set_yscale(self, *args, **kwargs) From 386bb303171ac6bfb9c49ffc23c307eb10f595c6 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 10 Aug 2017 01:54:55 -0400 Subject: [PATCH 04/15] Apply padding to radial ticks in polar plots. --- lib/matplotlib/projections/polar.py | 72 ++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 50f78be9c8b2..4a08b7261a09 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -370,6 +370,58 @@ def view_limits(self, vmin, vmax): return mtransforms.nonsingular(min(0, vmin), vmax) +class _ThetaShift(mtransforms.ScaledTranslation): + """ + Apply a padding shift based on axes theta limits. + + This is used to create padding for radial ticks. + + Parameters + ---------- + axes : matplotlib.axes.Axes + The owning axes; used to determine limits. + pad : float + The padding to apply, in points. + start : str, {'min', 'max', 'rlabel'} + Whether to shift away from the start (``'min'``) or the end (``'max'``) + of the axes, or using the rlabel position (``'rlabel'``). + """ + def __init__(self, axes, pad, mode): + mtransforms.ScaledTranslation.__init__(self, pad, pad, + axes.figure.dpi_scale_trans) + self.set_children(axes._realViewLim) + self.axes = axes + self.mode = mode + self.pad = pad + + def get_matrix(self): + if self._invalid: + if self.mode == 'rlabel': + angle = ( + np.deg2rad(self.axes.get_rlabel_position()) * + self.axes.get_theta_direction() + + self.axes.get_theta_offset() + ) + else: + if self.mode == 'min': + angle = self.axes._realViewLim.xmin + elif self.mode == 'max': + angle = self.axes._realViewLim.xmax + angle %= 2 * np.pi + if angle < 0: + angle += 2 * np.pi + + if self.mode in ('rlabel', 'min'): + padx = np.cos(angle - np.pi / 2) + pady = np.sin(angle - np.pi / 2) + else: + padx = np.cos(angle + np.pi / 2) + pady = np.sin(angle + np.pi / 2) + + self._t = (self.pad * padx / 72, self.pad * pady / 72) + return mtransforms.ScaledTranslation.get_matrix(self) + + class RadialTick(maxis.YTick): """ A radial-axis tick. @@ -746,17 +798,25 @@ def get_yaxis_transform(self, which='grid'): def get_yaxis_text1_transform(self, pad): thetamin, thetamax = self._realViewLim.intervalx - full = _is_full_circle_rad(thetamin, thetamax) - if self.get_theta_direction() > 0 or full: - return self._yaxis_text_transform, 'center', 'left' + if _is_full_circle_rad(thetamin, thetamax): + halign = 'left' + pad_shift = _ThetaShift(self, pad, 'rlabel') + elif self.get_theta_direction() > 0: + halign = 'left' + pad_shift = _ThetaShift(self, pad, 'min') else: - return self._yaxis_text_transform, 'center', 'right' + halign = 'right' + pad_shift = _ThetaShift(self, pad, 'max') + return self._yaxis_text_transform + pad_shift, 'center', halign def get_yaxis_text2_transform(self, pad): if self.get_theta_direction() > 0: - return self._yaxis_text_transform, 'center', 'right' + halign = 'right' + pad_shift = _ThetaShift(self, pad, 'max') else: - return self._yaxis_text_transform, 'center', 'left' + halign = 'left' + pad_shift = _ThetaShift(self, pad, 'min') + return self._yaxis_text_transform + pad_shift, 'center', halign def draw(self, *args, **kwargs): thetamin, thetamax = self._realViewLim.intervalx From ba242b16cd7a2290022ee62a12e377c0b9d6ebc8 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 10 Aug 2017 17:26:33 -0400 Subject: [PATCH 05/15] Apply padding to theta ticks in polar plots. --- lib/matplotlib/projections/polar.py | 65 ++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 4a08b7261a09..361a9bde190a 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -5,6 +5,7 @@ import numpy as np +import matplotlib.artist as martist from matplotlib.axes import Axes import matplotlib.axis as maxis from matplotlib import cbook @@ -248,18 +249,52 @@ class ThetaTick(maxis.XTick): This subclass of `XTick` provides angular ticks with some small modification to their re-positioning such that ticks are rotated based on tick location. This results in ticks that are correctly perpendicular to - the arc spine. Labels are also rotated to be parallel to the spine. + the arc spine. + + Labels are also rotated to be parallel to the spine. The label padding is + also applied here since it's not possible to use a generic axes transform + to produce tick-specific padding. """ + def __init__(self, axes, *args, **kwargs): + self._text1_translate = mtransforms.ScaledTranslation( + 0, 0, + axes.figure.dpi_scale_trans) + self._text2_translate = mtransforms.ScaledTranslation( + 0, 0, + axes.figure.dpi_scale_trans) + maxis.XTick.__init__(self, axes, *args, **kwargs) + def _get_text1(self): t = maxis.XTick._get_text1(self) t.set_rotation_mode('anchor') + t.set_transform(t.get_transform() + self._text1_translate) return t def _get_text2(self): t = maxis.XTick._get_text2(self) t.set_rotation_mode('anchor') + t.set_transform(t.get_transform() + self._text2_translate) return t + def _apply_params(self, **kw): + maxis.XTick._apply_params(self, **kw) + + # Ensure transform is correct; sometimes this gets reset. + trans = self.label1.get_transform() + if not trans.contains_branch(self._text1_translate): + self.label1.set_transform(trans + self._text1_translate) + trans = self.label2.get_transform() + if not trans.contains_branch(self._text2_translate): + self.label2.set_transform(trans + self._text2_translate) + + def _update_padding(self, angle): + padx = self._pad * np.cos(angle) / 72 + pady = self._pad * np.sin(angle) / 72 + self._text1_translate._t = (padx, pady) + self._text1_translate.invalidate() + self._text2_translate._t = (-padx, -pady) + self._text2_translate.invalidate() + def update_position(self, loc): maxis.XTick.update_position(self, loc) axes = self.axes @@ -292,6 +327,9 @@ def update_position(self, loc): if self.label2On: self.label2.set_rotation(np.rad2deg(angle) + self._labelrotation) + self._update_padding(self._loc * axes.get_theta_direction() + + axes.get_theta_offset()) + class ThetaAxis(maxis.XAxis): """ @@ -325,6 +363,18 @@ def _set_scale(self, value, **kwargs): maxis.XAxis._set_scale(self, value, **kwargs) self._wrap_locator_formatter() + def _copy_tick_props(self, src, dest): + 'Copy the props from src tick to dest tick' + if src is None or dest is None: + return + maxis.XAxis._copy_tick_props(self, src, dest) + + # Ensure that tick transforms are independent so that padding works. + trans = dest._get_text1_transform()[0] + dest.label1.set_transform(trans + dest._text1_translate) + trans = dest._get_text2_transform()[0] + dest.label2.set_transform(trans + dest._text2_translate) + class RadialLocator(mticker.Locator): """ @@ -752,14 +802,7 @@ def _set_lim_and_transforms(self): .translate(0.0, -0.5) \ .scale(1.0, -1.0) \ .translate(0.0, 0.5) - self._xaxis_text1_transform = ( - flipr_transform + - mtransforms.Affine2D().translate(0.0, 0.1) + - self._xaxis_transform) - self._xaxis_text2_transform = ( - flipr_transform + - mtransforms.Affine2D().translate(0.0, -0.1) + - self._xaxis_transform) + self._xaxis_text_transform = flipr_transform + 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 thetamin to @@ -782,10 +825,10 @@ def get_xaxis_transform(self, which='grid'): return self._xaxis_transform def get_xaxis_text1_transform(self, pad): - return self._xaxis_text1_transform, 'bottom', 'center' + return self._xaxis_text_transform, 'bottom', 'center' def get_xaxis_text2_transform(self, pad): - return self._xaxis_text2_transform, 'top', 'center' + return self._xaxis_text_transform, 'top', 'center' def get_yaxis_transform(self, which='grid'): if which in ('tick1', 'tick2'): From a2531eec3f96759e44fe68317bd156f3469c4d73 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 29 Aug 2017 02:41:54 -0400 Subject: [PATCH 06/15] Add clip path when resetting radial ticks. --- lib/matplotlib/projections/polar.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 361a9bde190a..67c55ca177e1 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -1007,6 +1007,7 @@ def set_theta_direction(self, direction): # FIXME: Why is this needed? Even though the tick label gets # re-created, the alignment is not correctly updated without a reset. self.yaxis.reset_ticks() + self.yaxis.set_clip_path(self.patch) def get_theta_direction(self): """ @@ -1064,6 +1065,7 @@ def set_rlabel_position(self, value): """ self._r_label_position.clear().translate(np.deg2rad(value), 0.0) self.yaxis.reset_ticks() + self.yaxis.set_clip_path(self.patch) def set_yscale(self, *args, **kwargs): Axes.set_yscale(self, *args, **kwargs) From 5f1ecbd21795b96d262e516468a11f40fc2f7ce0 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Tue, 29 Aug 2017 03:23:19 -0400 Subject: [PATCH 07/15] Add info about polar tick(label) changes. --- doc/users/whats_new.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 96e7e2139504..0d5cb25cd7e8 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -62,6 +62,10 @@ 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. +For plots of a partial circle, radial ticks and tick labels have been modified +to be parallel to the circular grid line. Angular ticks have been modified to +be parallel to the grid line and the labels are now perpendicular to the grid +line (i.e., parallel to the outer boundary.) Merge JSAnimation From 9529875907dbc3dc51556c51c9bbfa45325502ce Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2017 19:33:54 -0400 Subject: [PATCH 08/15] Restore old polar tick label behaviour for full circles. --- lib/matplotlib/projections/polar.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 67c55ca177e1..084ed9300136 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -322,10 +322,12 @@ def update_position(self, loc): trans = self.tick2line._marker._transform self.tick2line._marker._transform = trans - if self.label1On: - self.label1.set_rotation(np.rad2deg(angle) + self._labelrotation) - if self.label2On: - self.label2.set_rotation(np.rad2deg(angle) + self._labelrotation) + if not _is_full_circle_deg(axes.get_thetamin(), axes.get_thetamax()): + angle = np.rad2deg(angle) + self._labelrotation + if self.label1On: + self.label1.set_rotation(angle) + if self.label2On: + self.label2.set_rotation(angle) self._update_padding(self._loc * axes.get_theta_direction() + axes.get_theta_offset()) @@ -479,7 +481,8 @@ class RadialTick(maxis.YTick): This subclass of `YTick` provides radial ticks with some small modification to their re-positioning such that ticks are rotated based on axes limits. This results in ticks that are correctly perpendicular to the spine. Labels - are also rotated to be perpendicular to the spine. + are also rotated to be perpendicular to the spine, but only for wedges, to + preserve backwards compatibility. """ def _get_text1(self): t = maxis.YTick._get_text1(self) @@ -502,7 +505,7 @@ def update_position(self, loc): full = _is_full_circle_deg(thetamin, thetamax) if full: - angle = axes.get_rlabel_position() * direction + offset - 90 + angle = 0 tick_angle = np.deg2rad(angle) else: angle = thetamin * direction + offset - 90 @@ -825,10 +828,16 @@ def get_xaxis_transform(self, which='grid'): return self._xaxis_transform def get_xaxis_text1_transform(self, pad): - return self._xaxis_text_transform, 'bottom', 'center' + if _is_full_circle_rad(*self._realViewLim.intervalx): + return self._xaxis_text_transform, 'center', 'center' + else: + return self._xaxis_text_transform, 'bottom', 'center' def get_xaxis_text2_transform(self, pad): - return self._xaxis_text_transform, 'top', 'center' + if _is_full_circle_rad(*self._realViewLim.intervalx): + return self._xaxis_text_transform, 'center', 'center' + else: + return self._xaxis_text_transform, 'top', 'center' def get_yaxis_transform(self, which='grid'): if which in ('tick1', 'tick2'): @@ -842,8 +851,7 @@ def get_yaxis_transform(self, which='grid'): def get_yaxis_text1_transform(self, pad): thetamin, thetamax = self._realViewLim.intervalx if _is_full_circle_rad(thetamin, thetamax): - halign = 'left' - pad_shift = _ThetaShift(self, pad, 'rlabel') + return self._yaxis_text_transform, 'bottom', 'left' elif self.get_theta_direction() > 0: halign = 'left' pad_shift = _ThetaShift(self, pad, 'min') From 5b8d2df03aa595272cd717d583e7b836d24d3650 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2017 19:55:07 -0400 Subject: [PATCH 09/15] Tweak padding on angular ticks to match previous. --- lib/matplotlib/projections/polar.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 084ed9300136..435cd01de010 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -287,9 +287,9 @@ def _apply_params(self, **kw): if not trans.contains_branch(self._text2_translate): self.label2.set_transform(trans + self._text2_translate) - def _update_padding(self, angle): - padx = self._pad * np.cos(angle) / 72 - pady = self._pad * np.sin(angle) / 72 + def _update_padding(self, pad, angle): + padx = pad * np.cos(angle) / 72 + pady = pad * np.sin(angle) / 72 self._text1_translate._t = (padx, pady) self._text1_translate.invalidate() self._text2_translate._t = (-padx, -pady) @@ -329,7 +329,11 @@ def update_position(self, loc): if self.label2On: self.label2.set_rotation(angle) - self._update_padding(self._loc * axes.get_theta_direction() + + # This extra padding helps preserve the look from previous releases but + # is also needed because labels are anchored to their center. + pad = self._pad + 7 + self._update_padding(pad, + self._loc * axes.get_theta_direction() + axes.get_theta_offset()) From 33377ce75f91469f33888902c957efd39fd0f242 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 30 Aug 2017 22:10:58 -0400 Subject: [PATCH 10/15] Enable ticks on some polar plot tests. This enables checking that they appear correctly rotated. --- lib/matplotlib/tests/test_axes.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 25a4fff43edc..76e2114d117a 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -382,6 +382,8 @@ def test_polar_annotations(): verticalalignment='baseline', ) + ax.tick_params(axis='x', tick1On=True, tick2On=True, direction='out') + @image_comparison(baseline_images=['polar_coords'], style='default', remove_text=True) @@ -676,6 +678,7 @@ def test_polar_theta_limits(): theta_mins = np.arange(15.0, 361.0, 90.0) theta_maxs = np.arange(50.0, 361.0, 90.0) + DIRECTIONS = ('out', 'in', 'inout') fig, axes = plt.subplots(len(theta_mins), len(theta_maxs), subplot_kw={'polar': True}, @@ -686,9 +689,11 @@ def test_polar_theta_limits(): 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) + ax.tick_params(tick1On=True, tick2On=True, + direction=DIRECTIONS[i % len(DIRECTIONS)]) + ax.yaxis.set_tick_params(label2On=True) else: ax.set_visible(False) From 80a5f10a7d11ee5768af520a62a2e7de6dcc7eb1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Thu, 31 Aug 2017 15:10:06 -0400 Subject: [PATCH 11/15] Cleanup polar tick changes based on review. --- lib/matplotlib/projections/polar.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 435cd01de010..d16c962bd080 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -262,22 +262,22 @@ def __init__(self, axes, *args, **kwargs): self._text2_translate = mtransforms.ScaledTranslation( 0, 0, axes.figure.dpi_scale_trans) - maxis.XTick.__init__(self, axes, *args, **kwargs) + super(ThetaTick, self).__init__(axes, *args, **kwargs) def _get_text1(self): - t = maxis.XTick._get_text1(self) + t = super(ThetaTick, self)._get_text1() t.set_rotation_mode('anchor') t.set_transform(t.get_transform() + self._text1_translate) return t def _get_text2(self): - t = maxis.XTick._get_text2(self) + t = super(ThetaTick, self)._get_text2() t.set_rotation_mode('anchor') t.set_transform(t.get_transform() + self._text2_translate) return t def _apply_params(self, **kw): - maxis.XTick._apply_params(self, **kw) + super(ThetaTick, self)._apply_params(**kw) # Ensure transform is correct; sometimes this gets reset. trans = self.label1.get_transform() @@ -296,7 +296,7 @@ def _update_padding(self, pad, angle): self._text2_translate.invalidate() def update_position(self, loc): - maxis.XTick.update_position(self, loc) + super(ThetaTick, self).update_position(loc) axes = self.axes angle = (loc * axes.get_theta_direction() + axes.get_theta_offset() - np.pi / 2) @@ -361,19 +361,19 @@ def _wrap_locator_formatter(self): self.isDefault_majfmt = True def cla(self): - maxis.XAxis.cla(self) + super(ThetaAxis, self).cla() self.set_ticks_position('none') self._wrap_locator_formatter() def _set_scale(self, value, **kwargs): - maxis.XAxis._set_scale(self, value, **kwargs) + super(ThetaAxis, self)._set_scale(value, **kwargs) self._wrap_locator_formatter() def _copy_tick_props(self, src, dest): 'Copy the props from src tick to dest tick' if src is None or dest is None: return - maxis.XAxis._copy_tick_props(self, src, dest) + super(ThetaAxis, self)._copy_tick_props(src, dest) # Ensure that tick transforms are independent so that padding works. trans = dest._get_text1_transform()[0] @@ -463,9 +463,6 @@ def get_matrix(self): angle = self.axes._realViewLim.xmin elif self.mode == 'max': angle = self.axes._realViewLim.xmax - angle %= 2 * np.pi - if angle < 0: - angle += 2 * np.pi if self.mode in ('rlabel', 'min'): padx = np.cos(angle - np.pi / 2) @@ -489,17 +486,17 @@ class RadialTick(maxis.YTick): preserve backwards compatibility. """ def _get_text1(self): - t = maxis.YTick._get_text1(self) + t = super(RadialTick, self)._get_text1() t.set_rotation_mode('anchor') return t def _get_text2(self): - t = maxis.YTick._get_text2(self) + t = super(RadialTick, self)._get_text2() t.set_rotation_mode('anchor') return t def update_position(self, loc): - maxis.YTick.update_position(self, loc) + super(RadialTick, self).update_position(loc) axes = self.axes thetamin = axes.get_thetamin() thetamax = axes.get_thetamax() @@ -592,12 +589,12 @@ def _wrap_locator_formatter(self): self.isDefault_majloc = True def cla(self): - maxis.YAxis.cla(self) + super(RadialAxis, self).cla() self.set_ticks_position('none') self._wrap_locator_formatter() def _set_scale(self, value, **kwargs): - maxis.YAxis._set_scale(self, value, **kwargs) + super(RadialAxis, self)._set_scale(value, **kwargs) self._wrap_locator_formatter() From 77e2532dedb305405903441ef7147406c31624e7 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 25 Sep 2017 17:45:44 -0400 Subject: [PATCH 12/15] Ensure polar ticks are always "right-side up". --- lib/matplotlib/projections/polar.py | 61 ++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index d16c962bd080..32eeca7d1c53 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -323,11 +323,17 @@ def update_position(self, loc): self.tick2line._marker._transform = trans if not _is_full_circle_deg(axes.get_thetamin(), axes.get_thetamax()): - angle = np.rad2deg(angle) + self._labelrotation - if self.label1On: - self.label1.set_rotation(angle) - if self.label2On: - self.label2.set_rotation(angle) + if angle > np.pi / 2: + angle -= np.pi + elif angle < -np.pi / 2: + angle += np.pi + else: + angle = 0 + angle = np.rad2deg(angle) + self._labelrotation + if self.label1On: + self.label1.set_rotation(angle) + if self.label2On: + self.label2.set_rotation(angle) # This extra padding helps preserve the look from previous releases but # is also needed because labels are anchored to their center. @@ -495,6 +501,18 @@ def _get_text2(self): t.set_rotation_mode('anchor') return t + def _determine_anchor(self, angle, start): + if start: + if -90 <= angle <= 90: + return 'left', 'center' + else: + return 'right', 'center' + else: + if -90 <= angle <= 90: + return 'right', 'center' + else: + return 'left', 'center' + def update_position(self, loc): super(RadialTick, self).update_position(loc) axes = self.axes @@ -506,16 +524,31 @@ def update_position(self, loc): full = _is_full_circle_deg(thetamin, thetamax) if full: - angle = 0 - tick_angle = np.deg2rad(angle) + angle = axes.get_rlabel_position() + tick_angle = 0 + text_angle = 0 else: angle = thetamin * direction + offset - 90 if direction > 0: tick_angle = np.deg2rad(angle) else: tick_angle = np.deg2rad(angle + 180) + if angle > 90: + text_angle = angle - 180 + elif angle < -90: + text_angle = angle + 180 + else: + text_angle = angle + text_angle += self._labelrotation if self.label1On: - self.label1.set_rotation(angle + self._labelrotation) + if full: + ha = 'left' + va = 'bottom' + else: + ha, va = self._determine_anchor(angle, True) + self.label1.set_ha(ha) + self.label1.set_va(va) + self.label1.set_rotation(text_angle) if self.tick1On: marker = self.tick1line.get_marker() if marker == mmarkers.TICKLEFT: @@ -544,8 +577,18 @@ def update_position(self, loc): tick_angle = np.deg2rad(angle) else: tick_angle = np.deg2rad(angle + 180) + if angle > 90: + text_angle = angle - 180 + elif angle < -90: + text_angle = angle + 180 + else: + text_angle = angle + text_angle += self._labelrotation if self.label2On: - self.label2.set_rotation(angle + self._labelrotation) + ha, va = self._determine_anchor(angle, False) + self.label2.set_ha(ha) + self.label2.set_va(va) + self.label2.set_rotation(text_angle) if self.tick2On: marker = self.tick2line.get_marker() if marker == mmarkers.TICKLEFT: From 228ee536f81ac92d69caef1a0b40669a5ea96ad1 Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 25 Sep 2017 18:35:13 -0400 Subject: [PATCH 13/15] Add an 'auto' option to label rotation. This option enables the automatic rotation of polar tick labels. --- doc/users/whats_new.rst | 11 +++++--- lib/matplotlib/axis.py | 24 +++++++++++++++-- lib/matplotlib/projections/polar.py | 42 ++++++++++++++++++++--------- lib/matplotlib/tests/test_axes.py | 6 +++-- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 0d5cb25cd7e8..852d64121217 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -62,10 +62,13 @@ 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. -For plots of a partial circle, radial ticks and tick labels have been modified -to be parallel to the circular grid line. Angular ticks have been modified to -be parallel to the grid line and the labels are now perpendicular to the grid -line (i.e., parallel to the outer boundary.) +Radial ticks have be modified to be parallel to the circular grid line. Angular +ticks will be modified to be parallel to the grid line. It may also be useful +to rotate tick labels to match the boundary. Calling +``ax.tick_params(rotation='auto')`` will enable this new behavior. Radial tick +labels will be modified to be parallel to the circular grid line. Angular tick +labels will be perpendicular to the grid line (i.e., parallel to the outer +boundary.) Merge JSAnimation diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index cf7f9b8f80f7..6cc38bb9066e 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -140,7 +140,7 @@ def __init__(self, axes, loc, label, labelsize = rcParams['%s.labelsize' % name] self._labelsize = labelsize - self._labelrotation = labelrotation + self._set_labelrotation(labelrotation) if zorder is None: if major: @@ -167,6 +167,20 @@ def __init__(self, axes, loc, label, self.update_position(loc) + def _set_labelrotation(self, labelrotation): + if isinstance(labelrotation, six.string_types): + mode = labelrotation + angle = 0 + elif isinstance(labelrotation, (tuple, list)): + mode, angle = labelrotation + else: + mode = 'default' + angle = labelrotation + if mode not in ('auto', 'default'): + raise ValueError("Label rotation mode must be 'default' or " + "'auto', not '{}'.".format(mode)) + self._labelrotation = (mode, angle) + def apply_tickdir(self, tickdir): """ Calculate self._pad and self._tickmarkers @@ -331,8 +345,14 @@ def _apply_params(self, **kw): self.tick2line.set(**tick_kw) for k, v in six.iteritems(tick_kw): setattr(self, '_' + k, v) + + if 'labelrotation' in kw: + self._set_labelrotation(kw.pop('labelrotation')) + self.label1.set(rotation=self._labelrotation[1]) + self.label2.set(rotation=self._labelrotation[1]) + label_list = [k for k in six.iteritems(kw) - if k[0] in ['labelsize', 'labelcolor', 'labelrotation']] + if k[0] in ['labelsize', 'labelcolor']] if label_list: label_kw = {k[5:]: v for k, v in label_list} self.label1.set(**label_kw) diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index 32eeca7d1c53..6c519a01a1a6 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -1,6 +1,8 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +import six + from collections import OrderedDict import numpy as np @@ -251,9 +253,9 @@ class ThetaTick(maxis.XTick): tick location. This results in ticks that are correctly perpendicular to the arc spine. - Labels are also rotated to be parallel to the spine. The label padding is - also applied here since it's not possible to use a generic axes transform - to produce tick-specific padding. + When 'auto' rotation is enabled, labels are also rotated to be parallel to + the spine. The label padding is also applied here since it's not possible + to use a generic axes transform to produce tick-specific padding. """ def __init__(self, axes, *args, **kwargs): self._text1_translate = mtransforms.ScaledTranslation( @@ -322,14 +324,15 @@ def update_position(self, loc): trans = self.tick2line._marker._transform self.tick2line._marker._transform = trans - if not _is_full_circle_deg(axes.get_thetamin(), axes.get_thetamax()): + mode, user_angle = self._labelrotation + if mode == 'default': + angle = 0 + else: if angle > np.pi / 2: angle -= np.pi elif angle < -np.pi / 2: angle += np.pi - else: - angle = 0 - angle = np.rad2deg(angle) + self._labelrotation + angle = np.rad2deg(angle) + user_angle if self.label1On: self.label1.set_rotation(angle) if self.label2On: @@ -488,8 +491,8 @@ class RadialTick(maxis.YTick): This subclass of `YTick` provides radial ticks with some small modification to their re-positioning such that ticks are rotated based on axes limits. This results in ticks that are correctly perpendicular to the spine. Labels - are also rotated to be perpendicular to the spine, but only for wedges, to - preserve backwards compatibility. + are also rotated to be perpendicular to the spine, when 'auto' rotation is + enabled. """ def _get_text1(self): t = super(RadialTick, self)._get_text1() @@ -524,9 +527,14 @@ def update_position(self, loc): full = _is_full_circle_deg(thetamin, thetamax) if full: - angle = axes.get_rlabel_position() + angle = axes.get_rlabel_position() * direction + offset - 90 tick_angle = 0 - text_angle = 0 + if angle > 90: + text_angle = angle - 180 + elif angle < -90: + text_angle = angle + 180 + else: + text_angle = angle else: angle = thetamin * direction + offset - 90 if direction > 0: @@ -539,7 +547,11 @@ def update_position(self, loc): text_angle = angle + 180 else: text_angle = angle - text_angle += self._labelrotation + mode, user_angle = self._labelrotation + if mode == 'auto': + text_angle += user_angle + else: + text_angle = user_angle if self.label1On: if full: ha = 'left' @@ -583,7 +595,11 @@ def update_position(self, loc): text_angle = angle + 180 else: text_angle = angle - text_angle += self._labelrotation + mode, user_angle = self._labelrotation + if mode == 'auto': + text_angle += user_angle + else: + text_angle = user_angle if self.label2On: ha, va = self._determine_anchor(angle, False) self.label2.set_ha(ha) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 76e2114d117a..c0f2c728742d 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -668,6 +668,7 @@ def test_polar_rlabel_position(): fig = plt.figure() ax = fig.add_subplot(111, projection='polar') ax.set_rlabel_position(315) + ax.tick_params(rotation='auto') @image_comparison(baseline_images=['polar_theta_wedge'], style='default', @@ -692,8 +693,9 @@ def test_polar_theta_limits(): ax.set_thetamin(start) ax.set_thetamax(end) ax.tick_params(tick1On=True, tick2On=True, - direction=DIRECTIONS[i % len(DIRECTIONS)]) - ax.yaxis.set_tick_params(label2On=True) + direction=DIRECTIONS[i % len(DIRECTIONS)], + rotation='auto') + ax.yaxis.set_tick_params(label2On=True, rotation='auto') else: ax.set_visible(False) From 549c07fc8ad1f3192071c51d375fe04c955e3acb Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Mon, 25 Sep 2017 19:44:39 -0400 Subject: [PATCH 14/15] Re-create polar tests images with new ticks. --- .../test_axes/markevery_polar.png | Bin 95104 -> 95099 bytes .../test_axes/markevery_polar.svg | 1086 ++++++++--------- .../baseline_images/test_axes/polar_axes.pdf | Bin 18390 -> 18555 bytes .../baseline_images/test_axes/polar_axes.png | Bin 74184 -> 74596 bytes .../baseline_images/test_axes/polar_axes.svg | 223 +++- .../test_axes/polar_negative_rmin.pdf | Bin 16918 -> 16923 bytes .../test_axes/polar_negative_rmin.png | Bin 74966 -> 75027 bytes .../test_axes/polar_negative_rmin.svg | 48 +- .../test_axes/polar_rlabel_position.pdf | Bin 12737 -> 12824 bytes .../test_axes/polar_rlabel_position.png | Bin 47952 -> 50548 bytes .../test_axes/polar_rlabel_position.svg | 54 +- .../baseline_images/test_axes/polar_rmin.pdf | Bin 17890 -> 17885 bytes .../baseline_images/test_axes/polar_rmin.png | Bin 71767 -> 71784 bytes .../baseline_images/test_axes/polar_rmin.svg | 52 +- .../test_axes/polar_rorigin.pdf | Bin 20108 -> 20104 bytes .../test_axes/polar_rorigin.png | Bin 83374 -> 83462 bytes .../test_axes/polar_rorigin.svg | 52 +- .../test_axes/polar_theta_position.pdf | Bin 17710 -> 17711 bytes .../test_axes/polar_theta_position.png | Bin 93876 -> 93871 bytes .../test_axes/polar_theta_position.svg | 50 +- .../test_axes/polar_theta_wedge.pdf | Bin 36102 -> 37905 bytes .../test_axes/polar_theta_wedge.png | Bin 95388 -> 108018 bytes .../test_axes/polar_theta_wedge.svg | 864 ++++++++++--- .../baseline_images/test_axes/polar_units.pdf | Bin 16677 -> 16668 bytes .../baseline_images/test_axes/polar_units.png | Bin 71508 -> 71444 bytes .../baseline_images/test_axes/polar_units.svg | 54 +- .../test_axes/polar_units_2.pdf | Bin 17246 -> 17236 bytes .../test_axes/polar_units_2.png | Bin 83276 -> 83308 bytes .../test_axes/polar_units_2.svg | 54 +- .../test_axes/polar_wrap_180.pdf | Bin 13842 -> 13846 bytes .../test_axes/polar_wrap_180.png | Bin 53757 -> 53709 bytes .../test_axes/polar_wrap_180.svg | 66 +- .../test_axes/polar_wrap_360.pdf | Bin 14192 -> 14197 bytes .../test_axes/polar_wrap_360.png | Bin 54236 -> 54193 bytes .../test_axes/polar_wrap_360.svg | 76 +- .../test_patches/polar_proj.png | Bin 63801 -> 63768 bytes 36 files changed, 1667 insertions(+), 1012 deletions(-) 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 6682d5c93a2d4c0a07905ad5971774311c43db83..ea3bcb619069105aa5b381027e56d88c0184eeae 100644 GIT binary patch delta 63 zcmZp8&ieZq>jY0m--%w@S{4Qd+UW+#X(jY26pow1E`evqPdMT-82HNT7$!X^1X~}7cItm#jB?VUc`nicE1v&X8 YIhjd%`9 - - - - - - - - @@ -118,7 +118,7 @@ L 130.570864 90.850435 - - - +" id="me2f31ad8f9" style="stroke:#1f77b4;"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -646,56 +646,56 @@ z - - - - - - - - @@ -704,7 +704,7 @@ L 256.613217 90.850435 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1044,56 +1044,56 @@ z - - - - - - - - @@ -1102,7 +1102,7 @@ L 382.65557 90.850435 - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -1439,56 +1439,56 @@ z - - - - - - - - @@ -1497,7 +1497,7 @@ L 130.570864 160.270956 - - - - - - - + + + + @@ -1815,56 +1815,56 @@ z - - - - - - - - @@ -1873,7 +1873,7 @@ L 256.613217 160.270956 - - - - - - + + + @@ -2190,56 +2190,56 @@ z - - - - - - - - @@ -2248,7 +2248,7 @@ L 382.65557 160.270956 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2597,56 +2597,56 @@ z - - - - - - - - @@ -2655,7 +2655,7 @@ L 130.570864 229.691478 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3004,56 +3004,56 @@ z - - - - - - - - @@ -3062,7 +3062,7 @@ L 256.613217 229.691478 - - - - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -3389,56 +3389,56 @@ z - - - - - - - - @@ -3447,7 +3447,7 @@ L 382.65557 229.691478 - - - - - - - + + + + @@ -3765,56 +3765,56 @@ z - - - - - - - - @@ -3823,7 +3823,7 @@ L 130.570864 299.112 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4172,56 +4172,56 @@ z - - - - - - - - @@ -4230,7 +4230,7 @@ L 256.613217 299.112 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4534,7 +4534,7 @@ C 264.337011 286.218697 265.085217 282.457207 265.085217 278.658783 - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + + + + @@ -112,18 +132,38 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + - - + + + + + + + + + + + + + + + + + @@ -169,7 +209,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -177,11 +217,31 @@ z - - + + + + + + + + + + + + + + + + + @@ -216,7 +276,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -224,11 +284,31 @@ z - - + + + + + + + + + + + + + + + + + @@ -278,7 +358,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -287,11 +367,31 @@ z - - + + + + + + + + + + + + + + + + + @@ -335,7 +435,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -344,11 +444,21 @@ z - - + + + + + + + + + + + @@ -377,7 +487,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -386,11 +496,26 @@ z - - + + + + + + + + + + + + + + @@ -404,7 +529,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -413,14 +538,24 @@ z - - + + + + + + + + + + + - + @@ -431,8 +566,8 @@ L 330.2448 268.6128 - - + - - + - - + - - + - - + - - + - + - - + - + +" id="m593a424f5d" style="stroke:#1f77b4;"/> - - + + @@ -1646,7 +1781,7 @@ z - + - @@ -112,7 +112,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -120,7 +120,7 @@ z - @@ -169,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -178,7 +178,7 @@ z - @@ -216,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -225,7 +225,7 @@ z - @@ -278,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -288,7 +288,7 @@ z - @@ -335,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -345,7 +345,7 @@ z - @@ -377,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -387,7 +387,7 @@ z - @@ -404,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -414,13 +414,13 @@ z - - + @@ -432,7 +432,7 @@ L 328.150441 270.550441 - - - - - - - + - @@ -112,7 +112,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -120,7 +120,7 @@ z - @@ -169,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -178,7 +178,7 @@ z - @@ -216,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -225,7 +225,7 @@ z - @@ -278,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -288,7 +288,7 @@ z - @@ -335,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -345,7 +345,7 @@ z - @@ -377,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -387,7 +387,7 @@ z - @@ -404,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -414,13 +414,13 @@ z - - + @@ -432,7 +432,7 @@ L 330.2448 268.6128 - - + @@ -515,7 +515,7 @@ z - - + @@ -620,7 +620,7 @@ L 289.3824 174.528 - - + @@ -757,7 +757,7 @@ z - - + @@ -952,7 +952,7 @@ L 342.6048 174.528 - - + @@ -1169,7 +1169,7 @@ C 365.774248 209.303607 369.216 192.000754 369.216 174.528 - + - @@ -112,7 +112,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -120,7 +120,7 @@ z - @@ -169,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -178,7 +178,7 @@ z - @@ -216,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -225,7 +225,7 @@ z - @@ -278,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -288,7 +288,7 @@ z - @@ -335,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -345,7 +345,7 @@ z - @@ -377,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -387,7 +387,7 @@ z - @@ -404,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -414,13 +414,13 @@ z - - + @@ -432,7 +432,7 @@ L 328.150441 270.550441 - - - - - - - - - + - @@ -112,7 +112,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -120,7 +120,7 @@ z - @@ -169,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -178,7 +178,7 @@ z - @@ -216,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -225,7 +225,7 @@ z - @@ -278,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -288,7 +288,7 @@ z - @@ -335,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -345,7 +345,7 @@ z - @@ -377,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -387,7 +387,7 @@ z - @@ -404,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -414,13 +414,13 @@ z - - + @@ -432,7 +432,7 @@ L 328.150441 270.550441 - - - - - - - - - + - @@ -112,7 +112,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -120,7 +120,7 @@ z - @@ -169,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -178,7 +178,7 @@ z - @@ -216,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -225,7 +225,7 @@ z - @@ -278,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -288,7 +288,7 @@ z - @@ -335,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -345,7 +345,7 @@ z - @@ -377,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -387,7 +387,7 @@ z - @@ -404,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -414,13 +414,13 @@ z - - + @@ -432,7 +432,7 @@ L 110.680648 241.92 - - - - - - - - + - + + + + + + + + + + + + + + + + @@ -108,7 +128,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -116,11 +136,31 @@ z - - + + + + + + + + + + + + + + + + + @@ -142,7 +182,7 @@ L 4.890625 26.703125 z " id="DejaVuSans-34"/> - + @@ -152,30 +192,50 @@ z - - + + + + + + + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + - - + + + + + + + + + + + + + + + + + @@ -519,7 +609,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -527,11 +617,31 @@ z - - + + + + + + + + + + + + + + + + + @@ -549,7 +659,7 @@ L 12.40625 0 z " id="DejaVuSans-31"/> - + @@ -560,8 +670,8 @@ z - - + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + - - + + + + + + + + + + + - + @@ -884,14 +1024,24 @@ L 347.838094 56.400227 - - + + + + + + + + + + + - + @@ -902,8 +1052,8 @@ L 319.776597 105.004167 - - + + + + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + - - + + + + + + + + + + + - + @@ -1241,14 +1426,24 @@ L 464.349724 52.465792 - - + + + + + + + + + + + - + @@ -1257,11 +1452,31 @@ L 436.765818 100.242519 - - + + + + + + + + + + + + + + + + + @@ -1298,7 +1513,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -1309,8 +1524,8 @@ z - - + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + - - + + + + + + + + + + + - + @@ -1653,14 +1898,24 @@ L 228.216492 146.094595 - - + + + + + + + + + + + - + @@ -1671,30 +1926,45 @@ L 208.299348 162.807062 - - + + + + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + - - + + + + + + + + + + + + + + + + + - + @@ -2021,14 +2321,24 @@ L 338.475511 158.068939 - - + + + + + + + + + + + - + @@ -2039,8 +2349,8 @@ L 335.399764 193.224877 - - + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + - - + + + + + + + + + + + - + @@ -2363,14 +2703,24 @@ L 439.93461 186.527915 - - + + + + + + + + + + + - + @@ -2381,8 +2731,8 @@ L 492.672974 205.72311 - - + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + - - + + + + + + + + + + + + + + - + @@ -2692,14 +3077,29 @@ L 319.233487 258.478248 - - + + + + + + + + + + + + + + - + @@ -2710,30 +3110,50 @@ L 332.233479 280.994895 - - + + + + + + + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + - - + + + + + + + + + + + - + @@ -3060,14 +3500,29 @@ L 434.817025 254.620189 - - + + + + + + + + + + + + + + - + @@ -3078,8 +3533,8 @@ L 494.926923 276.498403 - - + + + + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + - - + + + + + + + + + + + - + @@ -3390,14 +3880,24 @@ L 478.635682 377.001058 - - + + + + + + + + + + + - + @@ -3408,30 +3908,40 @@ L 498.552826 360.28859 - - + + + + + + + + + + + - + - + - - + + + + + + + + + + + - + - + - - + + - + - + - + - + - + - + - + - + - + - @@ -112,7 +112,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -120,7 +120,7 @@ z - @@ -169,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -178,7 +178,7 @@ z - @@ -216,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -225,7 +225,7 @@ z - @@ -278,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -288,7 +288,7 @@ z - @@ -335,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -345,7 +345,7 @@ z - @@ -377,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -387,7 +387,7 @@ z - @@ -404,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -414,13 +414,13 @@ z - - + @@ -515,7 +515,7 @@ L 54.390625 54.6875 z " id="DejaVuSans-67"/> - + @@ -525,7 +525,7 @@ z - - - - - - - - - + - @@ -95,7 +95,7 @@ L 10.6875 0 z " id="DejaVuSans-2e"/> - + @@ -104,7 +104,7 @@ z - @@ -289,7 +289,7 @@ Q 48.484375 72.75 52.59375 71.296875 z " id="DejaVuSans-36"/> - + @@ -309,7 +309,7 @@ z - @@ -341,7 +341,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -360,7 +360,7 @@ z - @@ -385,7 +385,7 @@ L 4.890625 26.703125 z " id="DejaVuSans-34"/> - + @@ -404,13 +404,13 @@ z - - + @@ -429,13 +429,13 @@ L 103.104 174.528 - - + @@ -454,13 +454,13 @@ L 142.0752 268.6128 - - + @@ -479,13 +479,13 @@ L 236.16 307.584 - - + @@ -580,7 +580,7 @@ Q 14.796875 37.203125 14.796875 27.296875 z " id="DejaVuSans-64"/> - + @@ -590,7 +590,7 @@ z - - - - - - - - - + - @@ -112,7 +112,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -120,7 +120,7 @@ z - @@ -169,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -178,7 +178,7 @@ z - @@ -216,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -225,7 +225,7 @@ z - @@ -278,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -288,7 +288,7 @@ z - @@ -335,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -345,7 +345,7 @@ z - @@ -377,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -387,7 +387,7 @@ z - @@ -404,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -414,13 +414,13 @@ z - - + @@ -432,7 +432,7 @@ L 330.2448 268.6128 - - - - - - - @@ -1240,15 +1240,15 @@ C -1.341951 -0.77937 -1.5 -0.397805 -1.5 0 C -1.5 0.397805 -1.341951 0.77937 -1.06066 1.06066 C -0.77937 1.341951 -0.397805 1.5 0 1.5 z -" id="m1cd630211c" style="stroke:#0000ff;"/> +" id="m24b8fe4450" style="stroke:#0000ff;"/> - - - + + + - @@ -1262,11 +1262,11 @@ C -1.341951 -0.77937 -1.5 -0.397805 -1.5 0 C -1.5 0.397805 -1.341951 0.77937 -1.06066 1.06066 C -0.77937 1.341951 -0.397805 1.5 0 1.5 z -" id="m61a4c73bb6" style="stroke:#008000;"/> +" id="m6fd3ff8595" style="stroke:#008000;"/> - - - + + + @@ -1292,7 +1292,7 @@ C 365.774248 209.303607 369.216 192.000754 369.216 174.528 - + - @@ -112,7 +112,7 @@ Q 18.5 74.21875 25 74.21875 z " id="DejaVuSans-b0"/> - + @@ -120,7 +120,7 @@ z - @@ -169,7 +169,7 @@ Q 14.890625 38.140625 10.796875 36.28125 z " id="DejaVuSans-35"/> - + @@ -178,7 +178,7 @@ z - @@ -216,7 +216,7 @@ Q 23.96875 32.421875 30.609375 32.421875 z " id="DejaVuSans-39"/> - + @@ -225,7 +225,7 @@ z - @@ -278,7 +278,7 @@ Q 46.96875 40.921875 40.578125 39.3125 z " id="DejaVuSans-33"/> - + @@ -288,7 +288,7 @@ z - @@ -335,7 +335,7 @@ Q 18.3125 60.0625 18.3125 54.390625 z " id="DejaVuSans-38"/> - + @@ -345,7 +345,7 @@ z - @@ -377,7 +377,7 @@ Q 31.109375 20.453125 19.1875 8.296875 z " id="DejaVuSans-32"/> - + @@ -387,7 +387,7 @@ z - @@ -404,7 +404,7 @@ L 8.203125 64.59375 z " id="DejaVuSans-37"/> - + @@ -414,13 +414,13 @@ z - - + @@ -432,7 +432,7 @@ L 330.2448 268.6128 - - - - - - - @@ -1240,15 +1240,15 @@ C -1.341951 -0.77937 -1.5 -0.397805 -1.5 0 C -1.5 0.397805 -1.341951 0.77937 -1.06066 1.06066 C -0.77937 1.341951 -0.397805 1.5 0 1.5 z -" id="m442dbed36a" style="stroke:#0000ff;"/> +" id="m5089aada37" style="stroke:#0000ff;"/> - - - + + + - @@ -1262,15 +1262,15 @@ C -1.341951 -0.77937 -1.5 -0.397805 -1.5 0 C -1.5 0.397805 -1.341951 0.77937 -1.06066 1.06066 C -0.77937 1.341951 -0.397805 1.5 0 1.5 z -" id="m0b7b5ba9df" style="stroke:#008000;"/> +" id="m20ffc6b985" style="stroke:#008000;"/> - - - + + + - @@ -1284,11 +1284,11 @@ C -1.341951 -0.77937 -1.5 -0.397805 -1.5 0 C -1.5 0.397805 -1.341951 0.77937 -1.06066 1.06066 C -0.77937 1.341951 -0.397805 1.5 0 1.5 z -" id="m16f2dc62e6" style="stroke:#ff0000;"/> +" id="m5ad22babb7" style="stroke:#ff0000;"/> - - - + + + @@ -1314,7 +1314,7 @@ C 365.774248 209.303607 369.216 192.000754 369.216 174.528 - +