From 1a6ec0f477892b13aa08e915a9d478e841d55c16 Mon Sep 17 00:00:00 2001 From: Pranav Date: Sat, 13 Apr 2024 23:20:29 +0530 Subject: [PATCH 01/13] Add support for multiple hatches, edgecolor and linewidth in histograms --- lib/matplotlib/axes/_axes.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 52c99b125d36..65441b7a1ef4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -7210,9 +7210,34 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, # If None, make all labels None (via zip_longest below); otherwise, # cast each element to str, but keep a single str as it. labels = [] if label is None else np.atleast_1d(np.asarray(label, str)) + + if 'hatch' in kwargs: + if not isinstance(kwargs['hatch'], str): + hatches = itertools.cycle(kwargs['hatch']) + else: + hatches = itertools.cycle([kwargs['hatch']]) + + if 'edgecolor' in kwargs: + if not isinstance(kwargs['edgecolor'], str): + edgecolors = itertools.cycle(kwargs['edgecolor']) + else: + edgecolors = itertools.cycle([kwargs['edgecolor']]) + + if 'linewidth' in kwargs: + if isinstance(kwargs['linewidth'], list or tuple): + linewidths = itertools.cycle(kwargs['linewidth']) + else: + linewidths = itertools.cycle([kwargs['linewidth']]) + for patch, lbl in itertools.zip_longest(patches, labels): if patch: p = patch[0] + if 'hatch' in kwargs: + kwargs['hatch'] = next(hatches) + if 'edgecolor' in kwargs: + kwargs['edgecolor'] = next(edgecolors) + if 'linewidth' in kwargs: + kwargs['linewidth'] = next(linewidths) p._internal_update(kwargs) if lbl is not None: p.set_label(lbl) From 0e52daad459d0f3b1f1238016533ebf4583a3438 Mon Sep 17 00:00:00 2001 From: Pranav Date: Sun, 14 Apr 2024 11:13:46 +0530 Subject: [PATCH 02/13] Add hatch, linewidth and edgecolor parameters to multihist example --- .../examples/statistics/histogram_multihist.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/galleries/examples/statistics/histogram_multihist.py b/galleries/examples/statistics/histogram_multihist.py index f1957dc38939..6d2a2d1fc55a 100644 --- a/galleries/examples/statistics/histogram_multihist.py +++ b/galleries/examples/statistics/histogram_multihist.py @@ -27,19 +27,24 @@ fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(nrows=2, ncols=2) colors = ['red', 'tan', 'lime'] -ax0.hist(x, n_bins, density=True, histtype='bar', color=colors, label=colors) +ax0.hist(x, n_bins, density=True, histtype='bar', color=colors, + label=colors, hatch=['o', '*', '.'], + edgecolor=['black', 'red', 'blue'], linewidth=[1, 2, 3]) ax0.legend(prop={'size': 10}) ax0.set_title('bars with legend') -ax1.hist(x, n_bins, density=True, histtype='bar', stacked=True) +ax1.hist( + x, n_bins, density=True, histtype="bar", stacked=True, + edgecolor=["black", "yellow", "blue"]) ax1.set_title('stacked bar') -ax2.hist(x, n_bins, histtype='step', stacked=True, fill=False) +ax2.hist(x, n_bins, histtype='step', stacked=True, fill=False, + linewidth=[1, 2, 3]) ax2.set_title('stack step (unfilled)') # Make a multiple-histogram of data-sets with different length. x_multi = [np.random.randn(n) for n in [10000, 5000, 2000]] -ax3.hist(x_multi, n_bins, histtype='bar') +ax3.hist(x_multi, n_bins, histtype='bar', hatch=['\\', '/', '-']) ax3.set_title('different sample sizes') fig.tight_layout() From 34df06a477c7bf05517b92d886d676a35ac33fe2 Mon Sep 17 00:00:00 2001 From: Pranav Date: Thu, 25 Apr 2024 18:23:12 +0530 Subject: [PATCH 03/13] Add test for added parameters Specify extensions for test Added modified baseline images Modified test for histogram with single parameters Fixed test Add modified baseline images Made changes concise according to suggestion Made a more detailed gallery example Fix Docs Added whats new note, documentation for vectorization, doc fix Added new test, and reverted changes in old test Added baseline images Modified test to pass codecov, added plot in whats new entry Fix test Added baseline images Altered whats new entry, docs and gallery example Resolved edgecolor and facecolor setting Minor fix Fix docs Modified files to include facecolor and added test Removed figsize from test Add multiple baseline image names Fixed test? Fixed test? Removed parametrize usage Add baseline images Add baseline image Fix docs Fix docs Deleted baseline images, changed test Fix test Fix test Handled None array passing to color Handled passing None list Modified nested patch condition Minor Fix Grammar nits Modified test, edited None handling in sequence so that it errors out --- .../histogram_vectorized_parameters.rst | 46 ++++++++ .../statistics/histogram_multihist.py | 102 ++++++++++++++++-- lib/matplotlib/axes/_axes.py | 56 +++++----- lib/matplotlib/tests/test_axes.py | 33 ++++++ 4 files changed, 197 insertions(+), 40 deletions(-) create mode 100644 doc/users/next_whats_new/histogram_vectorized_parameters.rst diff --git a/doc/users/next_whats_new/histogram_vectorized_parameters.rst b/doc/users/next_whats_new/histogram_vectorized_parameters.rst new file mode 100644 index 000000000000..4f063c14651d --- /dev/null +++ b/doc/users/next_whats_new/histogram_vectorized_parameters.rst @@ -0,0 +1,46 @@ +Vectorize ``hatch``, ``edgecolor``, ``facecolor``, ``linewidth`` and ``linestyle`` in *hist* methods +---------------------------------------------------------------------------------------------------- + +The parameters ``hatch``, ``edgecolor``, ``facecolor``, ``linewidth`` and ``linestyle`` +of the `~matplotlib.axes.Axes.hist` method are now vectorized. +This means that you can pass in unique parameters for each histogram that is generated +when the input *x* has multiple datasets. + + +.. plot:: + :include-source: true + :alt: Four charts, each displaying stacked histograms of three Poisson distributions. Each chart differentiates the histograms using various parameters: ax1 uses different linewidths, ax2 uses different hatches, ax3 uses different edgecolors, and ax4 uses different facecolors. Each histogram in ax1 and ax3 also has a different edgecolor. + + import matplotlib.pyplot as plt + import numpy as np + np.random.seed(19680801) + + fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(9, 9)) + + data1 = np.random.poisson(5, 1000) + data2 = np.random.poisson(7, 1000) + data3 = np.random.poisson(10, 1000) + + labels = ["Data 1", "Data 2", "Data 3"] + + ax1.hist([data1, data2, data3], bins=range(17), histtype="step", stacked=True, + edgecolor=["red", "green", "blue"], linewidth=[1, 2, 3]) + ax1.set_title("Different linewidths") + ax1.legend(labels) + + ax2.hist([data1, data2, data3], bins=range(17), histtype="barstacked", + hatch=["/", ".", "*"]) + ax2.set_title("Different hatch patterns") + ax2.legend(labels) + + ax3.hist([data1, data2, data3], bins=range(17), histtype="bar", fill=False, + edgecolor=["red", "green", "blue"], linestyle=["--", "-.", ":"]) + ax3.set_title("Different linestyles") + ax3.legend(labels) + + ax4.hist([data1, data2, data3], bins=range(17), histtype="barstacked", + facecolor=["red", "green", "blue"]) + ax4.set_title("Different facecolors") + ax4.legend(labels) + + plt.show() diff --git a/galleries/examples/statistics/histogram_multihist.py b/galleries/examples/statistics/histogram_multihist.py index 6d2a2d1fc55a..0f12b34855d8 100644 --- a/galleries/examples/statistics/histogram_multihist.py +++ b/galleries/examples/statistics/histogram_multihist.py @@ -15,7 +15,7 @@ select these parameters: http://docs.astropy.org/en/stable/visualization/histogram.html """ - +# %% import matplotlib.pyplot as plt import numpy as np @@ -27,29 +27,111 @@ fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(nrows=2, ncols=2) colors = ['red', 'tan', 'lime'] -ax0.hist(x, n_bins, density=True, histtype='bar', color=colors, - label=colors, hatch=['o', '*', '.'], - edgecolor=['black', 'red', 'blue'], linewidth=[1, 2, 3]) +ax0.hist(x, n_bins, density=True, histtype='bar', color=colors, label=colors) ax0.legend(prop={'size': 10}) ax0.set_title('bars with legend') -ax1.hist( - x, n_bins, density=True, histtype="bar", stacked=True, - edgecolor=["black", "yellow", "blue"]) +ax1.hist(x, n_bins, density=True, histtype='bar', stacked=True) ax1.set_title('stacked bar') -ax2.hist(x, n_bins, histtype='step', stacked=True, fill=False, - linewidth=[1, 2, 3]) +ax2.hist(x, n_bins, histtype='step', stacked=True, fill=False) ax2.set_title('stack step (unfilled)') # Make a multiple-histogram of data-sets with different length. x_multi = [np.random.randn(n) for n in [10000, 5000, 2000]] -ax3.hist(x_multi, n_bins, histtype='bar', hatch=['\\', '/', '-']) +ax3.hist(x_multi, n_bins, histtype='bar') ax3.set_title('different sample sizes') fig.tight_layout() plt.show() +# %% +# ----------------------------------- +# Setting properties for each dataset +# ----------------------------------- +# +# Plotting bar charts with datasets differentiated using: +# +# * edgecolors +# * facecolors +# * hatches +# * linewidths +# * linestyles +# +# +# Histograms with Edge-Colors +# ........................... + +fig, ax = plt.subplots() + +edgecolors = ['green', 'red', 'blue'] + +ax.hist(x, n_bins, fill=False, histtype="step", stacked=True, + edgecolor=edgecolors, label=edgecolors) +ax.legend() +ax.set_title('Stacked Steps with Edgecolors') + +plt.show() + +# %% +# Face colors +# ........................... + +fig, ax = plt.subplots() + +facecolors = ['green', 'red', 'blue'] + +ax.hist(x, n_bins, histtype="barstacked", facecolor=facecolors, label=facecolors) +ax.legend() +ax.set_title("Bars with different Facecolors") + +plt.show() + +# %% +# Hatches +# ....................... + +fig, ax = plt.subplots() + +hatches = [".", "o", "x"] + +ax.hist(x, n_bins, histtype="barstacked", hatch=hatches, label=hatches) +ax.legend() +ax.set_title("Hatches on Stacked Bars") + +plt.show() + +# %% +# Linewidths +# .......................... + +fig, ax = plt.subplots() + +linewidths = [1, 2, 3] +edgecolors = ["green", "red", "blue"] + +ax.hist(x, n_bins, fill=False, histtype="bar", linewidth=linewidths, + edgecolor=edgecolors, label=linewidths) +ax.legend() +ax.set_title("Bars with Linewidths") + +plt.show() + +# %% +# LineStyles +# .......................... + +fig, ax = plt.subplots() + +linestyles = ['-', ':', '--'] + +ax.hist(x, n_bins, fill=False, histtype='bar', linestyle=linestyles, + edgecolor=edgecolors, label=linestyles) +ax.legend() +ax.set_title('Bars with Linestyles') + +plt.show() + # %% # # .. admonition:: References diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 65441b7a1ef4..f936dba5580c 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6937,7 +6937,10 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, DATA_PARAMETER_PLACEHOLDER **kwargs - `~matplotlib.patches.Patch` properties + `~matplotlib.patches.Patch` properties. The following properties + additionally accept a sequence of values corresponding to the + datasets in *x*: + *edgecolors*, *facecolors*, *linewidths*, *linestyles*, *hatches*. See Also -------- @@ -7211,39 +7214,32 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, # cast each element to str, but keep a single str as it. labels = [] if label is None else np.atleast_1d(np.asarray(label, str)) - if 'hatch' in kwargs: - if not isinstance(kwargs['hatch'], str): - hatches = itertools.cycle(kwargs['hatch']) - else: - hatches = itertools.cycle([kwargs['hatch']]) - - if 'edgecolor' in kwargs: - if not isinstance(kwargs['edgecolor'], str): - edgecolors = itertools.cycle(kwargs['edgecolor']) - else: - edgecolors = itertools.cycle([kwargs['edgecolor']]) + if histtype == "step": + edgecolors = itertools.cycle(np.atleast_1d(kwargs.get('edgecolor', + colors))) + else: + edgecolors = itertools.cycle(np.atleast_1d(kwargs.get("edgecolor", None))) - if 'linewidth' in kwargs: - if isinstance(kwargs['linewidth'], list or tuple): - linewidths = itertools.cycle(kwargs['linewidth']) - else: - linewidths = itertools.cycle([kwargs['linewidth']]) + facecolors = itertools.cycle(np.atleast_1d(kwargs.get('facecolor', colors))) + hatches = itertools.cycle(np.atleast_1d(kwargs.get('hatch', None))) + linewidths = itertools.cycle(np.atleast_1d(kwargs.get('linewidth', None))) + linestyles = itertools.cycle(np.atleast_1d(kwargs.get('linestyle', None))) for patch, lbl in itertools.zip_longest(patches, labels): - if patch: - p = patch[0] - if 'hatch' in kwargs: - kwargs['hatch'] = next(hatches) - if 'edgecolor' in kwargs: - kwargs['edgecolor'] = next(edgecolors) - if 'linewidth' in kwargs: - kwargs['linewidth'] = next(linewidths) + p = patch[0] + kwargs.update({ + 'hatch': next(hatches), + 'linewidth': next(linewidths), + 'linestyle': next(linestyles), + 'edgecolor': next(edgecolors), + 'facecolor': next(facecolors), + }) + p._internal_update(kwargs) + if lbl is not None: + p.set_label(lbl) + for p in patch[1:]: p._internal_update(kwargs) - if lbl is not None: - p.set_label(lbl) - for p in patch[1:]: - p._internal_update(kwargs) - p.set_label('_nolegend_') + p.set_label('_nolegend_') if nx == 1: return tops[0], bins, patches[0] diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index dd37d3d8ee80..9119c1050e3f 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4603,6 +4603,39 @@ def test_hist_stacked_bar(): ax.legend(loc='upper right', bbox_to_anchor=(1.0, 1.0), ncols=1) +@pytest.mark.parametrize("histtype", ["step", "stepfilled"]) +@pytest.mark.parametrize("color", [["blue", "green", "brown"], None]) +@pytest.mark.parametrize("edgecolor", [["red", "black", "blue"], [None]*3]) +@pytest.mark.parametrize("facecolor", [["blue", "green", "brown"], [None]*3]) +@check_figures_equal(extensions=["png"]) +def test_hist_vectorized_params(fig_test, fig_ref, histtype, color, edgecolor, + facecolor): + np.random.seed(19680801) + x = [np.random.randn(n) for n in [2000, 5000, 10000]] + linewidth = [1, 1.5, 2] + hatch = ["/", "\\", "."] + linestyle = ["-", "--", ":"] + + facecolor = facecolor if facecolor[0] is not None else color + if histtype == "step": + edgecolor = edgecolor if edgecolor[0] is not None else color + + _, bins, _ = fig_test.subplots().hist(x, bins=10, histtype=histtype, color=color, + edgecolor=edgecolor, facecolor=facecolor, + linewidth=linewidth, hatch=hatch, + linestyle=linestyle) + ref_ax = fig_ref.subplots() + color = [None]*3 if color is None else color + edgecolor = [None]*3 if edgecolor is None else edgecolor + facecolor = [None]*3 if facecolor is None else facecolor + + for i in range(2, -1, -1): + ref_ax.hist(x[i], bins=bins, histtype=histtype, color=color[i], + edgecolor=edgecolor[i], facecolor=facecolor[i], + linewidth=linewidth[i], hatch=hatch[i], + linestyle=linestyle[i]) + + def test_hist_barstacked_bottom_unchanged(): b = np.array([10, 20]) plt.hist([[0, 1], [0, 1]], 2, histtype="barstacked", bottom=b) From 4fcb70982e45ab9885f829a52d39219ecf428347 Mon Sep 17 00:00:00 2001 From: Pranav Date: Sun, 16 Jun 2024 15:41:59 +0530 Subject: [PATCH 04/13] Reduced to 13 tests in total Separated tests Modified test Modified test to pass by using halved zorders --- lib/matplotlib/tests/test_axes.py | 69 ++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 9119c1050e3f..c8e5b1eaa5a2 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4603,37 +4603,56 @@ def test_hist_stacked_bar(): ax.legend(loc='upper right', bbox_to_anchor=(1.0, 1.0), ncols=1) -@pytest.mark.parametrize("histtype", ["step", "stepfilled"]) -@pytest.mark.parametrize("color", [["blue", "green", "brown"], None]) -@pytest.mark.parametrize("edgecolor", [["red", "black", "blue"], [None]*3]) -@pytest.mark.parametrize("facecolor", [["blue", "green", "brown"], [None]*3]) @check_figures_equal(extensions=["png"]) -def test_hist_vectorized_params(fig_test, fig_ref, histtype, color, edgecolor, - facecolor): +def test_hist_vectorized_params(fig_test, fig_ref): np.random.seed(19680801) x = [np.random.randn(n) for n in [2000, 5000, 10000]] - linewidth = [1, 1.5, 2] + + facecolor = ["b", "g", "r"] + edgecolor = ["b", "g", "r"] hatch = ["/", "\\", "."] linestyle = ["-", "--", ":"] + linewidth = [1, 1.5, 2] + colors = ["b", "g", "r"] + ((axt1, axt2, axt3), (axt4, axt5, axt6)) = fig_test.subplots(2, 3) + ((axr1, axr2, axr3), (axr4, axr5, axr6)) = fig_ref.subplots(2, 3) + + _, bins, _ = axt1.hist(x, bins=10, histtype="stepfilled", facecolor=facecolor) + + for i, (xi, fc) in enumerate(zip(x, facecolor)): + axr1.hist(xi, bins=bins, histtype="stepfilled", facecolor=fc, + zorder=(len(x)-i)/2) + + _, bins, _ = axt2.hist(x, bins=10, histtype="step", edgecolor=edgecolor) + + for i, (xi, ec) in enumerate(zip(x, edgecolor)): + axr2.hist(xi, bins=bins, histtype="step", edgecolor=ec, zorder=(len(x)-i)/2) + + _, bins, _ = axt3.hist(x, bins=10, histtype="stepfilled", hatch=hatch, + facecolor=facecolor) + + for i, (xi, fc, ht) in enumerate(zip(x, facecolor, hatch)): + axr3.hist(xi, bins=bins, histtype="stepfilled", hatch=ht, facecolor=fc, + zorder=(len(x)-i)/2) + + _, bins, _ = axt4.hist(x, bins=10, histtype="step", linestyle=linestyle, + edgecolor=edgecolor) + + for i, (xi, ec, ls) in enumerate(zip(x, edgecolor, linestyle)): + axr4.hist(xi, bins=bins, histtype="step", linestyle=ls, edgecolor=ec, + zorder=(len(x)-i)/2) + + _, bins, _ = axt5.hist(x, bins=10, histtype="step", linewidth=linewidth, + edgecolor=edgecolor) + + for i, (xi, ec, lw) in enumerate(zip(x, edgecolor, linewidth)): + axr5.hist(xi, bins=bins, histtype="step", linewidth=lw, edgecolor=ec, + zorder=(len(x)-i)/2) + + _, bins, _ = axt6.hist(x, bins=10, histtype="stepfilled", color=colors) - facecolor = facecolor if facecolor[0] is not None else color - if histtype == "step": - edgecolor = edgecolor if edgecolor[0] is not None else color - - _, bins, _ = fig_test.subplots().hist(x, bins=10, histtype=histtype, color=color, - edgecolor=edgecolor, facecolor=facecolor, - linewidth=linewidth, hatch=hatch, - linestyle=linestyle) - ref_ax = fig_ref.subplots() - color = [None]*3 if color is None else color - edgecolor = [None]*3 if edgecolor is None else edgecolor - facecolor = [None]*3 if facecolor is None else facecolor - - for i in range(2, -1, -1): - ref_ax.hist(x[i], bins=bins, histtype=histtype, color=color[i], - edgecolor=edgecolor[i], facecolor=facecolor[i], - linewidth=linewidth[i], hatch=hatch[i], - linestyle=linestyle[i]) + for i, (xi, c) in enumerate(zip(x, colors)): + axr6.hist(xi, bins=bins, histtype="stepfilled", color=c, zorder=(len(x)-i)/2) def test_hist_barstacked_bottom_unchanged(): From 347ce221f0b289b2ec577114b29729c844760402 Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 25 Jun 2024 11:47:38 +0530 Subject: [PATCH 05/13] Added color semantics test and modified vectorized parameters test Edited expected facecolors for step Changed blue to C0 --- lib/matplotlib/tests/test_axes.py | 98 ++++++++++++++++--------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index c8e5b1eaa5a2..2456a3880a81 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4603,56 +4603,62 @@ def test_hist_stacked_bar(): ax.legend(loc='upper right', bbox_to_anchor=(1.0, 1.0), ncols=1) +@pytest.mark.parametrize('kwargs', ({'facecolor': ["b", "g", "r"]}, + {'edgecolor': ["b", "g", "r"]}, + {'hatch': ["/", "\\", "."]}, + {'linestyle': ["-", "--", ":"]}, + {'linewidth': [1, 1.5, 2]}, + {'color': ["b", "g", "r"]})) @check_figures_equal(extensions=["png"]) -def test_hist_vectorized_params(fig_test, fig_ref): +def test_hist_vectorized_params(fig_test, fig_ref, kwargs): np.random.seed(19680801) x = [np.random.randn(n) for n in [2000, 5000, 10000]] - facecolor = ["b", "g", "r"] - edgecolor = ["b", "g", "r"] - hatch = ["/", "\\", "."] - linestyle = ["-", "--", ":"] - linewidth = [1, 1.5, 2] - colors = ["b", "g", "r"] - ((axt1, axt2, axt3), (axt4, axt5, axt6)) = fig_test.subplots(2, 3) - ((axr1, axr2, axr3), (axr4, axr5, axr6)) = fig_ref.subplots(2, 3) - - _, bins, _ = axt1.hist(x, bins=10, histtype="stepfilled", facecolor=facecolor) - - for i, (xi, fc) in enumerate(zip(x, facecolor)): - axr1.hist(xi, bins=bins, histtype="stepfilled", facecolor=fc, - zorder=(len(x)-i)/2) - - _, bins, _ = axt2.hist(x, bins=10, histtype="step", edgecolor=edgecolor) - - for i, (xi, ec) in enumerate(zip(x, edgecolor)): - axr2.hist(xi, bins=bins, histtype="step", edgecolor=ec, zorder=(len(x)-i)/2) - - _, bins, _ = axt3.hist(x, bins=10, histtype="stepfilled", hatch=hatch, - facecolor=facecolor) - - for i, (xi, fc, ht) in enumerate(zip(x, facecolor, hatch)): - axr3.hist(xi, bins=bins, histtype="stepfilled", hatch=ht, facecolor=fc, - zorder=(len(x)-i)/2) - - _, bins, _ = axt4.hist(x, bins=10, histtype="step", linestyle=linestyle, - edgecolor=edgecolor) - - for i, (xi, ec, ls) in enumerate(zip(x, edgecolor, linestyle)): - axr4.hist(xi, bins=bins, histtype="step", linestyle=ls, edgecolor=ec, - zorder=(len(x)-i)/2) - - _, bins, _ = axt5.hist(x, bins=10, histtype="step", linewidth=linewidth, - edgecolor=edgecolor) - - for i, (xi, ec, lw) in enumerate(zip(x, edgecolor, linewidth)): - axr5.hist(xi, bins=bins, histtype="step", linewidth=lw, edgecolor=ec, - zorder=(len(x)-i)/2) - - _, bins, _ = axt6.hist(x, bins=10, histtype="stepfilled", color=colors) - - for i, (xi, c) in enumerate(zip(x, colors)): - axr6.hist(xi, bins=bins, histtype="stepfilled", color=c, zorder=(len(x)-i)/2) + (axt1, axt2) = fig_test.subplots(2) + (axr1, axr2) = fig_ref.subplots(2) + + for histtype, axt, axr in [("stepfilled", axt1, axr1), ("step", axt2, axr2)]: + _, bins, _ = axt.hist(x, bins=10, histtype=histtype, **kwargs) + + kw, values = next(iter(kwargs.items())) + for i, (xi, value) in enumerate(zip(x, values)): + axr.hist(xi, bins=bins, histtype=histtype, **{kw: value}, + zorder=(len(x)-i)/2) + + +@pytest.mark.parametrize('kwargs, patch_face, patch_edge', + [({'histtype': 'stepfilled', 'color': 'r', + 'facecolor': 'y', 'edgecolor': 'g'}, 'y', 'g'), + ({'histtype': 'step', 'color': 'r', + 'facecolor': 'y', 'edgecolor': 'g'}, ('y', 0), 'g'), + ({'histtype': 'stepfilled', 'color': 'r', + 'edgecolor': 'g'}, 'r', 'g'), + ({'histtype': 'step', 'color': 'r', + 'edgecolor': 'g'}, ('r', 0), 'g'), + ({'histtype': 'stepfilled', 'color': 'r', + 'facecolor': 'y'}, 'y', 'k'), + ({'histtype': 'step', 'color': 'r', + 'facecolor': 'y'}, ('y', 0), 'r'), + ({'histtype': 'stepfilled', + 'facecolor': 'y', 'edgecolor': 'g'}, 'y', 'g'), + ({'histtype': 'step', 'facecolor': 'y', + 'edgecolor': 'g'}, ('y', 0), 'g'), + ({'histtype': 'stepfilled', 'color': 'r'}, 'r', 'k'), + ({'histtype': 'step', 'color': 'r'}, ('r', 0), 'r'), + ({'histtype': 'stepfilled', 'facecolor': 'y'}, 'y', 'k'), + ({'histtype': 'step', 'facecolor': 'y'}, ('y', 0), 'C0'), + ({'histtype': 'stepfilled', 'edgecolor': 'g'}, 'C0', 'g'), + ({'histtype': 'step', 'edgecolor': 'g'}, ('C0', 0), 'g'), + ({'histtype': 'stepfilled'}, 'C0', 'k'), + ({'histtype': 'step'}, ('C0', 0), 'C0')]) +def test_hist_color_semantics(kwargs, patch_face, patch_edge): + _, _, patches = plt.figure().subplots().hist([1, 2, 3], **kwargs) + # 'C0'(blue) stands for the first color of the default color cycle + # as well as the patch.facecolor rcParam + # When the expected edgecolor is 'k'(black), it corresponds to the + # patch.edgecolor rcParam + assert all(mcolors.same_color([p.get_facecolor(), p.get_edgecolor()], + [patch_face, patch_edge]) for p in patches) def test_hist_barstacked_bottom_unchanged(): From d7ffeaa16c5ee7dc5306adc426bcd2ca4b65e27f Mon Sep 17 00:00:00 2001 From: Pranav Date: Fri, 28 Jun 2024 10:21:01 +0530 Subject: [PATCH 06/13] Added None patch test --- lib/matplotlib/axes/_axes.py | 27 ++++++++++++++------------- lib/matplotlib/tests/test_axes.py | 5 +++++ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index f936dba5580c..a619d458ff07 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -7226,20 +7226,21 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, linestyles = itertools.cycle(np.atleast_1d(kwargs.get('linestyle', None))) for patch, lbl in itertools.zip_longest(patches, labels): - p = patch[0] - kwargs.update({ - 'hatch': next(hatches), - 'linewidth': next(linewidths), - 'linestyle': next(linestyles), - 'edgecolor': next(edgecolors), - 'facecolor': next(facecolors), - }) - p._internal_update(kwargs) - if lbl is not None: - p.set_label(lbl) - for p in patch[1:]: + if patch: + p = patch[0] + kwargs.update({ + 'hatch': next(hatches), + 'linewidth': next(linewidths), + 'linestyle': next(linestyles), + 'edgecolor': next(edgecolors), + 'facecolor': next(facecolors), + }) p._internal_update(kwargs) - p.set_label('_nolegend_') + if lbl is not None: + p.set_label(lbl) + for p in patch[1:]: + p._internal_update(kwargs) + p.set_label('_nolegend_') if nx == 1: return tops[0], bins, patches[0] diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2456a3880a81..35da74dfea62 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4672,6 +4672,11 @@ def test_hist_emptydata(): ax.hist([[], range(10), range(10)], histtype="step") +def test_hist_none_patch(): + x = [[1, 2], [2, 3]] + plt.hist(x, label=["First", "Second", "Third"]) + + def test_hist_labels(): # test singleton labels OK fig, ax = plt.subplots() From 9024be4fc848af32b09f7b62e81c1062adab4e45 Mon Sep 17 00:00:00 2001 From: Pranav Date: Fri, 28 Jun 2024 10:27:02 +0530 Subject: [PATCH 07/13] Heading edit --- galleries/examples/statistics/histogram_multihist.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/galleries/examples/statistics/histogram_multihist.py b/galleries/examples/statistics/histogram_multihist.py index 0f12b34855d8..ba9570f97036 100644 --- a/galleries/examples/statistics/histogram_multihist.py +++ b/galleries/examples/statistics/histogram_multihist.py @@ -59,7 +59,7 @@ # * linestyles # # -# Histograms with Edge-Colors +# Edge-Colors # ........................... fig, ax = plt.subplots() @@ -74,7 +74,7 @@ plt.show() # %% -# Face colors +# Face-Colors # ........................... fig, ax = plt.subplots() From 3bfca5fe17e36798f0f836654319b4f72f67575b Mon Sep 17 00:00:00 2001 From: Pranav Date: Fri, 28 Jun 2024 10:50:10 +0530 Subject: [PATCH 08/13] Simplified test --- lib/matplotlib/tests/test_axes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 35da74dfea62..051cc488676b 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4673,8 +4673,7 @@ def test_hist_emptydata(): def test_hist_none_patch(): - x = [[1, 2], [2, 3]] - plt.hist(x, label=["First", "Second", "Third"]) + plt.hist([1, 2], label=["First", "Second"]) def test_hist_labels(): From 6029e32e35dcc2573b3572103d6b9aa73b73399e Mon Sep 17 00:00:00 2001 From: Pranav Date: Mon, 1 Jul 2024 11:51:48 +0530 Subject: [PATCH 09/13] Updated none patch test --- lib/matplotlib/tests/test_axes.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 051cc488676b..dbdf73fa7e73 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4673,7 +4673,13 @@ def test_hist_emptydata(): def test_hist_none_patch(): - plt.hist([1, 2], label=["First", "Second"]) + # To cover None patches when excess labels are provided + labels = ["First", "Second"] + patches = [[1, 2]] + fig, ax = plt.subplots() + ax.hist(patches, label=labels) + _, lbls = ax.get_legend_handles_labels() + assert (len(lbls) < len(labels) and len(patches) < len(labels)) def test_hist_labels(): From 0349e7d42b6ef256a45f21b769c59a8d786d9aaf Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 2 Jul 2024 08:35:26 +0530 Subject: [PATCH 10/13] Made None patch condition explicit --- lib/matplotlib/axes/_axes.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index a619d458ff07..c0329abc02c7 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -7226,21 +7226,22 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, linestyles = itertools.cycle(np.atleast_1d(kwargs.get('linestyle', None))) for patch, lbl in itertools.zip_longest(patches, labels): - if patch: - p = patch[0] - kwargs.update({ - 'hatch': next(hatches), - 'linewidth': next(linewidths), - 'linestyle': next(linestyles), - 'edgecolor': next(edgecolors), - 'facecolor': next(facecolors), - }) + if not patch: + continue + p = patch[0] + kwargs.update({ + 'hatch': next(hatches), + 'linewidth': next(linewidths), + 'linestyle': next(linestyles), + 'edgecolor': next(edgecolors), + 'facecolor': next(facecolors), + }) + p._internal_update(kwargs) + if lbl is not None: + p.set_label(lbl) + for p in patch[1:]: p._internal_update(kwargs) - if lbl is not None: - p.set_label(lbl) - for p in patch[1:]: - p._internal_update(kwargs) - p.set_label('_nolegend_') + p.set_label('_nolegend_') if nx == 1: return tops[0], bins, patches[0] From d1a80797948cd4bdbd70e04d05b4c183e931d781 Mon Sep 17 00:00:00 2001 From: Pranav Date: Tue, 9 Jul 2024 18:50:30 +0530 Subject: [PATCH 11/13] Made suggested changes --- .../histogram_vectorized_parameters.rst | 6 ++--- .../statistics/histogram_multihist.py | 23 ++++++++++--------- lib/matplotlib/tests/test_axes.py | 23 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/doc/users/next_whats_new/histogram_vectorized_parameters.rst b/doc/users/next_whats_new/histogram_vectorized_parameters.rst index 4f063c14651d..f4b6b38e1ce6 100644 --- a/doc/users/next_whats_new/histogram_vectorized_parameters.rst +++ b/doc/users/next_whats_new/histogram_vectorized_parameters.rst @@ -1,9 +1,9 @@ -Vectorize ``hatch``, ``edgecolor``, ``facecolor``, ``linewidth`` and ``linestyle`` in *hist* methods ----------------------------------------------------------------------------------------------------- +Vectorized ``hist`` style parameters +------------------------------------ The parameters ``hatch``, ``edgecolor``, ``facecolor``, ``linewidth`` and ``linestyle`` of the `~matplotlib.axes.Axes.hist` method are now vectorized. -This means that you can pass in unique parameters for each histogram that is generated +This means that you can pass in individual parameters for each histogram when the input *x* has multiple datasets. diff --git a/galleries/examples/statistics/histogram_multihist.py b/galleries/examples/statistics/histogram_multihist.py index ba9570f97036..63cfde06c053 100644 --- a/galleries/examples/statistics/histogram_multihist.py +++ b/galleries/examples/statistics/histogram_multihist.py @@ -50,16 +50,17 @@ # Setting properties for each dataset # ----------------------------------- # -# Plotting bar charts with datasets differentiated using: +# You can style the histograms individually by passing a list of values to the +# following parameters: # -# * edgecolors -# * facecolors -# * hatches -# * linewidths -# * linestyles +# * edgecolor +# * facecolor +# * hatch +# * linewidth +# * linestyle # # -# Edge-Colors +# edgecolor # ........................... fig, ax = plt.subplots() @@ -74,7 +75,7 @@ plt.show() # %% -# Face-Colors +# facecolor # ........................... fig, ax = plt.subplots() @@ -88,7 +89,7 @@ plt.show() # %% -# Hatches +# hatch # ....................... fig, ax = plt.subplots() @@ -102,7 +103,7 @@ plt.show() # %% -# Linewidths +# linewidth # .......................... fig, ax = plt.subplots() @@ -118,7 +119,7 @@ plt.show() # %% -# LineStyles +# linestyle # .......................... fig, ax = plt.subplots() diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index dbdf73fa7e73..56c3d53a617c 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4612,18 +4612,18 @@ def test_hist_stacked_bar(): @check_figures_equal(extensions=["png"]) def test_hist_vectorized_params(fig_test, fig_ref, kwargs): np.random.seed(19680801) - x = [np.random.randn(n) for n in [2000, 5000, 10000]] + xs = [np.random.randn(n) for n in [20, 50, 100]] (axt1, axt2) = fig_test.subplots(2) (axr1, axr2) = fig_ref.subplots(2) for histtype, axt, axr in [("stepfilled", axt1, axr1), ("step", axt2, axr2)]: - _, bins, _ = axt.hist(x, bins=10, histtype=histtype, **kwargs) + _, bins, _ = axt.hist(xs, bins=10, histtype=histtype, **kwargs) kw, values = next(iter(kwargs.items())) - for i, (xi, value) in enumerate(zip(x, values)): - axr.hist(xi, bins=bins, histtype=histtype, **{kw: value}, - zorder=(len(x)-i)/2) + for i, (x, value) in enumerate(zip(xs, values)): + axr.hist(x, bins=bins, histtype=histtype, **{kw: value}, + zorder=(len(xs)-i)/2) @pytest.mark.parametrize('kwargs, patch_face, patch_edge', @@ -4672,14 +4672,13 @@ def test_hist_emptydata(): ax.hist([[], range(10), range(10)], histtype="step") -def test_hist_none_patch(): - # To cover None patches when excess labels are provided - labels = ["First", "Second"] - patches = [[1, 2]] +def test_hist_unused_labels(): + # When a list with one dataset and N elements is provided and N labels, ensure + # that the first label is used for the dataset and all other labels are ignored fig, ax = plt.subplots() - ax.hist(patches, label=labels) - _, lbls = ax.get_legend_handles_labels() - assert (len(lbls) < len(labels) and len(patches) < len(labels)) + ax.hist([[1, 2, 3]], label=["values", "unused", "also unused"]) + _, labels = ax.get_legend_handles_labels() + assert labels == ["values"] def test_hist_labels(): From 9a813f490822c6299e05cb7d37c064480e31f3e4 Mon Sep 17 00:00:00 2001 From: Pranav Date: Wed, 10 Jul 2024 14:26:37 +0530 Subject: [PATCH 12/13] Added version-added directive --- lib/matplotlib/axes/_axes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index c0329abc02c7..4721b26980d4 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6942,6 +6942,8 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, datasets in *x*: *edgecolors*, *facecolors*, *linewidths*, *linestyles*, *hatches*. + .. versionadded:: 3.10 + See Also -------- hist2d : 2D histogram with rectangular bins From 43ce57d8155fd3839967a5f0439bfc22f1b52c18 Mon Sep 17 00:00:00 2001 From: Pranav Date: Sun, 14 Jul 2024 13:04:56 +0530 Subject: [PATCH 13/13] Made suggested changes --- .../next_whats_new/histogram_vectorized_parameters.rst | 4 ++-- galleries/examples/statistics/histogram_multihist.py | 10 +++++----- lib/matplotlib/axes/_axes.py | 3 ++- lib/matplotlib/tests/test_axes.py | 8 ++++---- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/doc/users/next_whats_new/histogram_vectorized_parameters.rst b/doc/users/next_whats_new/histogram_vectorized_parameters.rst index f4b6b38e1ce6..7b9c04e71739 100644 --- a/doc/users/next_whats_new/histogram_vectorized_parameters.rst +++ b/doc/users/next_whats_new/histogram_vectorized_parameters.rst @@ -1,7 +1,7 @@ Vectorized ``hist`` style parameters ------------------------------------ -The parameters ``hatch``, ``edgecolor``, ``facecolor``, ``linewidth`` and ``linestyle`` +The parameters *hatch*, *edgecolor*, *facecolor*, *linewidth* and *linestyle* of the `~matplotlib.axes.Axes.hist` method are now vectorized. This means that you can pass in individual parameters for each histogram when the input *x* has multiple datasets. @@ -9,7 +9,7 @@ when the input *x* has multiple datasets. .. plot:: :include-source: true - :alt: Four charts, each displaying stacked histograms of three Poisson distributions. Each chart differentiates the histograms using various parameters: ax1 uses different linewidths, ax2 uses different hatches, ax3 uses different edgecolors, and ax4 uses different facecolors. Each histogram in ax1 and ax3 also has a different edgecolor. + :alt: Four charts, each displaying stacked histograms of three Poisson distributions. Each chart differentiates the histograms using various parameters: top left uses different linewidths, top right uses different hatches, bottom left uses different edgecolors, and bottom right uses different facecolors. Each histogram on the left side also has a different edgecolor. import matplotlib.pyplot as plt import numpy as np diff --git a/galleries/examples/statistics/histogram_multihist.py b/galleries/examples/statistics/histogram_multihist.py index 63cfde06c053..b9a9c5f0bf26 100644 --- a/galleries/examples/statistics/histogram_multihist.py +++ b/galleries/examples/statistics/histogram_multihist.py @@ -61,7 +61,7 @@ # # # edgecolor -# ........................... +# ......... fig, ax = plt.subplots() @@ -76,7 +76,7 @@ # %% # facecolor -# ........................... +# ......... fig, ax = plt.subplots() @@ -90,7 +90,7 @@ # %% # hatch -# ....................... +# ..... fig, ax = plt.subplots() @@ -104,7 +104,7 @@ # %% # linewidth -# .......................... +# ......... fig, ax = plt.subplots() @@ -120,7 +120,7 @@ # %% # linestyle -# .......................... +# ......... fig, ax = plt.subplots() diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4721b26980d4..07393b1028d2 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -6940,9 +6940,10 @@ def hist(self, x, bins=None, range=None, density=False, weights=None, `~matplotlib.patches.Patch` properties. The following properties additionally accept a sequence of values corresponding to the datasets in *x*: - *edgecolors*, *facecolors*, *linewidths*, *linestyles*, *hatches*. + *edgecolors*, *facecolors*, *lines*, *linestyles*, *hatches*. .. versionadded:: 3.10 + Allowing sequences of values in above listed Patch properties. See Also -------- diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 56c3d53a617c..b1f97b3f855f 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -4627,6 +4627,10 @@ def test_hist_vectorized_params(fig_test, fig_ref, kwargs): @pytest.mark.parametrize('kwargs, patch_face, patch_edge', + # 'C0'(blue) stands for the first color of the + # default color cycle as well as the patch.facecolor rcParam + # When the expected edgecolor is 'k'(black), + # it corresponds to the patch.edgecolor rcParam [({'histtype': 'stepfilled', 'color': 'r', 'facecolor': 'y', 'edgecolor': 'g'}, 'y', 'g'), ({'histtype': 'step', 'color': 'r', @@ -4653,10 +4657,6 @@ def test_hist_vectorized_params(fig_test, fig_ref, kwargs): ({'histtype': 'step'}, ('C0', 0), 'C0')]) def test_hist_color_semantics(kwargs, patch_face, patch_edge): _, _, patches = plt.figure().subplots().hist([1, 2, 3], **kwargs) - # 'C0'(blue) stands for the first color of the default color cycle - # as well as the patch.facecolor rcParam - # When the expected edgecolor is 'k'(black), it corresponds to the - # patch.edgecolor rcParam assert all(mcolors.same_color([p.get_facecolor(), p.get_edgecolor()], [patch_face, patch_edge]) for p in patches)