-
-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Consolidate style parameter handling for plotting methods that call other plotting methods #30808
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
base: main
Are you sure you want to change the base?
Conversation
| if colors is None: | ||
| colors = [axes._get_lines.get_next_color() for _ in y] | ||
|
|
||
| kwargs['hatch'] = hatch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsure whether to do this or to remove hatch from the named parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really important, but I'm inclined to remove the explicit hatch parameter. The code is simpler without and there's no reason to name this explicitly, but not other style parameters.
| else: | ||
| colors = (axes._get_lines.get_next_color() for _ in y) | ||
| if colors is None: | ||
| colors = [axes._get_lines.get_next_color() for _ in y] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed this from a generator to a list because to_rgba_array needs something with a __len__.
…ther plotting methods Co-authored-by: Ilakkuvaselvi Manoharan <ilakkmanoharan@gmail.com>
cf5016c to
db8bfba
Compare
478264e to
ab39326
Compare
lib/matplotlib/axes/_axes.py
Outdated
| for i, (hs, label, color) in enumerate(zip(heights, labels, colors)): | ||
| lefts = (group_centers - 0.5 * group_distance + margin_abs | ||
| + i * (bar_width + bar_spacing_abs)) | ||
| if orientation == "vertical": | ||
| bc = self.bar(lefts, hs, width=bar_width, align="edge", | ||
| label=label, color=color, **kwargs) | ||
| label=label, color=color, **next(kwargs_gen)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pattern is not ideal. It splits the iteration. Part of the advancement is done in for ... in ..., part is done through the next(). The kwargs_gen should go into the zip.
Also: Slight concern but probably ok for now: iterate_styles() is a bit invasive in that it handles also the non-style kwargs. After calling iterate_styles() the kwargs are folded into to generator and kwargs should likely not be used any further. In particular this means (1) all kwarg prepocessing has to be done before iterate_styles(), (2) we could not have another iterate_something() in the future.
The alternative would be something like
style_kwargs, kwargs = _style_helpers.extract_style_kwargs(kwargs)
...
for i, (hs, label, color, style_kw) in enumerate(zip(heights, labels, colors, style_kwargs)):
...
bar(..., color=color, **style_kw, **kwargs)where we could still decide whether we extract constant style parameters and turn them into an iterator or leave them in kwargs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So more like what I did in my first commit? 🙃
| ax.stackplot(x, y1, y2, | ||
| facecolor=['tab:red', 'tab:blue'], | ||
| edgecolor=['black', 'gray'], | ||
| linestyle=[':', '--'], | ||
| linewidth=[2, 3], | ||
| hatch=['.', '*']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it is structurally reasonable to also iterate edge-related parameters, I don't see a practical use case because the horizontal edges overlap, so that you get no clear picture / artifacts if they are different.
I would only do an example if we can come up with an example that may be actually used in practice.
We also don't have to showcase every aspect of the feature, e.g. different line widths should be extermely rare.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I played around a bit, but didn't produce a plot I actually liked, so just removed it.
This reverts commit ab39326.
timhoffm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Comments are small suggestions.
| The following properties additionally accept a sequence of values | ||
| corresponding to the datasets in *heights*: | ||
| *edgecolor*, *facecolor*, *linewidth*, *linestyle*, *hatch*. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we make this a bit more prominent?
| The following properties additionally accept a sequence of values | |
| corresponding to the datasets in *heights*: | |
| *edgecolor*, *facecolor*, *linewidth*, *linestyle*, *hatch*. | |
| Properties applied to all bars. The following properties additionally | |
| accept a sequence of values corresponding to the datasets in | |
| *heights*: | |
| - *edgecolor* | |
| - *facecolor* | |
| - *linewidth* | |
| - *linestyle* | |
| - *hatch* |
| 'edgecolor': next(edgecolors), | ||
| 'facecolor': next(facecolors), | ||
| }) | ||
| kwargs.update(next(style_gen)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the correct translation keeping existing behavior.
Semi-OT: I'm wondering whether the behavior is logically correct: If patches is empty, we do not advance the style. This means depending on data contents the assignment of styles to datasets may vary.
| ('hatch', ["/", "\\", "."]), | ||
| ('linestyle', ["-", "--", ":"]), | ||
| ('linewidth', [1, 1.5, 2])]) | ||
| def test_style_generator_list(key, value): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| def test_style_generator_list(key, value): | |
| def test_style_generator_list(key, value): | |
| """Test that style parameter lists are distributed to the generator.""" |
or as similar wording.
| ('hatch', "/"), | ||
| ('linestyle', "-"), | ||
| ('linewidth', 1)]) | ||
| def test_style_generator_single(key, value): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| def test_style_generator_single(key, value): | |
| def test_style_generator_single(key, value): | |
| """Test that single-value style parameters are distributed to the generator.""" |
|
|
||
|
|
||
| @pytest.mark.parametrize('key', ['facecolor', 'hatch', 'linestyle']) | ||
| def test_style_generator_empty(key): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| def test_style_generator_empty(key): | |
| def test_style_generator_raises_on_empty_style_parameter_list(key): |
quite verbose, but now exactly telling what we test :)
|
|
||
|
|
||
| def test_style_generator_sequence_type_styles(): | ||
| kw = {'facecolor': ('r', 0.5), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| kw = {'facecolor': ('r', 0.5), | |
| """ | |
| Test that sequence type style values are detected as single value | |
| and passed to a all elements of the generator. | |
| """ | |
| kw = {'facecolor': ('r', 0.5), |
| fcols = ['r', 'b'] | ||
|
|
||
| fig, ax = plt.subplots() | ||
|
|
||
| colls = ax.stackplot(x, y1, y2, facecolor=fcols, colors=['c', 'm']) | ||
| for coll, fcol in zip(colls, fcols): | ||
| assert mcolors.same_color(coll.get_facecolor(), fcol) | ||
|
|
||
| # Plural alias should also work | ||
| colls = ax.stackplot(x, y1, y2, facecolors=fcols, colors=['c', 'm']) | ||
| for coll, fcol in zip(colls, fcols): | ||
| assert mcolors.same_color(coll.get_facecolor(), fcol) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| fcols = ['r', 'b'] | |
| fig, ax = plt.subplots() | |
| colls = ax.stackplot(x, y1, y2, facecolor=fcols, colors=['c', 'm']) | |
| for coll, fcol in zip(colls, fcols): | |
| assert mcolors.same_color(coll.get_facecolor(), fcol) | |
| # Plural alias should also work | |
| colls = ax.stackplot(x, y1, y2, facecolors=fcols, colors=['c', 'm']) | |
| for coll, fcol in zip(colls, fcols): | |
| assert mcolors.same_color(coll.get_facecolor(), fcol) | |
| facecolors = ['r', 'b'] | |
| fig, ax = plt.subplots() | |
| colls = ax.stackplot(x, y1, y2, facecolor=facecolors, colors=['c', 'm']) | |
| for coll, fcol in zip(colls, facecolors): | |
| assert mcolors.same_color(coll.get_facecolor(), facecolors) | |
| # Plural alias should also work | |
| colls = ax.stackplot(x, y1, y2, facecolors=facecolors, colors=['c', 'm']) | |
| for coll, fcol in zip(colls, facecolors): | |
| assert mcolors.same_color(coll.get_facecolor(), facecolors) |
We can affort the additional letters, which IMHO makes this easier to read (In particular, fcols is prone to misinterpretation because (1) we have colls here and (2) col(s) is typically used as abbreviation for columns.
PR summary
hist,stackplotandgrouped_barall loop through datasets and call another plotting method repeatedly. This means they have a common requirement to support sequences for parameters such as facecolor and hatch.histalready has handling for this, which I have factored out into a new helper function. I wasn't sure where to put the helper, so for now it is in its own new private module. I tried putting it in_axes,_apiandcbookbut all those options gave me circular import problems. The tests from #29719 are now reimagined as cheaper tests on the helper.Using the helper in
stackplotcloses #30804.Using the helper in
grouped_barcloses #30673, so this would supersede #30726. I copied thegrouped_bartest from there so added @ilakkmanoharan as a co-author. Note that, contrary to agreement in #30726, this does support a single hatch string forgrouped_bar. I still don't think a single hatch makes sense for any of these methods, but it is already supported for bothhistandstackplotand I do not feel strongly enough to deprecate the existing support or to throw a special case exception ingrouped_bar.grouped_barexample usage:Note the helper does not handle color because the methods already have bespoke handling for colors. E.g.
stackplotmaps colors to facecolor,histwith "step" type maps colors to edgecolor. What should happen if color is in**kwargsprobably needs individual consideration for each method.PR checklist