matplotlib / matplotlib

matplotlib: plotting with Python
https://matplotlib.org/stable/
19.93k stars 7.56k forks source link

Surprising behavior of hatches in fill_between #11418

Open mwaskom opened 6 years ago

mwaskom commented 6 years ago

Bug report

Hatches are invisible in the output of ax.fill_between when color is specified. They are present when color is absent or when facecolor is used.

I found https://github.com/matplotlib/matplotlib/issues/9894 which might be related, but seems to be a much narrower

Code for reproduction

import matplotlib.pyplot as plt

f, ax = plt.subplots()
ax.fill_between([0, 1], [0, 1], hatch="//")
f.savefig("test1.png")

f, ax = plt.subplots()
ax.fill_between([0, 1], [0, 1], hatch="//", color="g")
f.savefig("test2.png")

f, ax = plt.subplots()
ax.fill_between([0, 1], [0, 1], hatch="//", facecolor="g")
f.savefig("test3.png")

Actual outcome

test1.png: test1

test2.png: test2

test3.png: test3

Expected outcome

I expected test2.png to have black hatch marks. I think what's happening is that the color= kwarg is being picked up globally by the artist and so the hatches in test2.png are there, they just have the same green color as the background. Unfortunately I don't think any hatch metadata other than the string code gets added to the artist, so I can't check.

Having color= globally change both the face and edge color of the artist makes a certain amount of sense, and I think the hatches are mostly inheriting their attributes from the kwargs that set the edge aesthetics. So I think I understand what's happening, but it took me a long time to figure out why the hatch argument had, apparently, no effect. I'll also note that the hatch demo is using the color= kwarg, but with ax.bar.

Matplotlib version

This is a fresh install from conda to test, but I also saw it on matplotlib 2.2.0.

mwaskom commented 6 years ago

More surprising behavior: whether or not the hatches inherit the alpha= from the main artist varies across output formats. This is consistent across the three examples above but does reveal that the hatches are indeed present but invisible when specifying color:

f, ax = plt.subplots()
ax.fill_between([0, 1], [0, 1], hatch="//", color="g", alpha=.5)
f.savefig("test2.png")
f.savefig("test2.pdf")

test2.png: test2

test2.pdf: screenshot 2018-06-11 14 01 34

jklymak commented 6 years ago

I think the work around is to call fill_between twice, once for the hatch and once for the facecolor.. I imagine an API proposal for how to make this controlable from the API would be welcome. Note the hatch.linewidth and hatch.color rcParams. You could probably also change those on the fly, though that is kludgey as well.

mwaskom commented 6 years ago

To be clear, I can accomplish what I want perfectly fine by using facecolor, but that wasn't obvious and using color just made it seem like hatch was broken somehow.

Short of adding a whole new set of kwargs that specifically pertain to hatches, I'm not exactly sure how to "solve" the first issue. But it's possible that a situation where the user specifies a hatch and color but not facecolor or edgecolor would be a reasonable place to fire a warning, as (I think?) we can be reasonably certain that the user doesn't mean to be doing that.

The second issue actually seems more like an actual bug?

jklymak commented 6 years ago

Right, but sometimes folks want to color the hatch as well, vs using the default color. Calling twice is the workaround for that.

The second issue seems to definitely be a bug... Adding a pdf tag.

mwaskom commented 6 years ago

Right, but sometimes folks want to color the hatch as well, vs using the default color. Calling twice is the workaround for that.

You can also accomplish that with edgecolor (but there's no way to get three distinct colors for the face, edges, and hatches; edgecolor overrides the hatch.color rcParam).

jklymak commented 6 years ago

Ah, OK, I see. Probably rare that someone wants the edge and the hatch to be different, so thats probaby a decent API.

Tracking the docs through the color kwarg got me to: https://matplotlib.org/api/collections_api.html#matplotlib.collections.Collection.set_color which is pretty explicit that it'll set the edge and face colors. Where else in the docs should this be specified? FWIW, I'd personally just get rid of the color kwarg, but they outcry would be great!

mwaskom commented 6 years ago

Please do not get rid of the color kwarg! It is essential for higher-level APIs like seaborn.FacetGrid.

mwaskom commented 6 years ago

If you want to solve this with docs changes then I would suggest more clearly documenting how hatching works. AFAICT there's basically only the hatch demo, which demonstrates (without explication) a usage pattern that produces invisible hatches with fill_between, and then a subsection of the 2.0 release notes that documents how hatches get (some) customization by using the specified edge attributes.

ImportanceOfBeingErnest commented 6 years ago

a situation where the user specifies a hatch and color but not facecolor or edgecolor would be a reasonable place to fire a warning, as (I think?) we can be reasonably certain that the user doesn't mean to be doing that.

This is the situation and it gives a perfectly expected result.

rect = plt.Rectangle((.2,.2),.6,.6, color="g", hatch="\\", fill=False)
plt.gca().add_patch(rect)

image


Probably rare that someone wants the edge and the hatch to be different, so thats probaby a decent API.

This is not rare at all. You would e.g. want a black border and differently colored hatches/faces in a bar plot. Currently it's only solvable by creating the same plot twice.


In general hatching is very underdevelopped. At one point @tacaswell mentionned that creating a MEP for it would be good; but I guess I forgot about it.

mwaskom commented 6 years ago

This is the situation and it gives a perfectly expected result.

Technically this does involve the user specifying the facecolor because setting fill=False changes its alpha channel:

rect = plt.Rectangle((0, 0), 1, 1, color="b", fill=False)
print(rect.get_facecolor())
print(rect.get_edgecolor())
(0.0, 0.0, 1.0, 0)
(0.0, 0.0, 1.0, 1)

It's also worth noting that it is possible to get a UserWarning in a similar context:

UserWarning: Setting the 'color' property will override the edgecolor or facecolor properties

jklymak commented 6 years ago

In general hatching is very underdevelopped. At one point @tacaswell mentionned that creating a MEP for it would be good; but I guess I forgot about it.

We have a pretty big API that covers some rare use-cases. i.e. you could imagine a class for hatches where the hatch was an object that you manipulated. But we always have to balance if that extra API is really necessary given the occasional inconvenince of having to do things manually.

tacaswell commented 6 years ago

For some fields hatching is not a rare use case (given how quickly it was reported when we broke some things about hatching in 2.0) in some fields.

You can also accomplish that with edgecolor (but there's no way to get three distinct colors for the face, edges, and hatches; edgecolor overrides the hatch.color rcParam).

Correct, the work to thread the hatch color through the API to be on the same footing as edge color and face color needs to be done. This includes both the set side to thread it through the plotting methods and the Artists and the backend side so that all of the backends that we ship respect the hatch color correctly.

As @ImportanceOfBeingErnest mentioned I commented else where this should get a MEP / some planning before the works get started and my rough estimate is that his would take a few weeks of full time effort by someone who knows the MPL code base to implement.

ImportanceOfBeingErnest commented 6 years ago

I don't know about any big API. Hatching is essentially done on the artist level. There is currently no way to set the hatch linewidth, the hatch density (other than some divisor of some obscure default unit cell), the hatch alpha or the hatch angle.

So, yes, independent on the issue here, there is a need for a hatch API.


Coming back to the real issue here, I actually observe a difference in the facecolor, with or without hatching being used. Using matplotlib 2.2.2 and Qt4Agg as well as matplotlib master(from 2 days ago) and Qt5Agg.

f, (ax, ax2) = plt.subplots(ncols=2, figsize=(4,2))
ax.fill_between([0, 1], [0, 1], color="g", alpha=.5)
ax2.fill_between([0, 1], [0, 1], hatch="//", color="g", alpha=.5)
f.savefig("test2.png")
f.savefig("test2.pdf")
f.savefig("test2.svg")

png

test2

pdf

svg

image

It seems in the pdf file output also depends on the viewer. In Foxit Reader, no alpha is shown.
In the svg file the hatching is missing but alpha is correctly applied to the face, not the hatch.

To make things even more weird, using facecolor instead of color in the above I get

png

test2

pdf

svg

image

Here, the png shows a black hatching, while in the svg alpha seems to be applied to the hatch as well. In pdf it again depends on the viewer, Foxit shows black hatching, Acrobat shows hatching with alpha - however the result is still different compared to the color case.

In total there is not a single matching case here. I would say one needs to define what the expected outcome for each of the combinations of color, facecolor, edgecolor really should be.

mwaskom commented 6 years ago

Yikes!

mwaskom commented 6 years ago

So, if I can summarize:

  1. Part of this should be (narrowly but easily) addressed with some better documentation of how hatching interactions with other artist aesthetics.
  2. Part of this could be additionally addressed by adding a warning when a user is generating a collection with "invisible" hatches
  3. The ultimate solution is to develop a first-class API for controlling hatch aesthetics, but that will require some substantial design and implementation effort
  4. There are some problems with how different backends/writers (not 100% sure of the distinction) render hatches that should be fixed independent of what the user-facing API is.

Do you agree?

WeatherGod commented 6 years ago

I agree with 1, 3, and 4. I think 2 might be a bit of a red herring. We should make sure that the API does not lead one to do actions that don't make sense. Furthermore, we currently don't warn for other decisions like plotting something outside the plotting area. So, I would only agree to point 2 if we determine that such scenarios are unavoidable and can implicitly happen even after improving the API.

On Tue, Jun 12, 2018 at 9:51 AM, Michael Waskom notifications@github.com wrote:

So, if I can summarize:

  1. Part of this should be (narrowly but easily) addressed with some better documentation of how hatching interactions with other artist aesthetics.
  2. Part of this could be additionally addressed by adding a warning when a user is generating a collection with "invisible" hatches
  3. The ultimate solution is to develop a first-class API for controlling hatch aesthetics, but that will require some substantial design and implementation effort
  4. There are some problems with how different backends/writers (not 100% sure of the distinction) render hatches that should be fixed independent of what the user-facing API is.

Do you agree?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/matplotlib/matplotlib/issues/11418#issuecomment-396596338, or mute the thread https://github.com/notifications/unsubscribe-auth/AARy-MKufI4arRPH9XChfgRlscPiNXWFks5t78dPgaJpZM4UjIqJ .

mwaskom commented 6 years ago

I think 2 might be a bit of a red herring. We should make sure that the API does not lead one to do actions that don't make sense.

Absolutely. But in the meantime, the current approach is basically a hack that requires some knowledge of the implementation details; doing the most obvious thing fails in a confusing way. First-class support for hatching would be great, but also seems like a lot of work and I haven't seen anyone commit to it. So in the meantime, it might be helpful to give users some more guide rails. With that said I think that being conservative about issuing warnings is usually the right policy so I'm not going to push for it.

I think I can find time to make some small additions to the hatch demo and solve (1).

mwaskom commented 6 years ago

Here's another fun one: it doesn't seem possible to set hatch.linewidth on the fly using a with statement, but hatch.color works fine.

jklymak commented 6 years ago

I think I agree that it’d be nice if we had a big API for hatching/stippling. Not going to happen in next 1.5 weeks, and is a bit of an API argument about how to handle. However, I expect we need a FillStyle object similar to a Color object and allow folks to specify that instead of the string shortcuts (which would of course generate a set of default FillStyle objects).

Not sure if facecolor should be part of the FillStyle. naively, that seems the most complete way to do things.

github-actions[bot] commented 1 month ago

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!