Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork8.1k
ENH: Add broadcasted hatch to grouped_bar#30683
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Changes fromall commits
787375ac0d13d13ceee0c38deb9340bac2ef9cf38f0e8b15e2ca419f91843e41fc24933bf57a742a134d7f7779edc499a14b255c27a314e447c575ba9358955d8f84cFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| grouped_bar hatch patterns | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| `.Axes.grouped_bar` now accepts a list of strings describing hatch patterns, | ||
| which are applied sequentially to the datasets, cycling if fewer patterns are | ||
| provided—similar to how colors are handled. | ||
| .. plot:: | ||
| import matplotlib.pyplot as plt | ||
| import numpy as np | ||
| fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(8, 4)) | ||
| x = np.arange(3) | ||
| heights = [ | ||
| [1, 2, 3], | ||
| [2, 1, 2], | ||
| [3, 2, 1], | ||
| ] | ||
| ax1.grouped_bar(heights, tick_labels=["A", "B", "C"], hatch="/") | ||
| ax2.grouped_bar(heights, tick_labels=["A", "B", "C"], hatch=["/", "\\\\", ".."]) | ||
| ax1.set_title("hatch='/'") | ||
| ax2.set_title("hatch=['/', '\\\\', '..']") | ||
| plt.show() | ||
| The first plot applies the same hatch to all bars. | ||
| The second plot uses a different hatch for each dataset, cycling automatically | ||
| if the list of hatches is shorter than the number of datasets. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -3050,7 +3050,7 @@ def broken_barh(self, xranges, yrange, align="bottom", **kwargs): | ||
| @_docstring.interpd | ||
| def grouped_bar(self, heights, *, positions=None, group_spacing=1.5, bar_spacing=0, | ||
| tick_labels=None, labels=None, orientation="vertical", colors=None, | ||
| hatch=None,**kwargs): | ||
| """ | ||
| Make a grouped bar plot. | ||
| @@ -3190,6 +3190,19 @@ def grouped_bar(self, heights, *, positions=None, group_spacing=1.5, bar_spacing | ||
| If not specified, the colors from the Axes property cycle will be used. | ||
| hatch : sequence of str or None, optional | ||
| Hatch pattern(s) to apply per dataset. | ||
| - If ``None`` (default), no hatching is applied. | ||
| - If a sequence of strings is provided (e.g., ``['//', 'xx', '..']``), | ||
| the patterns are cycled across datasets. | ||
| - Single string values (e.g., ``'//'``) are **not supported**. | ||
| Raises | ||
| ------ | ||
| ValueError | ||
| If ``hatch`` is a single string or a non-iterable value. | ||
| **kwargs : `.Rectangle` properties | ||
| %(Rectangle:kwdoc)s | ||
| @@ -3318,6 +3331,36 @@ def grouped_bar(self, heights, *, positions=None, group_spacing=1.5, bar_spacing | ||
| # TODO: do we want to be more restrictive and check lengths? | ||
| colors = itertools.cycle(colors) | ||
| if hatch is None: | ||
| # No hatch specified: disable hatching entirely by cycling [None]. | ||
| hatches = itertools.cycle([None]) | ||
| # TODO: Discussion — | ||
| # Should grouped_bar() apply a default hatch pattern (e.g., '//') | ||
| # when none is provided ? | ||
| elif isinstance(hatch, str) or not hasattr(hatch, "__iter__"): | ||
| # Single strings or non-iterable values are not supported here. | ||
| # Explicit sequences of hatch patterns are required, ensuring | ||
| # predictable one-to-one mapping between datasets and hatches. | ||
| raise ValueError( | ||
| "'hatch' must be a sequence of strings with one entry per dataset" | ||
| ) | ||
| else: | ||
| # Sequence of hatch patterns: cycle through them as needed. | ||
| # Example: hatch=['//', 'xx', '..'] → patterns repeat across datasets. | ||
| hatches = itertools.cycle(hatch) | ||
Member 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. If we have enforced the length of the list then I think we do not need Member 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. There are good arguments for and against enforcing length. Since we currently do not do this in most parts of the library, let's stick to that approach for now. Enforcing length is a separate discussion. Author 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. Author 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. If you confirm that we can allow cycling for this PR, I’ll go ahead and make the required changes. I’ve also opened an issue to discuss this behavior more broadly and reach a consensus, since it could be a useful improvement across style arguments: Member 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. @ilakkmanoharan for this PR, please allow cycling and do not enforce the list length. Author 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. | ||
| # TODO: Discussion — | ||
| # We may later introduce optional strict validation: | ||
| # if len(hatch) != num_datasets: | ||
| # raise ValueError( | ||
| # f"Expected {num_datasets} hatches, got {len(hatch)}" | ||
| # ) | ||
| # This would enforce a strict 1:1 correspondence between | ||
| # datasets and provided hatches, preventing silent cycling. | ||
| bar_width = (group_distance / | ||
| (num_datasets + (num_datasets - 1) * bar_spacing + group_spacing)) | ||
| bar_spacing_abs = bar_spacing * bar_width | ||
| @@ -3331,15 +3374,26 @@ def grouped_bar(self, heights, *, positions=None, group_spacing=1.5, bar_spacing | ||
| # place the bars, but only use numerical positions, categorical tick labels | ||
| # are handled separately below | ||
| bar_containers = [] | ||
| # Both colors and hatches are cycled indefinitely using itertools.cycle. | ||
| # heights and labels, however, are finite (length = num_datasets). | ||
| # Because zip() stops at the shortest iterable, this loop executes exactly | ||
| # num_datasets times even though colors and hatches are infinite. | ||
| # This ensures one (color, hatch) pair per dataset | ||
| # without explicit length checks. | ||
| for i, (hs, label, color, hatch_pattern) in enumerate( | ||
| zip(heights, labels, colors, hatches) | ||
| ): | ||
| lefts = (group_centers - 0.5 * group_distance + margin_abs | ||
| + i * (bar_width + bar_spacing_abs)) | ||
| common_kwargs = dict( | ||
| align="edge", label=label, color=color, hatch=hatch_pattern, **kwargs | ||
| ) | ||
| if orientation == "vertical": | ||
| bc = self.bar(lefts, hs, width=bar_width, **common_kwargs, **kwargs) | ||
| else: | ||
| bc = self.barh(lefts, hs, height=bar_width, **common_kwargs, **kwargs) | ||
| bar_containers.append(bc) | ||
| if tick_labels is not None: | ||
Uh oh!
There was an error while loading.Please reload this page.