Repository: matplotlib/matplotlib

Issue:
[Bug]: stacked histogram does not properly handle edgecolor and hatches
### Bug summary

When using stacked histogram in matplotlib 3.7.2 the functionality of hatch and edgecolor is broken. It simply gives an error if trying to define either a separate edgecolor or hatch value for the stacked parts of the histogram.

### Code for reproduction

```python
import matplotlib.pyplot as plt
import numpy as np

plt.hist([np.random.uniform(size=100), np.random.uniform(size=50)],
         stacked=True,
         color=["C0", "C1"],
         edgecolor='black',
         hatch=["/", "."],
         linewidth=1,
         fill=True,
         density=False, 
         )
```


### Actual outcome

> Traceback (most recent call last):
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3460, in run_code
>     exec(code_obj, self.user_global_ns, self.user_ns)
>   File "<ipython-input-11-3f15e200502a>", line 1, in <module>
>     plt.hist([np.random.uniform(size=100), np.random.uniform(size=50)],
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/pyplot.py", line 2645, in hist
>     return gca().hist(
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/__init__.py", line 1446, in inner
>     return func(ax, *map(sanitize_sequence, args), **kwargs)
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/axes/_axes.py", line 6944, in hist
>     p._internal_update(kwargs)
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/artist.py", line 1223, in _internal_update
>     return self._update_props(
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/artist.py", line 1199, in _update_props
>     ret.append(func(v))
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/patches.py", line 525, in set_hatch
>     mhatch._validate_hatch_pattern(hatch)
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/hatch.py", line 188, in _validate_hatch_pattern
>     invalids = ''.join(sorted(invalids))
> TypeError: sequence item 0: expected str instance, NoneType found


While using different edgecolors for the stacked part with 

```
plt.hist([np.random.uniform(size=100), np.random.uniform(size=50)],
         stacked=True,
         color=["C0", "C1"],
         edgecolor=["C2", "C3"],
         hatch="/",
         linewidth=1,
         fill=True,
         density=False, 
         )
```

delivers the error

> Traceback (most recent call last):
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/IPython/core/interactiveshell.py", line 3460, in run_code
>     exec(code_obj, self.user_global_ns, self.user_ns)
>   File "<ipython-input-16-4bdbf23ac913>", line 1, in <module>
>     plt.hist([np.random.uniform(size=100), np.random.uniform(size=50)],
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/pyplot.py", line 2645, in hist
>     return gca().hist(
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/__init__.py", line 1446, in inner
>     return func(ax, *map(sanitize_sequence, args), **kwargs)
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/axes/_axes.py", line 6944, in hist
>     p._internal_update(kwargs)
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/artist.py", line 1223, in _internal_update
>     return self._update_props(
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/artist.py", line 1199, in _update_props
>     ret.append(func(v))
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/patches.py", line 341, in set_edgecolor
>     self._set_edgecolor(color)
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/patches.py", line 327, in _set_edgecolor
>     self._edgecolor = colors.to_rgba(color, self._alpha)
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/colors.py", line 299, in to_rgba
>     rgba = _to_rgba_no_colorcycle(c, alpha)
>   File "/opt/anaconda3/envs/code/lib/python3.10/site-packages/matplotlib/colors.py", line 383, in _to_rgba_no_colorcycle
>     raise ValueError("RGBA sequence should have length 3 or 4")
> ValueError: RGBA sequence should have length 3 or 4

It seems to interpret the list of `edgecolor` ["C2", "C3"] as one color, although it is able to manage similar input for `color`.

### Expected outcome

It should look similar to the following example, except that the top orange part should have dots and not slanted lines:
![test_bugreport](https://github.com/matplotlib/matplotlib/assets/43748463/d29ca5f2-3f89-4c57-a608-edb4aba302ce)
The code used to generate this plot:

```
import matplotlib.pyplot as plt
import numpy as np
plt.hist([np.random.uniform(size=100), np.random.uniform(size=50)],
         stacked=True,
         color=["C0", "C1"],
         edgecolor='black',
         hatch="/",
         linewidth=1,
         fill=True,
         density=False, 
         )
```

In case of the second example it should habe two different edgecolors for the first and second part of the stacked histogram.

### Additional information

_No response_

### Operating system

MacOSX

### Matplotlib Version

3.7.2

### Matplotlib Backend

MacOSX

### Python version

3.10.0

### Jupyter version

_No response_

### Installation

conda


Discussion:
Didn't have the chance to test on Python 3.10.0 and mpl 3.7.2 yet. But running first block of the code above with mpl 3.9.0 dev and Python 3.11.5 (conda) on Mac will give:

```
Traceback (most recent call last):
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/backend_bases.py", line 1215, in _on_timer
    ret = func(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/backends/backend_macosx.py", line 70, in callback_func
    callback()
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/backends/backend_macosx.py", line 93, in _draw_idle
    self.draw()
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/backends/backend_macosx.py", line 52, in draw
    super().draw()
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/backends/backend_agg.py", line 387, in draw
    self.figure.draw(self.renderer)
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/artist.py", line 95, in draw_wrapper
    result = draw(artist, renderer, *args, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/artist.py", line 72, in draw_wrapper
    return draw(artist, renderer)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/figure.py", line 3101, in draw
    mimage._draw_list_compositing_images(
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/image.py", line 132, in _draw_list_compositing_images
    a.draw(renderer)
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/artist.py", line 72, in draw_wrapper
    return draw(artist, renderer)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/axes/_base.py", line 3095, in draw
    mimage._draw_list_compositing_images(
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/image.py", line 132, in _draw_list_compositing_images
    a.draw(renderer)
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/artist.py", line 72, in draw_wrapper
    return draw(artist, renderer)
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/patches.py", line 588, in draw
    self._draw_paths_with_artist_properties(
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/patches.py", line 573, in _draw_paths_with_artist_properties
    renderer.draw_path(gc, *draw_path_args)
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/backends/backend_agg.py", line 131, in draw_path
    self._renderer.draw_path(gc, path, transform, rgbFace)
  File "/Users/xxxxxx/Documents/matplotlib-project/matplotlib/lib/matplotlib/backend_bases.py", line 1018, in get_hatch_path
    return Path.hatch(hatch, density)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'list'
```
Hi, I'm working in this issue if @stevezhang1999 is not . Right now 'color' is a parameter in hist function, but edgecolor/hatch isn't. The function that deals with this 2 other parameters is in artist.py, and accepts types 'color' or 'none'. I was thinking about adding these parameters in hist function, but that means we would have to change documentation. If someone have a better approach pls tell me 
> Hi, I'm working in this issue if @stevezhang1999 is not . Right now 'color' is a parameter in hist function, but edgecolor/hatch isn't. The function that deals with this 2 other parameters is in artist.py, and accepts types 'color' or 'none'. I was thinking about adding these parameters in hist function, but that means we would have to change documentation. If someone have a better approach pls tell me

You are welcomed to work on it if that interests you!
hey guys, in developer mode `plt.show()` doesn't work. Do you know how can I see the plots so I know if my code is working correctly?
There is no reason for plt.show to not work in developer mode.  Check your installation 



Patch:
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..7b9c04e71739
--- /dev/null
+++ b/doc/users/next_whats_new/histogram_vectorized_parameters.rst
@@ -0,0 +1,46 @@
+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 individual parameters for each histogram
+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: 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
+    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 f1957dc38939..b9a9c5f0bf26 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
 
@@ -45,6 +45,94 @@
 fig.tight_layout()
 plt.show()
 
+# %%
+# -----------------------------------
+# Setting properties for each dataset
+# -----------------------------------
+#
+# You can style the histograms individually by passing a list of values to the
+# following parameters:
+#
+# * edgecolor
+# * facecolor
+# * hatch
+# * linewidth
+# * linestyle
+#
+#
+# edgecolor
+# .........
+
+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()
+
+# %%
+# facecolor
+# .........
+
+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()
+
+# %%
+# hatch
+# .....
+
+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()
+
+# %%
+# linewidth
+# .........
+
+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()
+
+# %%
+# linestyle
+# .........
+
+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 52c99b125d36..07393b1028d2 100644
--- a/lib/matplotlib/axes/_axes.py
+++ b/lib/matplotlib/axes/_axes.py
@@ -6937,7 +6937,13 @@ 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*, *lines*, *linestyles*, *hatches*.
+
+            .. versionadded:: 3.10
+               Allowing sequences of values in above listed Patch properties.
 
         See Also
         --------
@@ -7210,15 +7216,35 @@ 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 histtype == "step":
+            edgecolors = itertools.cycle(np.atleast_1d(kwargs.get('edgecolor',
+                                                                  colors)))
+        else:
+            edgecolors = itertools.cycle(np.atleast_1d(kwargs.get("edgecolor", None)))
+
+        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 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]


Test patch:
diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py
index dd37d3d8ee80..b1f97b3f855f 100644
--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -4603,6 +4603,64 @@ 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, kwargs):
+    np.random.seed(19680801)
+    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(xs, bins=10, histtype=histtype, **kwargs)
+
+        kw, values = next(iter(kwargs.items()))
+        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',
+                         # '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',
+                            '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)
+    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():
     b = np.array([10, 20])
     plt.hist([[0, 1], [0, 1]], 2, histtype="barstacked", bottom=b)
@@ -4614,6 +4672,15 @@ def test_hist_emptydata():
     ax.hist([[], range(10), range(10)], histtype="step")
 
 
+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([[1, 2, 3]], label=["values", "unused", "also unused"])
+    _, labels = ax.get_legend_handles_labels()
+    assert labels == ["values"]
+
+
 def test_hist_labels():
     # test singleton labels OK
     fig, ax = plt.subplots()