diff --git a/lib/matplotlib/backends/backend_agg.py b/lib/matplotlib/backends/backend_agg.py index b937c64fce95..4a62f5a921cd 100644 --- a/lib/matplotlib/backends/backend_agg.py +++ b/lib/matplotlib/backends/backend_agg.py @@ -148,18 +148,65 @@ def draw_path(self, gc, path, transform, rgbFace=None): c = c[ii0:ii1] c[0] = Path.MOVETO # move to end of last chunk p = Path(v, c) + p.simplify_threshold = path.simplify_threshold try: self._renderer.draw_path(gc, p, transform, rgbFace) except OverflowError as err: - raise OverflowError( - "Exceeded cell block limit (set 'agg.path.chunksize' " - "rcparam)") from err + msg = ( + "Exceeded cell block limit in Agg.\n\n" + "Please reduce the value of " + f"rcParams['agg.path.chunksize'] (currently {nmax}) " + "or increase the path simplification threshold" + "(rcParams['path.simplify_threshold'] = " + f"{mpl.rcParams['path.simplify_threshold']:.2f} by " + "default and path.simplify_threshold = " + f"{path.simplify_threshold:.2f} on the input)." + ) + raise OverflowError(msg) from None else: try: self._renderer.draw_path(gc, path, transform, rgbFace) except OverflowError as err: - raise OverflowError("Exceeded cell block limit (set " - "'agg.path.chunksize' rcparam)") from err + cant_chunk = '' + if rgbFace is not None: + cant_chunk += "- can not split filled path\n" + if gc.get_hatch() is not None: + cant_chunk += "- can not split hatched path\n" + if not path.should_simplify: + cant_chunk += "- path.should_simplify is False\n" + if len(cant_chunk): + msg = ( + "Exceeded cell block limit in Agg, however for the " + "following reasons:\n\n" + f"{cant_chunk}\n" + "we can not automatically split up this path to draw." + "\n\nPlease manually simplify your path." + ) + + else: + inc_threshold = ( + "or increase the path simplification threshold" + "(rcParams['path.simplify_threshold'] = " + f"{mpl.rcParams['path.simplify_threshold']} " + "by default and path.simplify_threshold " + f"= {path.simplify_threshold} " + "on the input)." + ) + if nmax > 100: + msg = ( + "Exceeded cell block limit in Agg. Please reduce " + "the value of rcParams['agg.path.chunksize'] " + f"(currently {nmax}) {inc_threshold}" + ) + else: + msg = ( + "Exceeded cell block limit in Agg. Please set " + "the value of rcParams['agg.path.chunksize'], " + f"(currently {nmax}) to be greater than 100 " + + inc_threshold + ) + + raise OverflowError(msg) from None def draw_mathtext(self, gc, x, y, s, prop, angle): """Draw mathtext using :mod:`matplotlib.mathtext`.""" diff --git a/lib/matplotlib/tests/test_agg.py b/lib/matplotlib/tests/test_agg.py index 14573f5941f6..0e4abf86fe02 100644 --- a/lib/matplotlib/tests/test_agg.py +++ b/lib/matplotlib/tests/test_agg.py @@ -8,9 +8,12 @@ from matplotlib import ( collections, path, pyplot as plt, transforms as mtransforms, rcParams) -from matplotlib.image import imread +from matplotlib.backends.backend_agg import RendererAgg from matplotlib.figure import Figure +from matplotlib.image import imread +from matplotlib.path import Path from matplotlib.testing.decorators import image_comparison +from matplotlib.transforms import IdentityTransform def test_repeated_save_with_alpha(): @@ -72,10 +75,10 @@ def test_marker_with_nan(): def test_long_path(): buff = io.BytesIO() - - fig, ax = plt.subplots() - np.random.seed(0) - points = np.random.rand(70000) + fig = Figure() + ax = fig.subplots() + points = np.ones(100_000) + points[::2] *= -1 ax.plot(points) fig.savefig(buff, format='png') @@ -251,3 +254,79 @@ def test_draw_path_collection_error_handling(): ax.scatter([1], [1]).set_paths(path.Path([(0, 1), (2, 3)])) with pytest.raises(TypeError): fig.canvas.draw() + + +@pytest.fixture +def chunk_limit_setup(): + N = 100_000 + dpi = 500 + w = 5*dpi + h = 6*dpi + + # just fit in the width + x = np.linspace(0, w, N) + # and go top-to-bottom + y = np.ones(N) * h + y[::2] = 0 + + idt = IdentityTransform() + # make a renderer + ra = RendererAgg(w, h, dpi) + # setup the minimal gc to draw a line + gc = ra.new_gc() + gc.set_linewidth(1) + gc.set_foreground('r') + # make a Path + p = Path(np.vstack((x, y)).T) + # effectively disable path simplification (but leaving it "on") + p.simplify_threshold = 0 + + return ra, gc, p, idt + + +def test_chunksize_hatch_fail(chunk_limit_setup): + ra, gc, p, idt = chunk_limit_setup + + gc.set_hatch('/') + + with pytest.raises(OverflowError, match='hatched path'): + ra.draw_path(gc, p, idt) + + +def test_chunksize_rgbFace_fail(chunk_limit_setup): + ra, gc, p, idt = chunk_limit_setup + + with pytest.raises(OverflowError, match='filled path'): + ra.draw_path(gc, p, idt, (1, 0, 0)) + + +def test_chunksize_no_simplify_fail(chunk_limit_setup): + ra, gc, p, idt = chunk_limit_setup + p.should_simplify = False + with pytest.raises(OverflowError, match="should_simplify is False"): + ra.draw_path(gc, p, idt) + + +def test_chunksize_zero(chunk_limit_setup): + ra, gc, p, idt = chunk_limit_setup + # set to zero to disable, currently defaults to 0, but lets be sure + rcParams['agg.path.chunksize'] = 0 + with pytest.raises(OverflowError, match='Please set'): + ra.draw_path(gc, p, idt) + + +def test_chunksize_too_big_to_chunk(chunk_limit_setup): + ra, gc, p, idt = chunk_limit_setup + # set big enough that we do not try to chunk + rcParams['agg.path.chunksize'] = 1_000_000 + with pytest.raises(OverflowError, match='Please reduce'): + ra.draw_path(gc, p, idt) + + +def test_chunksize_toobig_chunks(chunk_limit_setup): + ra, gc, p, idt = chunk_limit_setup + # small enough we will try to chunk, but big enough we will fail + # to render + rcParams['agg.path.chunksize'] = 90_000 + with pytest.raises(OverflowError, match='Please reduce'): + ra.draw_path(gc, p, idt) diff --git a/lib/matplotlib/tests/test_simplification.py b/lib/matplotlib/tests/test_simplification.py index 952e890ce660..0749d0f3a115 100644 --- a/lib/matplotlib/tests/test_simplification.py +++ b/lib/matplotlib/tests/test_simplification.py @@ -305,8 +305,8 @@ def test_start_with_moveto(): def test_throw_rendering_complexity_exceeded(): plt.rcParams['path.simplify'] = False - xx = np.arange(200000) - yy = np.random.rand(200000) + xx = np.arange(2_000_000) + yy = np.random.rand(2_000_000) yy[1000] = np.nan fig, ax = plt.subplots() diff --git a/src/_backend_agg.cpp b/src/_backend_agg.cpp index 0a9e7ab7e160..79575697a08b 100644 --- a/src/_backend_agg.cpp +++ b/src/_backend_agg.cpp @@ -45,7 +45,7 @@ RendererAgg::RendererAgg(unsigned int width, unsigned int height, double dpi) rendererBase(), rendererAA(), rendererBin(), - theRasterizer(8192), + theRasterizer(32768), lastclippath(NULL), _fill_color(agg::rgba(1, 1, 1, 0)) {
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: