-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add support for multiple hatches, edgecolors and linewidths in histograms #28073
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
Changes from 8 commits
1a6ec0f
0e52daa
34df06a
4fcb709
347ce22
d7ffeaa
9024be4
3bfca5f
6029e32
0349e7d
d1a8079
9a813f4
43ce57d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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`` | ||
Impaler343 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
Impaler343 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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. | ||
Impaler343 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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() |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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) | ||||||
x = [np.random.randn(n) for n in [2000, 5000, 10000]] | ||||||
Impaler343 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
(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) | ||||||
Impaler343 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
|
||||||
F438 | @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()], | ||||||
Impaler343 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
[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,10 @@ def test_hist_emptydata(): | |||||
ax.hist([[], range(10), range(10)], histtype="step") | ||||||
|
||||||
|
||||||
def test_hist_none_patch(): | ||||||
plt.hist([1, 2], label=["First", "Second"]) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
None patch is the empty list/array case There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And what you probably want to test is
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This too works, basically the None condition is hit when we don't have enough datasets to cover each label. This is the doing of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should i still change it up? As the previous one seems clearer There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I thought that zip_longest would copy the patch to both labels. I think there needs to be some sort of assert that you're test is doing what you think it is - maybe then checking that len(patches) != len(labels) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is len(lbs) here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The number of labels that have been used in the plot There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I meant what were the actual numbers/does this test pass. But yes if this test passes and coverage improves (add a comment that this test is to cover the if not patch case), then I'll let this concern go. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But also assert len(patches) < len(labels) too I think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes the tests pass, and yes we can add the length(patches) < len(labels) one as well, does the same comparison. |
||||||
|
||||||
|
||||||
def test_hist_labels(): | ||||||
# test singleton labels OK | ||||||
fig, ax = plt.subplots() | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.