Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple hatches, edgecolors and linewidths in histograms #28073

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
49cbec6
Add support for multiple hatches, edgecolor and linewidth in histograms
Impaler343 Apr 13, 2024
7d32ed0
Add hatch, linewidth and edgecolor parameters to multihist example
Impaler343 Apr 14, 2024
fd45352
Add test for added parameters
Impaler343 Apr 25, 2024
bdfc2e7
Made changes concise according to suggestion
Impaler343 May 19, 2024
0f93187
Made a more detailed gallery example
Impaler343 May 19, 2024
0fa897a
Added whats new note, documentation for vectorization, doc fix
Impaler343 May 26, 2024
bd0ce7c
Added new test, and reverted changes in old test
Impaler343 May 28, 2024
2351cbd
Modified test to pass codecov, added plot in whats new entry
Impaler343 May 29, 2024
528baa1
Altered whats new entry, docs and gallery example
Impaler343 Jun 4, 2024
a9a60a6
Resolved edgecolor and facecolor setting
Impaler343 Jun 5, 2024
f6110d5
Minor fix
Impaler343 Jun 5, 2024
fc6a1ba
Fix docs
Impaler343 Jun 6, 2024
36e8a64
Modified files to include facecolor and added test
Impaler343 Jun 6, 2024
c6d19ed
Removed figsize from test
Impaler343 Jun 6, 2024
a55f7e7
Add multiple baseline image names
Impaler343 Jun 6, 2024
f40547d
Fixed test?
Impaler343 Jun 7, 2024
b69d32d
Fixed test?
Impaler343 Jun 7, 2024
a770e91
Removed parametrize usage
Impaler343 Jun 7, 2024
ab3b35f
Add baseline images
Impaler343 Jun 7, 2024
93fdac9
Add baseline image
Impaler343 Jun 7, 2024
6fa8870
Fix docs
Impaler343 Jun 7, 2024
f0c6544
Fix docs
Impaler343 Jun 7, 2024
a56811c
Deleted baseline images, changed test
Impaler343 Jun 8, 2024
73a30aa
Fix test
Impaler343 Jun 8, 2024
f2932c1
Fix test
Impaler343 Jun 8, 2024
969493a
Handled None array passing to color
Impaler343 Jun 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 46 additions & 0 deletions doc/users/next_whats_new/histogram_vectorized_parameters.rst
Original file line number Diff line number Diff line change
@@ -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. Edgecolors have ax1 and ax3 as well to accentuate the differences between the histograms.

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()
89 changes: 88 additions & 1 deletion galleries/examples/statistics/histogram_multihist.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -45,6 +45,93 @@
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()

# %%
# Histograms with 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()

# %%
# Histograms with 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()

# %%
# Histograms with 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()

# %%
# Histograms with 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
Expand Down
26 changes: 24 additions & 2 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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*, *linewidths*, *linestyles*, *hatches*.

See Also
--------
Expand Down Expand Up @@ -7011,7 +7014,7 @@ def hist(self, x, bins=None, range=None, density=False, weights=None,
if len_xi:
input_empty = False

if color is None:
if not any(np.atleast_1d(color)):
colors = [self._get_lines.get_next_color() for i in range(nx)]
else:
colors = mcolors.to_rgba_array(color)
Expand Down Expand Up @@ -7210,9 +7213,28 @@ 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)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing code that sets color=colors and you don't test the colors=set, facecolor=None, edgecolor=None case, so can you verify that case works as expected?

Copy link
Contributor Author

@Impaler343 Impaler343 Jun 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

colors is already set around line 7017. And yes it works as the table mentioned. Should I add it to the tests with an only color test as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry, I read colors = mcolors.to_rgba_array(color) wrong 🤦‍♀️
Um, yes, please add one "only color" case. Basically, the goal is to check that every combination in #28073 (comment) works as expected in the vectorized case - where hmm, easiest thing may then be 1 image with 10 subplots, one for each case, (And possibly label each case). I can also be sold on that being out of scope here/testing for the none vectorized case is good enough.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which also, this is where check_figures equal could be useful b/c then you can use pytest to autobuild all the combinations by stacking parameterizations
https://docs.pytest.org/en/7.1.x/how-to/parametrize.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The image might be too cluttered with 10 subplots, I tried making a big image earlier, is a limit on the figsize?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think wecan use parametrization with image comparison tests as there is only one baseline image to compare from?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that's why I was pushing paramterization + check_figures_equal
and there shouldn't be an issue w/ setting a figsize in the test far as I know

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]
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)
Expand Down
30 changes: 30 additions & 0 deletions lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4603,6 +4603,36 @@ 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]*3])
@pytest.mark.parametrize("edgecolor", [["red", "black", "blue"], [None]*3])
@pytest.mark.parametrize("facecolor", [["blue", "green", "brown"], [None]*3])
@pytest.mark.parametrize("linewidth", [[1, 1.5, 2], [None]*3])
@pytest.mark.parametrize("hatch", [["/", "\\", "."], [None]*3])
@pytest.mark.parametrize("linestyle", [["-", "--", ":"], [None]*3])
@check_figures_equal(extensions=["png"])
def test_hist_vectorized_params(fig_test, fig_ref, histtype, color, edgecolor,
facecolor, linewidth, hatch, linestyle):
np.random.seed(19680801)
x = [np.random.randn(n) for n in [2000, 5000, 10000]]

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()

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)
Expand Down