diff --git a/doc/users/next_whats_new/stackplot_hatch.rst b/doc/users/next_whats_new/stackplot_hatch.rst new file mode 100644 index 000000000000..8fd4ca0f81b0 --- /dev/null +++ b/doc/users/next_whats_new/stackplot_hatch.rst @@ -0,0 +1,27 @@ +``hatch`` parameter for stackplot +------------------------------------------- + +The `~.Axes.stackplot` *hatch* parameter now accepts a list of strings describing hatching styles that will be applied sequentially to the layers in the stack: + +.. plot:: + :include-source: true + :alt: Two charts, identified as ax1 and ax2, showing "stackplots", i.e. one-dimensional distributions of data stacked on top of one another. The first plot, ax1 has cross-hatching on all slices, having been given a single string as the "hatch" argument. The second plot, ax2 has different styles of hatching on each slice - diagonal hatching in opposite directions on the first two slices, cross-hatching on the third slice, and open circles on the fourth. + + import matplotlib.pyplot as plt + fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(10,5)) + + cols = 10 + rows = 4 + data = ( + np.reshape(np.arange(0, cols, 1), (1, -1)) ** 2 + + np.reshape(np.arange(0, rows), (-1, 1)) + + np.random.random((rows, cols))*5 + ) + x = range(data.shape[1]) + ax1.stackplot(x, data, hatch="x") + ax2.stackplot(x, data, hatch=["//","\\","x","o"]) + + ax1.set_title("hatch='x'") + ax2.set_title("hatch=['//','\\\\','x','o']") + + plt.show() diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 7f4aa12c9ed6..05b0d1b8917b 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -3792,12 +3792,15 @@ def spy( # Autogenerated by boilerplate.py. Do not edit as changes will be lost. @_copy_docstring_and_deprecators(Axes.stackplot) -def stackplot(x, *args, labels=(), colors=None, baseline="zero", data=None, **kwargs): +def stackplot( + x, *args, labels=(), colors=None, hatch=None, baseline="zero", data=None, **kwargs +): return gca().stackplot( x, *args, labels=labels, colors=colors, + hatch=hatch, baseline=baseline, **({"data": data} if data is not None else {}), **kwargs, diff --git a/lib/matplotlib/stackplot.py b/lib/matplotlib/stackplot.py index 2629593683e5..4e933f52887d 100644 --- a/lib/matplotlib/stackplot.py +++ b/lib/matplotlib/stackplot.py @@ -16,7 +16,7 @@ def stackplot(axes, x, *args, - labels=(), colors=None, baseline='zero', + labels=(), colors=None, hatch=None, baseline='zero', **kwargs): """ Draw a stacked area plot. @@ -55,6 +55,15 @@ def stackplot(axes, x, *args, If not specified, the colors from the Axes property cycle will be used. + hatch : list of str, default: None + A sequence of hatching styles. See + :doc:`/gallery/shapes_and_collections/hatch_style_reference`. + The sequence will be cycled through for filling the + stacked areas from bottom to top. + It need not be exactly the same length as the number + of provided *y*, in which case the styles will repeat from the + beginning. + data : indexable object, optional DATA_PARAMETER_PLACEHOLDER @@ -76,6 +85,11 @@ def stackplot(axes, x, *args, else: colors = (axes._get_lines.get_next_color() for _ in y) + if hatch is None or isinstance(hatch, str): + hatch = itertools.cycle([hatch]) + else: + hatch = itertools.cycle(hatch) + # Assume data passed has not been 'stacked', so stack it here. # We'll need a float buffer for the upcoming calculations. stack = np.cumsum(y, axis=0, dtype=np.promote_types(y.dtype, np.float32)) @@ -113,7 +127,9 @@ def stackplot(axes, x, *args, # Color between x = 0 and the first array. coll = axes.fill_between(x, first_line, stack[0, :], - facecolor=next(colors), label=next(labels, None), + facecolor=next(colors), + hatch=next(hatch), + label=next(labels, None), **kwargs) coll.sticky_edges.y[:] = [0] r = [coll] @@ -122,6 +138,7 @@ def stackplot(axes, x, *args, for i in range(len(y) - 1): r.append(axes.fill_between(x, stack[i, :], stack[i + 1, :], facecolor=next(colors), + hatch=next(hatch), label=next(labels, None), **kwargs)) return r diff --git a/lib/matplotlib/stackplot.pyi b/lib/matplotlib/stackplot.pyi index 503e282665d6..2981d449b566 100644 --- a/lib/matplotlib/stackplot.pyi +++ b/lib/matplotlib/stackplot.pyi @@ -12,6 +12,7 @@ def stackplot( *args: ArrayLike, labels: Iterable[str] = ..., colors: Iterable[ColorType] | None = ..., + hatch: Iterable[str] | str | None = ..., baseline: Literal["zero", "sym", "wiggle", "weighted_wiggle"] = ..., **kwargs ) -> list[PolyCollection]: ... diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index c82c79272d87..799bf5ddd774 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -3084,6 +3084,27 @@ def layers(n, m): axs[1, 1].stackplot(range(100), d.T, baseline='weighted_wiggle') +@check_figures_equal() +def test_stackplot_hatching(fig_ref, fig_test): + x = np.linspace(0, 10, 10) + y1 = 1.0 * x + y2 = 2.0 * x + 1 + y3 = 3.0 * x + 2 + # stackplot with different hatching styles (issue #27146) + ax_test = fig_test.subplots() + ax_test.stackplot(x, y1, y2, y3, hatch=["x", "//", "\\\\"], colors=["white"]) + ax_test.set_xlim((0, 10)) + ax_test.set_ylim((0, 70)) + # compare with result from hatching each layer individually + stack_baseline = np.zeros(len(x)) + ax_ref = fig_ref.subplots() + ax_ref.fill_between(x, stack_baseline, y1, hatch="x", facecolor="white") + ax_ref.fill_between(x, y1, y1+y2, hatch="//", facecolor="white") + ax_ref.fill_between(x, y1+y2, y1+y2+y3, hatch="\\\\", facecolor="white") + ax_ref.set_xlim((0, 10)) + ax_ref.set_ylim((0, 70)) + + def _bxp_test_helper( stats_kwargs={}, transform_stats=lambda s: s, bxp_kwargs={}): np.random.seed(937)