diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 96e7e2139504..2aba3df9eb21 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -62,6 +62,12 @@ 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. +Radial ticks have been modified to be parallel to the circular grid line, and +angular ticks have been 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 new behavior: radial tick +labels will be parallel to the circular grid line, and 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 5955ffb5fb1e..6c519a01a1a6 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -1,14 +1,18 @@ from __future__ import (absolute_import, division, print_function, unicode_literals) +import six + from collections import OrderedDict import numpy as np +import matplotlib.artist as martist from matplotlib.axes import Axes 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 +244,153 @@ 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. + + 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( + 0, 0, + axes.figure.dpi_scale_trans) + self._text2_translate = mtransforms.ScaledTranslation( + 0, 0, + axes.figure.dpi_scale_trans) + super(ThetaTick, self).__init__(axes, *args, **kwargs) + + def _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 = 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): + super(ThetaTick, self)._apply_params(**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, 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) + self._text2_translate.invalidate() + + def 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) + + 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 + + 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 + angle = np.rad2deg(angle) + user_angle + 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. + pad = self._pad + 7 + self._update_padding(pad, + self._loc * axes.get_theta_direction() + + axes.get_theta_offset()) + + +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 ThetaTick(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): + super(ThetaAxis, self).cla() + self.set_ticks_position('none') + self._wrap_locator_formatter() + + def _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 + super(ThetaAxis, self)._copy_tick_props(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): """ Used to locate radius ticks. @@ -284,6 +435,228 @@ 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 + + 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. + + 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, when 'auto' rotation is + enabled. + """ + def _get_text1(self): + t = super(RadialTick, self)._get_text1() + t.set_rotation_mode('anchor') + return t + + def _get_text2(self): + t = super(RadialTick, self)._get_text2() + 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 + 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 = 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: + 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 + mode, user_angle = self._labelrotation + if mode == 'auto': + text_angle += user_angle + else: + text_angle = user_angle + if self.label1On: + 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: + 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 angle > 90: + text_angle = angle - 180 + elif angle < -90: + text_angle = angle + 180 + else: + text_angle = angle + 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) + self.label2.set_va(va) + self.label2.set_rotation(text_angle) + 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. + + 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 RadialTick(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): + super(RadialAxis, self).cla() + self.set_ticks_position('none') + self._wrap_locator_formatter() + + def _set_scale(self, value, **kwargs): + super(RadialAxis, self)._set_scale(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 +770,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 +777,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 +789,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. @@ -503,14 +865,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 @@ -533,10 +888,16 @@ 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' + 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_text2_transform, 'center', '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'): @@ -549,51 +910,24 @@ 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 _is_full_circle_rad(thetamin, thetamax): + return self._yaxis_text_transform, 'bottom', 'left' + elif self.get_theta_direction() > 0: + halign = 'left' + pad_shift = _ThetaShift(self, pad, 'min') 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] + halign = 'right' + pad_shift = _ThetaShift(self, pad, 'max') + return self._yaxis_text_transform + pad_shift, 'center', halign 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: + halign = 'right' + pad_shift = _ThetaShift(self, pad, 'max') 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] + 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 @@ -738,6 +1072,10 @@ 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() + self.yaxis.set_clip_path(self.patch) def get_theta_direction(self): """ @@ -794,6 +1132,8 @@ 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() + self.yaxis.set_clip_path(self.patch) def set_yscale(self, *args, **kwargs): Axes.set_yscale(self, *args, **kwargs) 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 6682d5c93a2d..ea3bcb619069 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 14cb8b2720c4..fc9d79addc9c 100644 --- a/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg +++ b/lib/matplotlib/tests/baseline_images/test_axes/markevery_polar.svg @@ -60,56 +60,56 @@ z - - - - - - - - @@ -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 - +