diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index b60227128c17..ce492e7a4bb7 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -207,8 +207,6 @@ def get_text_width_height_descent(self, s, prop, ismath): # outside the backend """ if rcParams['text.usetex']: - # todo: handle props - size = prop.get_size_in_points() texmanager = self.get_texmanager() fontsize = prop.get_size_in_points() w, h, d = texmanager.get_text_width_height_descent(s, fontsize, @@ -248,6 +246,9 @@ def draw_tex(self, gc, x, y, s, prop, angle, ismath='TeX!', mtext=None): x = np.round(x + xd) y = np.round(y + yd) + if s == '': + return + self._renderer.draw_text_image(Z, x, y, angle, gc) def get_canvas_width_height(self): diff --git a/lib/matplotlib/bezier.py b/lib/matplotlib/bezier.py index 034aa1d45cd1..2975c6cb5e01 100644 --- a/lib/matplotlib/bezier.py +++ b/lib/matplotlib/bezier.py @@ -243,7 +243,10 @@ def split_path_inout(path, inside, tolerence=0.01, reorder_inout=False): path_iter = path.iter_segments() - ctl_points, command = next(path_iter) + try: + ctl_points, command = next(path_iter) + except StopIteration: + return path, Path(np.zeros((0, 2))) begin_inside = inside(ctl_points[-2:]) # true if begin point is inside bezier_path = None @@ -280,17 +283,14 @@ def split_path_inout(path, inside, tolerence=0.01, reorder_inout=False): codes_left = [Path.CURVE4, Path.CURVE4, Path.CURVE4] codes_right = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4] else: - raise ValueError() + raise ValueError('Unexpected segment length found in path codes.') verts_left = left[1:] verts_right = right[:] - #i += 1 - if path.codes is None: path_in = Path(concat([path.vertices[:i], verts_left])) path_out = Path(concat([verts_right, path.vertices[i:]])) - else: path_in = Path(concat([path.vertices[:iold], verts_left]), concat([path.codes[:iold], codes_left])) @@ -476,7 +476,7 @@ def make_path_regular(p): if c is None: c = np.empty(p.vertices.shape[:1], "i") c.fill(Path.LINETO) - c[0] = Path.MOVETO + c[0:1] = Path.MOVETO return Path(p.vertices, c) else: diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index 796280bbc480..ee649095d5f5 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -2536,6 +2536,9 @@ def _shrink(self, path, shrinkA, shrinkB): """ Shrink the path by fixed size (in points) with shrinkA and shrinkB """ + if path.vertices.size <= 1: + return path + if shrinkA: x, y = path.vertices[0] insideA = inside_circle(x, y, shrinkA) @@ -3119,6 +3122,8 @@ def _get_arrow_wedge(self, x0, y0, x1, y1, return vertices_arrow, codes_arrow, ddx, ddy def transmute(self, path, mutation_size, linewidth): + if path.vertices.size < 2: + return [path], [False] head_length, head_width = self.head_length * mutation_size, \ self.head_width * mutation_size diff --git a/lib/matplotlib/tests/test_animation.py b/lib/matplotlib/tests/test_animation.py index ba25fc945b33..e65ab34d0c00 100644 --- a/lib/matplotlib/tests/test_animation.py +++ b/lib/matplotlib/tests/test_animation.py @@ -10,8 +10,7 @@ from matplotlib import pyplot as plt from matplotlib import animation from matplotlib.testing.noseclasses import KnownFailureTest -from matplotlib.testing.decorators import cleanup -from matplotlib.testing.decorators import CleanupTest +from matplotlib.testing.decorators import CleanupTest, cleanup WRITER_OUTPUT = dict(ffmpeg='mp4', ffmpeg_file='mp4', diff --git a/lib/matplotlib/tests/test_arrow_patches.py b/lib/matplotlib/tests/test_arrow_patches.py index 4a6c4a94e0dd..96b1f8ab2ac3 100644 --- a/lib/matplotlib/tests/test_arrow_patches.py +++ b/lib/matplotlib/tests/test_arrow_patches.py @@ -4,7 +4,7 @@ import six import matplotlib.pyplot as plt -from matplotlib.testing.decorators import image_comparison +from matplotlib.testing.decorators import image_comparison, cleanup import matplotlib @@ -30,6 +30,18 @@ def test_fancyarrow(): ax.tick_params(labelleft=False, labelbottom=False) +@cleanup +def test_arrow_usetex(): + # Passing an empty string to latex triggered an exception, so we ensure + # that null strings are intercepted and drawn appropriately (i.e. don't + # raise an exception). + matplotlib.rcParams['text.usetex'] = True + props = dict(boxstyle='round', edgecolor='red', + facecolor='yellow', alpha=0.5) + plt.annotate(' ', (0, 0), (1, 0), arrowprops=dict(arrowstyle='->'), bbox=props) + plt.draw() + + if __name__ == '__main__': import nose nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_text.py b/lib/matplotlib/tests/test_text.py index e347e6e05ad1..cfec610a6dda 100644 --- a/lib/matplotlib/tests/test_text.py +++ b/lib/matplotlib/tests/test_text.py @@ -3,12 +3,17 @@ import six +import warnings + +import mock import numpy as np + import matplotlib -from matplotlib.testing.decorators import image_comparison, knownfailureif, cleanup +from matplotlib.testing.decorators import image_comparison, cleanup +import matplotlib.text as mtext import matplotlib.pyplot as plt -import warnings -from nose.tools import with_setup +from matplotlib.backend_bases import RendererBase +from matplotlib.font_manager import FontProperties, findfont @image_comparison(baseline_images=['font_styles']) @@ -21,12 +26,6 @@ def find_matplotlib_font(**kw): path = findfont(prop, directory=data_path) return FontProperties(fname=path) - from matplotlib.font_manager import FontProperties, findfont - 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", @@ -37,13 +36,13 @@ def find_matplotlib_font(**kw): 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, - ) + with mock.patch('warnings.warn') as warn: + boldFont = find_matplotlib_font(family="Foo", style="normal", + variant="normal", weight="bold", + stretch=500, size=14) + assert warn.call_args[0][0].startswith("findfont: Font family [u'Foo'] " + "not found. Falling back to") + ax.annotate("Bold Font", (0.1, 0.2), xycoords='axes fraction', fontproperties=boldFont) @@ -83,7 +82,6 @@ def find_matplotlib_font(**kw): @image_comparison(baseline_images=['multiline']) def test_multiline(): - fig = plt.figure() ax = plt.subplot(1, 1, 1) ax.set_title("multiline\ntext alignment") @@ -226,3 +224,28 @@ def test_set_position(): for a, b in zip(init_pos.min, post_pos.min): assert a + shift_val == b + + +def test_empty_string_with_bbox(): + # Check that an empty string Text instance can still draw a bbox + # (by calling the _draw_bbox method). + props = dict(boxstyle='round', edgecolor='red', + facecolor='yellow', alpha=0.5) + + renderer = RendererBase() + renderer.draw_text = mock.Mock() + fig = plt.figure() + + t = mtext.Text(0, 1, '', bbox=props) + t.set_figure(fig) + t._draw_bbox = mock.Mock() + + # Do the actual call. + t.draw(renderer) + t._draw_bbox.assert_called_once_with(renderer, 0.0, 1.0) + assert renderer.draw_text.call_count == 1 + + +if __name__ == "__main__": + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/tests/test_tightlayout.py b/lib/matplotlib/tests/test_tightlayout.py index 14e1528faeba..db44c35d02f6 100644 --- a/lib/matplotlib/tests/test_tightlayout.py +++ b/lib/matplotlib/tests/test_tightlayout.py @@ -5,17 +5,18 @@ import numpy as np -from matplotlib.testing.decorators import image_comparison, knownfailureif +from matplotlib.testing.decorators import image_comparison import matplotlib.pyplot as plt -from nose.tools import assert_raises -from numpy.testing import assert_array_equal +import matplotlib.gridspec as gridspec + def example_plot(ax, fontsize=12): - ax.plot([1, 2]) - ax.locator_params(nbins=3) - ax.set_xlabel('x-label', fontsize=fontsize) - ax.set_ylabel('y-label', fontsize=fontsize) - ax.set_title('Title', fontsize=fontsize) + ax.plot([1, 2]) + ax.locator_params(nbins=3) + ax.set_xlabel('x-label', fontsize=fontsize) + ax.set_ylabel('y-label', fontsize=fontsize) + ax.set_title('Title', fontsize=fontsize) + @image_comparison(baseline_images=['tight_layout1']) def test_tight_layout1(): @@ -39,10 +40,7 @@ def test_tight_layout2(): @image_comparison(baseline_images=['tight_layout3']) def test_tight_layout3(): - 'Test tight_layout for mutiple subplots' - - fig = plt.figure() - + # Test tight_layout for mutiple subplots ax1 = plt.subplot(221) ax2 = plt.subplot(223) ax3 = plt.subplot(122) @@ -57,10 +55,7 @@ def test_tight_layout3(): @image_comparison(baseline_images=['tight_layout4'], freetype_version=('2.4.5', '2.4.9')) def test_tight_layout4(): - 'Test tight_layout for subplot2grid' - - fig = plt.figure() - + # Test tight_layout for subplot2grid ax1 = plt.subplot2grid((3, 3), (0, 0)) ax2 = plt.subplot2grid((3, 3), (0, 1), colspan=2) ax3 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=2) @@ -76,10 +71,7 @@ def test_tight_layout4(): @image_comparison(baseline_images=['tight_layout5']) def test_tight_layout5(): - 'Test tight_layout for image' - - fig = plt.figure() - + # Test tight_layout for image ax = plt.subplot(111) arr = np.arange(100).reshape((10,10)) ax.imshow(arr, interpolation="none") @@ -87,15 +79,11 @@ def test_tight_layout5(): plt.tight_layout() - @image_comparison(baseline_images=['tight_layout6']) def test_tight_layout6(): - 'Test tight_layout for gridspec' - + # Test tight_layout for gridspec fig = plt.figure() - import matplotlib.gridspec as gridspec - gs1 = gridspec.GridSpec(2, 1) ax1 = fig.add_subplot(gs1[0]) ax2 = fig.add_subplot(gs1[1]) @@ -130,9 +118,8 @@ def test_tight_layout6(): @image_comparison(baseline_images=['tight_layout7']) def test_tight_layout7(): # tight layout with left and right titles - fig = plt.figure() fontsize = 24 - ax = fig.add_subplot(111) + ax = plt.subplot(111) ax.plot([1, 2]) ax.locator_params(nbins=3) ax.set_xlabel('x-label', fontsize=fontsize) @@ -141,10 +128,16 @@ def test_tight_layout7(): ax.set_title('Right Title', loc='right', fontsize=fontsize) plt.tight_layout() + @image_comparison(baseline_images=['tight_layout8']) def test_tight_layout8(): - 'Test automatic use of tight_layout' + # Test automatic use of tight_layout fig = plt.figure() fig.set_tight_layout({'pad': .1}) ax = fig.add_subplot(111) example_plot(ax, fontsize=24) + + +if __name__ == "__main__": + import nose + nose.runmodule(argv=['-s', '--with-doctest'], exit=False) diff --git a/lib/matplotlib/texmanager.py b/lib/matplotlib/texmanager.py index 51758fe6851e..a91b3940be0a 100644 --- a/lib/matplotlib/texmanager.py +++ b/lib/matplotlib/texmanager.py @@ -223,8 +223,9 @@ def get_basefile(self, tex, fontsize, dpi=None): s = ''.join([tex, self.get_font_config(), '%f' % fontsize, self.get_custom_preamble(), str(dpi or '')]) # make sure hash is consistent for all strings, regardless of encoding: - bytes = six.text_type(s).encode('utf-8') - return os.path.join(self.texcache, md5(bytes).hexdigest()) + checksum = md5(six.text_type(s).encode('utf-8')).hexdigest() + fname = os.path.join(self.texcache, checksum) + return fname def get_font_config(self): """Reinitializes self if relevant rcParams on have changed.""" @@ -282,6 +283,12 @@ def make_tex(self, tex, fontsize): fontcmd = {'sans-serif': r'{\sffamily %s}', 'monospace': r'{\ttfamily %s}'}.get(self.font_family, r'{\rmfamily %s}') + # Leading and trailing whitespace is ignored anyway (the + # dvipng -T tight call deeper down takes care of that) so convert + # an empty string into a single space character. + if tex.strip() == '': + tex = '\\ ' + tex = fontcmd % tex if rcParams['text.latex.unicode']: @@ -333,6 +340,11 @@ def make_tex_preview(self, tex, fontsize): fontcmd = {'sans-serif': r'{\sffamily %s}', 'monospace': r'{\ttfamily %s}'}.get(self.font_family, r'{\rmfamily %s}') + # Leading and trailing whitespace is ignored anyway (the + # dvipng -T tight call deeper down takes care of that) so convert + # an empty string into a single space character. + if tex.strip() == '': + tex = '\\ ' tex = fontcmd % tex if rcParams['text.latex.unicode']: @@ -434,7 +446,7 @@ def make_dvi_preview(self, tex, fontsize): """ generates a dvi file containing latex's layout of tex string. It calls make_tex_preview() method and store the size - information (width, height, descent) in a separte file. + information (width, height, descent) in a separate file. returns the file name """ @@ -644,9 +656,6 @@ def get_text_width_height_descent(self, tex, fontsize, renderer=None): """ return width, heigth and descent of the text. """ - if tex.strip() == '': - return 0, 0, 0 - if renderer: dpi_fraction = renderer.points_to_pixels(1.) else: diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index ccdf93f36015..9b0c46ad7b3c 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -529,8 +529,6 @@ def draw(self, renderer): self._renderer = renderer if not self.get_visible(): return - if self.get_text().strip() == '': - return renderer.open_group('text', self.get_gid()) @@ -728,6 +726,7 @@ def get_window_extent(self, renderer=None, dpi=None): if dpi is not None: dpi_orig = self.figure.dpi self.figure.dpi = dpi + if self.get_text().strip() == '': tx, ty = self._get_xy_display() return Bbox.from_bounds(tx, ty, 0, 0) @@ -1863,10 +1862,7 @@ def _update_position_xytext(self, renderer, xy_pixel): # Use FancyArrowPatch if self.arrowprops has "arrowstyle" key. # Otherwise, fallback to YAArrow. - - #if d.has_key("arrowstyle"): if self.arrow_patch: - # adjust the starting point of the arrow relative to # the textbox. # TODO : Rotation needs to be accounted. @@ -1880,7 +1876,6 @@ def _update_position_xytext(self, renderer, xy_pixel): # Then it will be shrinked by shirnkA and shrinkB # (in points). If patch A is not set, self.bbox_patch # is used. - self.arrow_patch.set_positions((ox0, oy0), (ox1, oy1)) mutation_scale = d.pop("mutation_scale", self.get_size()) mutation_scale = renderer.points_to_pixels(mutation_scale) @@ -1899,6 +1894,7 @@ def _update_position_xytext(self, renderer, xy_pixel): props = props.copy() pad = props.pop('pad', 4) pad = renderer.points_to_pixels(pad) + if self.get_text().strip() == "": self.arrow_patch.set_patchA(None) return @@ -1918,9 +1914,7 @@ def _update_position_xytext(self, renderer, xy_pixel): r.update(props) self.arrow_patch.set_patchA(r) - else: - # pick the x,y corner of the text bbox closest to point # annotated dsu = [(abs(val - x0), val) for val in (l, r, xc)]
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: