Skip to content

Deprecate savefig.facecolor, savefig.edgecolor, and savefig.transparent. #10023

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

Closed
wants to merge 1 commit into from

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Dec 16, 2017

They cause a number of complications for print_figure (which needs to
temporarily switch the figure and axes face and edge colors) that are
unnecessary (the user can directly set the color they wish (including
transparent) on the figure patch instead; an rcParam is even available
for that purpose).

xref #9080 (fixed, but discussion still relevant), #9921.

PR Summary

PR Checklist

  • Has Pytest style unit tests
  • Code is PEP 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@anntzer anntzer force-pushed the deprecate-savefig-colors branch from f2bb690 to 8d74de0 Compare December 16, 2017 09:23
@anntzer anntzer force-pushed the deprecate-savefig-colors branch 3 times, most recently from 34b8bfc to f7e0424 Compare December 16, 2017 22:03
@jenshnielsen
Copy link
Member

When these are finally removed it's no longer possible to define a style with a on display figure facecolor that is different from the saved one right? So this will change the on-display of classic style to no longer have a gray facecolor?

@tacaswell tacaswell added this to the v2.2 milestone Dec 19, 2017
@jklymak
Copy link
Member

jklymak commented Dec 19, 2017

@jenshnielsen Are you advocating for the ability to have a style like that?

I wonder if a savefig.style rcParam is something that would be easy to impliment. i.e. when savefig is called, the figure gets drawn in a different style context. That would be less of a "special case" bunch of code.

@anntzer
Copy link
Contributor Author

anntzer commented Dec 19, 2017

Right now styles are only applied at figure/axes creation time, not at draw time, so savefig.style wouldn't help (what you'd really need is artist style sheets, but that's a much more complicated project).

The "classic" style can be recovered by setting the patch background to transparent and the widget background to gray (see #9698 where I set it to white). This admittedly requires backend-dependent code, but I always assumed that the reason for the gray background in the classic style was exactly due to trying to match the widget color... In any case I agree it is less simple, and thus a tradeoff to consider.

@jklymak
Copy link
Member

jklymak commented Dec 19, 2017

The gray background was more than likely because that’s what matlab did. But I don’t think that’s a good reason for us to carry this millstone around.

@WeatherGod
Copy link
Member

WeatherGod commented Dec 19, 2017 via email

Copy link
Member

@jklymak jklymak left a comment

Choose a reason for hiding this comment

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

This all seems reasonable to me. I didn't test all the deprecation statements work - hopefully not too negligent of me.

@anntzer anntzer force-pushed the deprecate-savefig-colors branch from f7e0424 to ff4e297 Compare December 21, 2017 05:35
@anntzer anntzer force-pushed the deprecate-savefig-colors branch from ff4e297 to b6ebe16 Compare January 15, 2018 03:40
@anntzer
Copy link
Contributor Author

anntzer commented Jan 23, 2018

fwiw the widget color can be set in a backend dependent fashion (except for gtk2, which I haven't tested, but should be easy too?) as follows:

from pylab import *

rcParams["figure.facecolor"] = (0, 0, 0, 0)
if "qt" in get_backend().lower():
    gcf().canvas.setStyleSheet("QWidget {background: red;}")
elif "gtk" in get_backend().lower():
    from gi.repository import Gtk
    css = Gtk.CssProvider()
    css.load_from_data(b"* {background-color: red;}")
    gcf().canvas.get_style_context().add_provider(
        css, Gtk.STYLE_PROVIDER_PRIORITY_USER)
elif "tk" in get_backend().lower():
    gcf().canvas._tkcanvas.configure(background="red")
elif "wx" in get_backend().lower():
    gcf().canvas.SetBackgroundColour("red")
gca()
show()

(replace red by your favorite color, of course)
For gtk3{agg,cairo}, #10297 and #10298 need to be merged first.

They cause a number of complications for print_figure (which needs to
temporarily switch the figure and axes face and edge colors) that are
unnecessary (the user can directly set the color they wish (including
transparent) on the figure patch instead; an rcParam is even available
for that purpose).
@anntzer anntzer force-pushed the deprecate-savefig-colors branch from b6ebe16 to b20fd2f Compare January 23, 2018 03:22
@tacaswell tacaswell modified the milestones: v2.2, v3.0 Jan 23, 2018
@tacaswell
Copy link
Member

punted to 3 as I think this needs a bit more thought about consequences.

@ImportanceOfBeingErnest
Copy link
Member

So if you have a white figure and want to save it transparently instead of

plt.savefig("name", transparent=True)
plt.show()

you would need to write

ofc = plt.gcf().get_facecolor()
ofca = []
plt.gcf().set_facecolor((1,1,1,0))
for ax in plt.gcf().axes:
    ofca.append(ax.get_facecolor())
    ax.set_facecolor((1,1,1,0))
plt.savefig("name")
plt.gcf().set_facecolor(ofc)
for ax,fc in zip(plt.gcf().axes):
    ax.set_facecolor(fc)
plt.show()

Or, inversely, if all figures are transparent by default and you wanted to have a white background, instead of

plt.savefig("name", facecolor="w")
plt.show()

you would need

ofc = plt.gcf().get_facecolor()
ofca = []
plt.gcf().set_facecolor("w")
for ax in plt.gcf().axes:
    ofca.append(ax.get_facecolor())
    ax.set_facecolor("w")
plt.savefig("name")
plt.gcf().set_facecolor(ofc)
for ax,fc in zip(plt.gcf().axes):
    ax.set_facecolor(fc)
plt.show()

Is it 9 extra lines for saving a simple figure or am I missing something here?

@anntzer
Copy link
Contributor Author

anntzer commented Apr 1, 2018

We could definitely come up with a better API to do these manipulations (and even make sure that they are included before removing the functionality from savefig), e.g.

with force_style({"figure.background": "w"}): fig.savefig(...)

etc. (yes, the style is currently not applied to existing figures).

The point is that it doesn't really make sense to duplicate all the figure-and-axes manipulation API as keywords to savefig.

Edit: or

with properties_cm(fig, facecolor=...): fig.savefig(...)

(where properties_cm is like setp, but as a contextmanager) which works around the delayed style application problem.

@tacaswell tacaswell modified the milestones: v3.0, v3.1 Jul 15, 2018
@anntzer
Copy link
Contributor Author

anntzer commented Jan 3, 2019

See also #7619 for someone being confused by the duplication.

@jklymak
Copy link
Member

jklymak commented Feb 10, 2019

@anntzer is this still something you want to pursue? Not entirely sure a new context manager is any better than just passing kwargs to fig.savefig(), and I guess I agree w @ImportanceOfBeingErnest that this deprecation would make for a lot of user pain if they need to change things at print time. Though counter to that, I'm not sure why folks want to change colors etc just for saving the figure.

@jklymak jklymak modified the milestones: v3.1.0, v3.2.0 Feb 10, 2019
@ImportanceOfBeingErnest
Copy link
Member

If you don't buy the above arguments, here is another one: In Jupyter/IPython using the inline backend we can easily display the figures with a different background via

%config InlineBackend.print_figure_kwargs={"facecolor" : "#aaaaaa"}

if you then want to save it you will still want a white background mostly.

So let's not break all the people's notebooks which use a different color than white as background.

@anntzer
Copy link
Contributor Author

anntzer commented Feb 11, 2019

It's not clear how

%config InlineBackend.print_figure_kwargs={"facecolor" : "#aaaaaa"}

is different from setting the rcParam (without checking I'd guess that it's redundant with what the rcParam offers; this kind of duplication seriously annoys me :/), so it'd go through the same deprecation.

@ImportanceOfBeingErnest
Copy link
Member

Sorry, if that wasn't clear. So there is no difference between

%matplotlib inline
%config InlineBackend.print_figure_kwargs={"facecolor" : "#aaaaaa"}
# ... create figure etc.
fig.savefig("myplot.png")

and

%matplotlib inline
plt.rcParams["figure.facecolor"] = "#aaaaaa"
# ... create figure etc.
fig.savefig("myplot.png", facecolor="white")

Both will create grey background figures in the notebook, and save figures with white background.

But this PR intends to remove the facecolor argument. So neither of the above would be possible anymore. In addition, every notebook that relies on the above (e.g. through themes or by manually setting the print_figure_kwargs) would show a deprecation warning in each notebook cell that contains a figure.

@anntzer
Copy link
Contributor Author

anntzer commented Feb 11, 2019

Not supporting different "displaying" and "saving" background colors is indeed the intent of this PR, and like any other backcompat break, yes, some people will be annoyed by it.
As noted in the discussion above there are still cases where the intended functionality is broken (#9921) and people who are confused by it (#7619).
Just like @jklymak above (and to quote him), "I'm not sure why folks want to change colors etc just for saving the figure."
Note that the use case in #10023 (comment) can (to a certain extent) be covered by just setting the correct colors when creating the Figure/Axes (obviously, this means having them at display-time too).

@jklymak
Copy link
Member

jklymak commented Jul 24, 2019

@anntzer I think this is a good idea in general. We have too many ways to achieve the same thing. But it seems like it needs more discussion, and probably more use cases carefully laid out. I'm going to close this old PR, and suggest we open an issue. If you feel this is something we should pursue, after a bit more documentation of the plan we could discuss on a call? Of course, feel free to re-open if you strongly feel this is the way to go, but I don't think we have enough consensus yet.

@jklymak jklymak closed this Jul 24, 2019
@anntzer
Copy link
Contributor Author

anntzer commented Jul 24, 2019

I agree this is basically stuck needing more discussion for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants