From a65151be33b7b50fd9f40c72c5b3e965685cc13b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 27 Aug 2013 15:46:01 -0500 Subject: [PATCH 1/4] removed un-needed legacy code (_AnnotationBase._get_xy_legacy) All of the work done in _get_xy_legacy can be done via transforms returned by _get_xy_transform. Removed the sole usage of this function and the function. --- lib/matplotlib/text.py | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 891240edae9c..4ec2f5f9462e 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1463,9 +1463,6 @@ def _get_xy(self, renderer, x, y, s): if s2 == 'data': y = float(self.convert_yunits(y)) - if s in ['axes points', 'axes pixel', 'figure points', 'figure pixel']: - return self._get_xy_legacy(renderer, x, y, s) - tr = self._get_xy_transform(renderer, s) x1, y1 = tr.transform_point((x, y)) return x1, y1 @@ -1588,37 +1585,6 @@ def _get_ref_xy(self, renderer): # raise ValueError("A bbox instance is expected but got %s" % # str(bbox)) - def _get_xy_legacy(self, renderer, x, y, s): - """ - only used when s in ['axes points', 'axes pixel', 'figure points', - 'figure pixel']. - """ - s_ = s.split() - bbox0, xy0 = None, None - bbox_name, unit = s_ - - if bbox_name == "figure": - bbox0 = self.figure.bbox - elif bbox_name == "axes": - bbox0 = self.axes.bbox - - if unit == "points": - sc = self.figure.get_dpi() / 72. - elif unit == "pixels": - sc = 1 - - l, b, r, t = bbox0.extents - if x < 0: - x = r + x * sc - else: - x = l + x * sc - if y < 0: - y = t + y * sc - else: - y = b + y * sc - - return x, y - def set_annotation_clip(self, b): """ set *annotation_clip* attribute. From 3363b125715724ad89d4b33cbddb23e31e7277d5 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 27 Aug 2013 17:07:58 -0500 Subject: [PATCH 2/4] re factored `_AnnoationBase` to push keeping track of there the secondary object is to the sub-classes. Added properties to maintain back-compatibility added properties to replace xytext and textcoords (xyann, anncoords) properly deprecated `textcoord` and `xytext` in favor of `anncoords` and `xyann`. --- doc/api/api_changes.rst | 15 +++++ lib/matplotlib/offsetbox.py | 54 ++++++++++----- lib/matplotlib/text.py | 78 ++++++++++++++++------ lib/mpl_toolkits/axisartist/axis_artist.py | 2 +- 4 files changed, 108 insertions(+), 41 deletions(-) diff --git a/doc/api/api_changes.rst b/doc/api/api_changes.rst index 4c207c48e0b8..0d0ea43fdf6f 100644 --- a/doc/api/api_changes.rst +++ b/doc/api/api_changes.rst @@ -59,9 +59,24 @@ original location: thus `colorbar.ColorbarBase.outline` is now a `matplotlib.patches.Polygon` object. + * The rcParams `savefig.transparent` has been added to control default transparency when saving figures. +* Slightly refactored the `Annotation` family. The text location in + `Annotation` is now handled entirely handled by the underlying `Text` + object so `set_position` works as expected. The attributes `xytext` and + `textcoords` have been deprecated in favor of `xyann` and `anncoords` so + that `Annotation` and `AnnotaionBbox` can share a common sensibly named + api for getting/setting the location of the text or box. + + - `xyann` -> set the location of the annotation + - `xy` -> set where the arrow points to + - `anncoords` -> set the units of the annotation location + - `xycoords` -> set the units of the point location + - `set_position()` -> `Annotation` only set location of annotation + + .. _changes_in_1_3: diff --git a/lib/matplotlib/offsetbox.py b/lib/matplotlib/offsetbox.py index 07c17c496129..bbfeca8c02f1 100644 --- a/lib/matplotlib/offsetbox.py +++ b/lib/matplotlib/offsetbox.py @@ -1265,6 +1265,10 @@ def __init__(self, offsetbox, xy, self.set_fontsize(fontsize) + self.xybox = xybox + + self.boxcoords = boxcoords + if arrowprops is not None: self._arrow_relpos = self.arrowprops.pop("relpos", (0.5, 0.5)) self.arrow_patch = FancyArrowPatch((0, 0), (1, 1), @@ -1274,8 +1278,8 @@ def __init__(self, offsetbox, xy, self.arrow_patch = None _AnnotationBase.__init__(self, - xy, xytext=xybox, - xycoords=xycoords, textcoords=boxcoords, + xy, + xycoords=xycoords, annotation_clip=annotation_clip) martist.Artist.__init__(self, **kwargs) @@ -1295,6 +1299,22 @@ def __init__(self, offsetbox, xy, self.patch.set(**bboxprops) self._drawFrame = frameon + @property + def xyann(self): + return self.xybox + + @xyann.setter + def xyann(self, xyann): + self.xybox = xyann + + @property + def anncoords(self): + return self.boxcoords + + @anncoords.setter + def anncoords(self, coords): + self.boxcoords = coords + def contains(self, event): t, tinfo = self.offsetbox.contains(event) #if self.arrow_patch is not None: @@ -1352,14 +1372,14 @@ def _update_position_xybox(self, renderer, xy_pixel): patch. """ - x, y = self.xytext - if isinstance(self.textcoords, tuple): - xcoord, ycoord = self.textcoords + x, y = self.xybox + if isinstance(self.boxcoords, tuple): + xcoord, ycoord = self.boxcoords x1, y1 = self._get_xy(renderer, x, y, xcoord) x2, y2 = self._get_xy(renderer, x, y, ycoord) ox0, oy0 = x1, y2 else: - ox0, oy0 = self._get_xy(renderer, x, y, self.textcoords) + ox0, oy0 = self._get_xy(renderer, x, y, self.boxcoords) w, h, xd, yd = self.offsetbox.get_extent(renderer) @@ -1579,32 +1599,30 @@ def __init__(self, annotation, use_blit=False): def save_offset(self): ann = self.annotation - x, y = ann.xytext - if isinstance(ann.textcoords, tuple): - xcoord, ycoord = ann.textcoords + x, y = ann.xyann + if isinstance(ann.anncoords, tuple): + xcoord, ycoord = ann.anncoords x1, y1 = ann._get_xy(self.canvas.renderer, x, y, xcoord) x2, y2 = ann._get_xy(self.canvas.renderer, x, y, ycoord) ox0, oy0 = x1, y2 else: - ox0, oy0 = ann._get_xy(self.canvas.renderer, x, y, ann.textcoords) + ox0, oy0 = ann._get_xy(self.canvas.renderer, x, y, ann.anncoords) self.ox, self.oy = ox0, oy0 - self.annotation.textcoords = "figure pixels" + self.annotation.anncoords = "figure pixels" self.update_offset(0, 0) def update_offset(self, dx, dy): ann = self.annotation - ann.xytext = self.ox + dx, self.oy + dy - x, y = ann.xytext - # xy is never used - xy = ann._get_xy(self.canvas.renderer, x, y, ann.textcoords) + ann.xyann = self.ox + dx, self.oy + dy + x, y = ann.xyann def finalize_offset(self): - loc_in_canvas = self.annotation.xytext - self.annotation.textcoords = "axes fraction" + loc_in_canvas = self.annotation.xyann + self.annotation.anncoords = "axes fraction" pos_axes_fraction = self.annotation.axes.transAxes.inverted() pos_axes_fraction = pos_axes_fraction.transform_point(loc_in_canvas) - self.annotation.xytext = tuple(pos_axes_fraction) + self.annotation.xyann = tuple(pos_axes_fraction) if __name__ == "__main__": diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 4ec2f5f9462e..ad359f3301f2 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1435,19 +1435,12 @@ def __call__(self, renderer): class _AnnotationBase(object): def __init__(self, - xy, xytext=None, - xycoords='data', textcoords=None, + xy, + xycoords='data', annotation_clip=None): - if xytext is None: - xytext = xy - if textcoords is None: - textcoords = xycoords - # we'll draw ourself after the artist we annotate by default - x, y = self.xytext = xytext self.xy = xy self.xycoords = xycoords - self.textcoords = textcoords self.set_annotation_clip(annotation_clip) self._draggable = None @@ -1655,6 +1648,26 @@ def draggable(self, state=None, use_blit=False): return self._draggable + @property + @cbook.deprecated('1.4', message='Use `anncoords` instead', name='textcoords', alternative='anncoords') + def textcoords(self): + return self.anncoords + + @textcoords.setter + @cbook.deprecated('1.4', message='Use `anncoords` instead', name='textcoords', alternative='anncoords') + def textcoords(self, val): + self.anncoords = val + + @property + @cbook.deprecated('1.4', message='Use `xyann` instead', name='xytext', alternative='xyann') + def xytext(self): + self.xyann + + @xytext.setter + @cbook.deprecated('1.4', message='Use `xyann` instead', name='xytext', alternative='xyann') + def xytext(self, val): + self.xyann = val + class Annotation(Text, _AnnotationBase): """ @@ -1778,11 +1791,20 @@ def __init__(self, s, xy, """ _AnnotationBase.__init__(self, - xy, xytext=xytext, - xycoords=xycoords, textcoords=textcoords, + xy, + xycoords=xycoords, annotation_clip=annotation_clip) - x, y = self.xytext + # clean up textcoords and assign default + if textcoords is None: + textcoords = self.xycoords + self._textcoords = textcoords + + # cleanup xytext defaults + if xytext is None: + xytext = self.xy + x, y = xytext + Text.__init__(self, x, y, s, **kwargs) self.arrowprops = arrowprops @@ -1802,10 +1824,26 @@ def contains(self, event): if self.arrow is not None: in_arrow, _ = self.arrow.contains(event) contains = contains or in_arrow - # self.arrow_patch is currently not checked as this can be a line - JJ + # self.arrow_patch is currently not checked as this can be a line - J return contains, tinfo + @property + def xyann(self): + return self.get_position() + + @xyann.setter + def xyann(self, xytext): + self.set_position(xytext) + + @property + def anncoords(self): + return self._textcoords + + @anncoords.setter + def anncoords(self, coords): + self._textcoords = coords + def set_figure(self, fig): if self.arrow is not None: @@ -1825,18 +1863,14 @@ def _update_position_xytext(self, renderer, xy_pixel): """Update the pixel positions of the annotation text and the arrow patch. """ + # generate transformation, + self.set_transform(self._get_xy_transform(renderer, self.anncoords)) - x, y = self.xytext - self._x, self._y = self._get_xy(renderer, x, y, - self.textcoords) - - x, y = xy_pixel - - ox0, oy0 = self._x, self._y - ox1, oy1 = x, y + ox0, oy0 = self._get_xy_display() + ox1, oy1 = xy_pixel if self.arrowprops: - x0, y0 = x, y + x0, y0 = xy_pixel l, b, w, h = self.get_window_extent(renderer).bounds r = l + w t = b + h diff --git a/lib/mpl_toolkits/axisartist/axis_artist.py b/lib/mpl_toolkits/axisartist/axis_artist.py index 5d543308cde4..409c98d448c6 100644 --- a/lib/mpl_toolkits/axisartist/axis_artist.py +++ b/lib/mpl_toolkits/axisartist/axis_artist.py @@ -1378,7 +1378,7 @@ def _update_offsetText(self): self.offsetText.set_text( self.axis.major.formatter.get_offset() ) self.offsetText.set_size(self.major_ticklabels.get_size()) offset = self.major_ticklabels.get_pad() + self.major_ticklabels.get_size() + 2. - self.offsetText.xytext= (0, offset) + self.offsetText.xyann= (0, offset) def _draw_offsetText(self, renderer): From 38b938573666490de5d09b0fbb43a1a7b84c73b3 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 5 Sep 2013 23:58:04 -0500 Subject: [PATCH 3/4] pep8 fixes --- lib/matplotlib/tests/test_text.py | 93 ++++++++++++++++--------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index 5d5d7b6e2ce1..d2b031bdb16e 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -21,60 +21,60 @@ def find_matplotlib_font(**kw): return FontProperties(fname=path) from matplotlib.font_manager import FontProperties, findfont - warnings.filterwarnings('ignore','findfont: Font family \[\'Foo\'\] '+ \ + warnings.filterwarnings('ignore', 'findfont: Font family \[\'Foo\'\] '+ \ 'not found. Falling back to .', UserWarning, module='matplotlib.font_manager') fig = plt.figure() - ax = plt.subplot( 1, 1, 1 ) - - normalFont = find_matplotlib_font( family = "sans-serif", - style = "normal", - variant = "normal", - size = 14, - ) - ax.annotate( "Normal Font", (0.1, 0.1), xycoords='axes fraction', - fontproperties = normalFont ) - - boldFont = find_matplotlib_font( family = "Foo", - style = "normal", - variant = "normal", - weight = "bold", - stretch = 500, - size = 14, + ax = plt.subplot(1, 1, 1) + + normalFont = find_matplotlib_font(family="sans-serif", + style="normal", + variant="normal", + size=14, + ) + ax.annotate("Normal Font", (0.1, 0.1), xycoords='axes fraction', + fontproperties=normalFont) + + boldFont = find_matplotlib_font(family="Foo", + style="normal", + variant="normal", + weight="bold", + stretch=500, + size=14, ) - ax.annotate( "Bold Font", (0.1, 0.2), xycoords='axes fraction', - fontproperties = boldFont ) - - boldItemFont = find_matplotlib_font( family = "sans serif", - style = "italic", - variant = "normal", - weight = 750, - stretch = 500, - size = 14, + ax.annotate("Bold Font", (0.1, 0.2), xycoords='axes fraction', + fontproperties=boldFont) + + boldItemFont = find_matplotlib_font(family="sans serif", + style="italic", + variant="normal", + weight=750, + stretch=500, + size=14, ) - ax.annotate( "Bold Italic Font", (0.1, 0.3), xycoords='axes fraction', - fontproperties = boldItemFont ) - - lightFont = find_matplotlib_font( family = "sans-serif", - style = "normal", - variant = "normal", - weight = 200, - stretch = 500, - size = 14, + ax.annotate("Bold Italic Font", (0.1, 0.3), xycoords='axes fraction', + fontproperties=boldItemFont) + + lightFont = find_matplotlib_font(family="sans-serif", + style="normal", + variant="normal", + weight=200, + stretch=500, + size=14, ) - ax.annotate( "Light Font", (0.1, 0.4), xycoords='axes fraction', - fontproperties = lightFont ) - - condensedFont = find_matplotlib_font( family = "sans-serif", - style = "normal", - variant = "normal", - weight = 500, - stretch = 100, - size = 14, + ax.annotate("Light Font", (0.1, 0.4), xycoords='axes fraction', + fontproperties=lightFont) + + condensedFont = find_matplotlib_font(family="sans-serif", + style="normal", + variant="normal", + weight=500, + stretch=100, + size=14, ) - ax.annotate( "Condensed Font", (0.1, 0.5), xycoords='axes fraction', - fontproperties = condensedFont ) + ax.annotate("Condensed Font", (0.1, 0.5), xycoords='axes fraction', + fontproperties=condensedFont) ax.set_xticks([]) ax.set_yticks([]) @@ -101,6 +101,7 @@ def test_multiline(): ax.set_xticks([]) ax.set_yticks([]) + @image_comparison(baseline_images=['antialiased'], extensions=['png']) def test_antialiasing(): matplotlib.rcParams['text.antialiased'] = True From f6e3edc1bc574069b6f79e8b40af6ae5e539095d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 7 Sep 2013 09:34:16 -0500 Subject: [PATCH 4/4] added test for Annotation.set_position --- lib/matplotlib/tests/test_text.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index d2b031bdb16e..77af9bb8161d 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -194,3 +194,34 @@ def test_alignment(): ax.set_ylim([0, 1.5]) ax.set_xticks([]) ax.set_yticks([]) + + +@cleanup +def test_set_position(): + fig, ax = plt.subplots() + + # test set_position + ann = ax.annotate('test', (0, 0), xytext=(0, 0), textcoords='figure pixels') + plt.draw() + + init_pos = ann.get_window_extent(fig.canvas.renderer) + shift_val = 15 + ann.set_position((shift_val, shift_val)) + plt.draw() + post_pos = ann.get_window_extent(fig.canvas.renderer) + + for a, b in zip(init_pos.min, post_pos.min): + assert a + shift_val == b + + # test xyann + ann = ax.annotate('test', (0, 0), xytext=(0, 0), textcoords='figure pixels') + plt.draw() + + init_pos = ann.get_window_extent(fig.canvas.renderer) + shift_val = 15 + ann.xyann = (shift_val, shift_val) + plt.draw() + post_pos = ann.get_window_extent(fig.canvas.renderer) + + for a, b in zip(init_pos.min, post_pos.min): + assert a + shift_val == b