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

First attempt for individual hatching styles for stackplot #27158

Merged
merged 1 commit into from
Oct 31, 2023

Conversation

nbarlowATI
Copy link
Contributor

@nbarlowATI nbarlowATI commented Oct 20, 2023

PR summary

Closes #27146 this modifies stackplot.py to treat the hatch argument to stackplot in the same way as colors, i.e. if a list is provided, it will cycle through those hatch styles for the different data in the stack.
I believe the existing behaviour is preserved - if a single style (e.g. "x") is provided, this will be used for all elements of the stack, while if no "hatch" argument is passed, it will apply hatch style " " to all elements (though perhaps this isn't the best way of doing this?)

Using the example code in the original Issue from @pawjast :

fig, ax = plt.subplots(
    figsize=(10,6),
    facecolor="white",
    layout="constrained"
)
fig.suptitle(
    "with hatching",
    fontsize=25,
    weight="bold"
)

x = range(data.shape[1])
ax.stackplot(
    x, data,
    hatch=["x", "\\", "//", "-"]
)

ax.set_xlim(0, 9)
ax.set_ylim(0, 350)

I get the following:
Figure_1

Let me know if this looks OK, and if so I can make a start on updating documentation and tests.

PR checklist

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks.

You can also join us on gitter for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide

We strive to be a welcoming and open project. Please follow our Code of Conduct.

@rcomer
Copy link
Member

rcomer commented Oct 20, 2023

Thanks for the fast work @nbarlowATI and for checking this works for the example in the issue. I'm unsure whether passing a single string will work at the moment. Could you check what happens if you pass hatch="\\"?

This will also need some tests. Please see here for general guidance on testing. You might also like to look at the tests added at #24470, where a similar change was made for the pie method.

Please mark this as "ready for review" when you are ready (things marked as draft often get overlooked). In the meantime, if you need help, feel free to ask questions here. Or you may prefer to ask them in our incubator gitter room.

@rcomer rcomer added this to the v3.9.0 milestone Oct 20, 2023
@@ -76,6 +83,14 @@ def stackplot(axes, x, *args,
else:
colors = (axes._get_lines.get_next_color() for _ in y)

if hatch:
if isinstance(hatch, Iterable):
Copy link
Member

Choose a reason for hiding this comment

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

be careful because hatches can be multi-char strings, and strings are Iterable.

I think what you may want is

if isinstance(hatch, (str, None)):
    hatch = itertools.cycle([hatch])
else:
    hatch = itertools.cycle(hatch)

I think that should handle even the None case (also " " is not a valid hatch pattern and is causing warnings)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, and good point! You're right about the strings being iterables, and I think your logic is the right choice (though I had to modify slightly because None doesn't count as a type so can't be used in isinstance).

Copy link
Member

Choose a reason for hiding this comment

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

Ahh, str | None would work (but is py3.10+ only so we can't use it).

builtins.NoneType or type(None) would also work, but is used less commonly.

Type hints allow None as shorthand, but not isinstance, apparently.

@story645
Copy link
Member

story645 commented Oct 20, 2023

It'll also need a whats new example - what you have here is fine and you can also use the what's new in #24470 as an example.

ETA: also I'm psyched, this is great and we should vectorize all the things 😄

@pawjast
Copy link

pawjast commented Oct 22, 2023

I was thinking perhaps this change is a good chance to introduce the following API (provided this is clear and acceptable):

  • ax.stackplot(hatch="x"): single str gives result as we currently have (one big hatching over all data)
  • ax.stackplot(hatch=["x"]): one str defined in a list will use this one str and vary the density (like what I showed in the issue) so e.g. if there are 4 datasets in the data, the hatchings should be x, xx, ... xxxx
  • ax.stackplot(hatch=["x", "\\", "//", "-"]): list of str, the code cycles through the list

@story645
Copy link
Member

story645 commented Oct 22, 2023

ax.stackplot(hatch=["x"]): one str defined in a list will use this one str and vary the density (like what I showed in the issue) so e.g. if there are 4 datasets in the data, the hatchings should be x, xx, ... xxxx

I understand why you're asking for this but I'm concerned this is a bit magic cause this is not a thing we do for any variables (list of color vary by density). Also if there was support for this, it'd have to work for all the places that take a list of hatches.

And I wonder if this would be better achieved via custom hatch objects?

hatch=DensityHatch('x') which returns an iterator of increasing density - which actually may work currently given our API doesn't really care what type of iterator hatch is?

@pawjast
Copy link

pawjast commented Oct 23, 2023

ax.stackplot(hatch=["x"]): one str defined in a list will use this one str and vary the density (like what I showed in the issue) so e.g. if there are 4 datasets in the data, the hatchings should be x, xx, ... xxxx

I understand why you're asking for this but I'm concerned this is a bit magic cause this is not a thing we do for any variables (list of color vary by density). Also if there was support for this, it'd have to work for all the places that take a list of hatches.

And I wonder if this would be better achieved via custom hatch objects?

hatch=DensityHatch('x') which returns an iterator of increasing density - which actually may work currently given our API doesn't really care what type of iterator hatch is?

No problem. I was just throwing the idea for the debate.

I'm very much in favour of continuous improvement so even if there's a chance my proposal would be considered for implementation but hindered the current work, it's probably best to get out the current change (support for list of hatches) and then go back to the other ideas in the next iterations...

@nbarlowATI nbarlowATI marked this pull request as ready for review October 24, 2023 16:42
@nbarlowATI
Copy link
Contributor Author

I added a test in test_axes.py that compares a stackplot with a list of hatching styles with the result of usingfill_between with the same data and hatching styles.
I also added a "what's new" entry doc/users/next_whats_new/stackplot_hatch.rst that demonstrates the use of the hatch list.
Happy to add any other tests and/or examples that people think would be useful (now I'm getting the hang of this :) )

@story645
Copy link
Member

last thing left is fixing the stubtest by adding this variable - which I think is None | List[str] | str ? - to the type stub

@@ -3792,12 +3792,15 @@ def spy(

# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
Copy link
Member

Choose a reason for hiding this comment

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

As the comment says, the signatures here are autogenerated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes, sorry! I think I ran boilerplate.py myself in the hope of fixing the stub github action, as I didn't know about the .pyi file... Should I revert this commit, or will the autogeneration take care of it anyway?

Copy link
Member

Choose a reason for hiding this comment

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

I think you are supposed run boilerplate.py yourself, and this comment is meant to indicate that you shouldn't manually edit the type hints here.

Copy link
Member

@story645 story645 Oct 25, 2023

Choose a reason for hiding this comment

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

🤦‍♀️ I think Ruth is right given I edited this file in my PR too and we never call boilerplate.py as part of our build/install process. Sorry & I'll open up a follow up issue on documenting how this is used!

lib/matplotlib/stackplot.py Outdated Show resolved Hide resolved
lib/matplotlib/stackplot.py Outdated Show resolved Hide resolved
Comment on lines 59 to 60
A sequence of hatching styles (see reference in
https://matplotlib.org/devdocs/gallery/shapes_and_collections/hatch_style_reference.html)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
A sequence of hatching styles (see reference in
https://matplotlib.org/devdocs/gallery/shapes_and_collections/hatch_style_reference.html)
A sequence of hatching styles. See :ref:`sphx_gallery_shapes_and_collections_hatch_style_reference.py

Personal bias against parentheticals & we always cross-reference when possible so that the link goes to the tutorial in the same version of the docs
https://sphinx-gallery.github.io/stable/advanced.html#cross-referencing

But also double check that link by building the doc cause I may have done something wonky

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK makes sense. For me, the above ':ref:' link didn't quite work in my locally built version of the docs, but I copied something very similar (using ':doc:') from the pie chart docstring, that seems to work.

Copy link
Member

@story645 story645 Oct 26, 2023

Choose a reason for hiding this comment

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

thank you for your patience w/ my "oh, another thing" 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No problem (I'm a big fan of Columbo!), and thanks for your help! :)

Copy link
Member

@story645 story645 left a comment

Choose a reason for hiding this comment

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

Yay, it's got code and tests and typing and a whats new 🥳

Slight optional wording suggestion, but I think it's good to go. Also do you want to rebase or do you want one of us to?

doc/users/next_whats_new/stackplot_hatch.rst Outdated Show resolved Hide resolved
@nbarlowATI
Copy link
Contributor Author

Thanks! Re: rebasing, it would be great if one of you could do it - I'm away for the next few days (but I could also do it when I'm back...)

Copy link
Member

@rcomer rcomer left a comment

Choose a reason for hiding this comment

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

Thanks for your work on this @nbarlowATI. I just have one minor comment.

lib/matplotlib/stackplot.py Outdated Show resolved Hide resolved
@rcomer
Copy link
Member

rcomer commented Oct 28, 2023

This will also need squashing down to one commit if you are confident to do so, otherwise we can handle that on merge.

Edit: oops I see you already discussed that.

@nbarlowATI
Copy link
Contributor Author

Hi, I'm back online now, and happy to have a go at the rebase. Just to confirm, is the idea to both:

  • Rebase such that the commits here appear after the current state of main, i.e. git rebase upstream/main
  • Squash all the commits related to this change into one, using git rebase -i
    ?
    (Or is there a single rebase command that does both of these?)
    If so, I can give it a go.

@rcomer
Copy link
Member

rcomer commented Oct 31, 2023

Squash all the commits related to this change into one, using git rebase -i

This one please! Since there are no conflicts between your branch and upstream/main, the ordering of your commits vs upstream/main's commits do not matter.

We have some guidance here if you need it
https://matplotlib.org/devdocs/devel/development_workflow.html#rewrite-commit-history

@story645
Copy link
Member

Don't add complication you don't need of course, but git rebase -i upstream/main is the command I always use.

@nbarlowATI
Copy link
Contributor Author

Many thanks @rcomer and @story645 for all your help with this!!!
I believe the commits are now successfully squashed into one.

Copy link
Member

@rcomer rcomer left a comment

Choose a reason for hiding this comment

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

Thanks @nbarlowATI and congratulations on your first PR in Matplotlib! We hope to hear from you again.

@rcomer rcomer merged commit 5452efc into matplotlib:main Oct 31, 2023
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging this pull request may close these issues.

[ENH]: Multi hatching in ax.stackplot()
5 participants