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

Add easy style sheet selection #2236

Merged
merged 36 commits into from Nov 18, 2013
Merged

Add easy style sheet selection #2236

merged 36 commits into from Nov 18, 2013

Conversation

tonysyu
Copy link
Contributor

@tonysyu tonysyu commented Jul 21, 2013

Add easy switching between rcParams based on the implementation in mpltools.style. Basically, just call

from matplotlib import style
style.use('ggplot')

... to switch to a style sheet that sort of mimics ggplot's style.

Notes:

  • In the current implementation, shipped style files are stored in matplotlib/style/stylelib and user files are stored in ~/.matplotlib/stylelib.
  • I chose *.mplrc as an extension for the style files, but I'm fine with changing that. Style files in the style libraries have the extension *.style *.mplstyle.
  • Ideally there would an rc parameter (or some other mechanism) to easily add search paths for style files
  • One thing I liked in the original implementation was the ability to chain style sheets, with each style sheet adding the parameters they set. The current implementation doesn't work like this because rc_params_from_file initializes the default rcParams and then updates the defaults from the file. Thus, updating using the result from rc_params_from_file will overwrite all values.

@dpsanders
Copy link

Fantastic news! - many thanks for this. 💯
Apologies for not pulling this in myself -- I am still getting to grips with the code.
(But glad that my "gentle" prodding worked ;) )

I suggest .style as the extension for the style files.

@dpsanders
Copy link

I merged your branch and installed matplotlib, but there is no style submodule available to import from matplotlib.

@tonysyu
Copy link
Contributor Author

tonysyu commented Jul 21, 2013

@dpsanders Most likely I should have added the style package to some list for installation. One of the matplotlib devs will have to enlighten me on where this would be.

BTW, it sounds like you might want to tweak your git workflow. You don't need to merge my branch to test it out (unless by merge, you meant pull). Just checkout the branch and test on there (never merge into master---only pull from the "official" master). This might be helpful:

https://gist.github.com/piscisaureus/3342247

Also, if you run python setup.py develop, then you can avoid re-installing (unless there are compiled extensions) when testing out different branches.

@dpsanders
Copy link

On Sun, Jul 21, 2013 at 11:14 AM, Tony S Yu notifications@github.comwrote:

@dpsanders https://github.com/dpsanders Most likely I should have added
the style package to some list for installation. One of the matplotlib
devs will have to enlighten me on where this would be.

I am a novice at this stuff, but a quick Google threw up this, which seems
to be the answer:

http://stackoverflow.com/questions/16020725/python-setup-install-one-module-as-a-sub-module-of-another-module

I'll give it a go

BTW, it sounds like you might want to tweak your git workflow. You don't
need to merge my branch to test it out (unless by merge, you meant pull).
Just checkout the branch and test on there (never merge into master---only
pull from the "official" master). This might be helpful:

https://gist.github.com/piscisaureus/3342247

Also, if you run python setup.py develop, then you can avoid
re-installing (unless there are compiled extensions) when testing out
different branches.

Ah, many thanks -- that will make my life much easier :)

D.


Reply to this email directly or view it on GitHubhttps://github.com//pull/2236#issuecomment-21312409
.

Dr. David P. Sanders

Profesor Titular "A" / Associate Professor
Departamento de Física, Facultad de Ciencias
Universidad Nacional Autónoma de México (UNAM)

dpsanders@gmail.com
http://sistemas.fciencias.unam.mx/~dsanders

Cubículo / office: #414, 4o. piso del Depto. de Física

Tel.: +52 55 5622 4965

@ChrisBeaumont
Copy link
Contributor

+1 on this

A few outsider thoughts

  1. It would be easy to extend style.use to accept paths or urls, in addition to style names. Urls would be especially nice, since people could peruse nbviewer/github for good styles, and try them out without remembering the search path for style.use.
  2. Since rcParams is essentially a dict, It would be easy to accept json style sheets (e.g. https://raw.github.com/CamDavidsonPilon/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers/master/styles/bmh_matplotlibrc.json)
  3. Is it already possible to configure MPL to use a style by default? If not, perhaps there should be an rcParams option like 'style.default', to specify this?

@ChrisBeaumont
Copy link
Contributor

One other thought: if I'm parsing this correctly, it looks like styles update the current rcParams settings, but don't touch options that aren't specifically in the style sheet. I guess this enables the "chaining" behavior you talk about, but it also means that the state of rcParams after calling style.use is not always obvious. For example:

use('ggplot')  # image.cmap not modified, = 'jet'
use('greyscale') # image.cmap = 'gray'
use('ggplot') # image.cmap='gray'

It might be nice to have an option to explicitly set unspecified style options to a default (the obvious choice being rcParamsDefault or rcParamsOrig).

if np.isscalar(name):
name = [name]
for s in name:
plt.rcParams.update(library[s])
Copy link
Contributor

Choose a reason for hiding this comment

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

This would benefit from a more helpful error message if s not in library

@tonysyu
Copy link
Contributor Author

tonysyu commented Jul 21, 2013

@ChrisBeaumont Thanks for the feedback!

It would be easy to extend style.use to accept paths or urls, in addition to style names.

That's a great idea. Done!

Since rcParams is essentially a dict, It would be easy to accept json style sheets

I'm going to punt on this idea. It'd be simple to use the json file to update the settings, but ideally, it would be run through the same validation mechanism as normal rc files. Unfortunately, that validation mechanism is a bit tangled with the parser at the moment. I don't think it'd be hard to fix, but I'm not jumping at the opportunity to fix it myself ;)

Is it already possible to configure MPL to use a style by default?

Yeah, I would stick to the standard ~/.matplotlib/matplotlibrc file for this type of thing.

styles update the current rcParams settings, but don't touch options that aren't specifically in the style sheet. I guess this enables the "chaining" behavior you talk about, but it also means that the state of rcParams after calling style.use is not always obvious.

True. You can just call plt.rcdefaults() to do what you're suggesting. I'm thinking about adding style.reset() as an alias, but matplotlib already has too many names for the same thing so I'm hesitant.

@ChrisBeaumont
Copy link
Contributor

Good points.

My main reason for the suggestion about an rcParams-specified default style is to pave the way to change the defaults in MPL -- if users could add a style.default : classic line to their rcParams, then they could maintain a stable set of rcParams even if the defaults change. From discussions on the mailing list, such functionality sounds like a prerequisite for any proposed change to the defaults.

import re

import numpy as np
import matplotlib.pyplot as plt
Copy link
Member

Choose a reason for hiding this comment

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

I hope you don't really need pyplot for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. Fixed.

@efiring
Copy link
Member

efiring commented Jul 21, 2013

On 2013/07/21 10:15 AM, Tony S Yu wrote:

You can just call |plt.rcdefaults()| to do what you're suggesting. I'm
thinking about adding |style.reset()| as an alias, but matplotlib
already has too many names for the same thing so I'm hesitant.

plt.rcdefaults() is part of the present confusing mess, with too many
rc* names that are too hard to remember and that don't clearly indicate
what they are or do. style.reset() is clearer, but I think we need to
think longer term, about how to improve the pyplot layer itself.

@mdboom
Copy link
Member

mdboom commented Jul 22, 2013

Thanks, Tony. This looks quite good. I'm going to second @ChrisBeaumont's suggestion that an rcParam to set the style would be good, to make the transition easier, as suggested. It's a tricky bootstrapping problem, however. If it's applied when it is read in, because then subsequent values in the matplotlibrc file would overwrite the style. Maybe that's correct, but it has potential for surprise and introduces some order-dependence on the rc files that we don't currently have. And then there's what to do with cyclical references to styles. All resolvable problems -- just tricky to get right I suspect.

I don't like style as an extension. It's too generic, and these files are very matplotlib-specific. I'd prefer mplstyle (or something better than that).

I agree that we want two ways of applying styles: one for chaining styles over top of the existing settings, and one that resets and then applies the style. Not sure on the best way to present that.

@efiring: One of the complications with rcParams that has long irked me is that it combines style things with platform configuration. I've often felt that style belongs much closer to the plot -- not really in user-global configuration as we do now -- whereas the platform configuration stuff (which backend to use, and font formats to write out) do belong where they are. I'd love to see this separate out into two separate files (which would also solve the cyclical reference problem above, since the platform config would choose a style, but a style file could not choose another style (perhaps only inherit from another style). I don't think that would be hard to implement, but devising the right way to transition people is the hard part.

@ChrisBeaumont
Copy link
Contributor

@mdboom agreed, the bootstrapping issue seems a little awkward, with three layers to apply (default values, matplotlibrc values, default style). For compatibility, I think this order is needed to start

  1. rcParams populated with rcsetup.defualtParams
  2. matplotlibrc values applied
  3. default style applied in "implicit mode" (i.e. values not explicitly named are not overridden)

For now, the default style is an empty called 'classic'. If the default style changes, people have the option of switching back to 'classic' in matplotlibrc.

Long term, I agree it would be cleaner to pull the visual-level options out of defaultParams, and then load the default style in "explicit mode" (which applies the visual options currently in defaultParams). Likewise, matplotlibrc would not have visual-level options, so styles and matplotlibrc never conflict. The transition period would be awkward, however (you'd have to warn users that matplotlibrc values should not have visual level options, and then load them anyways after the default style is applied in explicit mode).

@@ -0,0 +1,2 @@
from core import *
Copy link
Member

Choose a reason for hiding this comment

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

is there a reason that this file doesn't just contain all the contents of core, instead of wildcard importing them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a matter of style, I guess. I prefer __init__.py modules to be fairly lightweight, but if that's frowned upon, I'm fine with changing it.

Copy link
Member

Choose a reason for hiding this comment

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

also, aren't we doing the dot-style imports now? I also still don't like the import *.

@ivanov
Copy link
Member

ivanov commented Jul 22, 2013

I'm just jumping in here because it was mentioned on twitter, but will it be possible to either make a context manager for this, or re-use the with mpl.rc_context(...) manager for this?

@mdboom
Copy link
Member

mdboom commented Jul 22, 2013

@ivanov: Good idea on the context manager.

Also, as a side note -- we should be able to wrap the xkcd command in this, and it wouldn't be a special one-off function any more.

@tonysyu
Copy link
Contributor Author

tonysyu commented Jul 23, 2013

@ChrisBeaumont, @mdboom I'm convinced: It would be good to make style sheets an rcParam, but in the current state it'll be difficult to validate because of circular imports.

I'm guessing that I need to add the stylelib directory to some sort of package data field in setup.py, but it's not clear to me where.

Updates:

  • I added a context manager as suggested by @ivanov
  • I switched the extension to *.mplstyle.
  • Tests!

@WeatherGod
Copy link
Member

I am not yet convinced that having a style rcParam makes any sense.
Conceptually speaking, the matplotlibrc file is more like the style, and
having multiple versions of these files is like having multiple styles to
choose from. The circular logic involved in implementing a style rcParam
should be a huge red warning sign saying "you are thinking this wrong!".

Instead, why don't we jump on the other idea @mdboom had, which was the
separation of style-type parameters from the non-style type parameters
(like the default backend and such), and use the "style" parameter in that
configuration file to point to the desired configuration file that contains
the style parameters?

@mdboom
Copy link
Member

mdboom commented Jul 23, 2013

@WeatherGod: The tricky thing about my suggestion -- of separating style from other parameters -- is how best to transition to it, as @ChrisBeaumont pointed out:

The transition period would be awkward, however (you'd have to warn users that matplotlibrc values should not have visual level options, and then load them anyways after the default style is applied in explicit mode).

But maybe we should bite the bullet and do it, despite its akwardness.

@tonysyu
Copy link
Contributor Author

tonysyu commented Jul 23, 2013

I agree that it would be good to separate style parameters from config parameters. Just to be clear though, this would be work for a separate PR, correct? (As part of that, removing the bulk of the rc logic from __init__.py and the mixing of data and logic in rcsetup.py would be very desirable---but maybe quite a bit of work.)

@WeatherGod
Copy link
Member

Understandable, but I would suggest holding off on a "style" parameter
until that split happens. It just makes zero sense to me until the split is
made.

@tonysyu
Copy link
Contributor Author

tonysyu commented Jul 23, 2013

I'd agree with holding off on a "style" parameter. In any case, a lot of rc-refactoring will need to be done before that.

@ChrisBeaumont
Copy link
Contributor

I understand the concern, but it's too bad -- once a style with better defaults matures, it will be a shame to have to write 'style.use('better')' at the top of every script.

I'd definitely be willing to help and/or spearhead the proposed rcParams refactoring, to keep momentum going on this -- though I may not be the most qualified person to tackle a big API change like this.

@tonysyu
Copy link
Contributor Author

tonysyu commented Jul 23, 2013

I understand the concern, but it's too bad -- once a style with better defaults matures, it will be a shame to have to write 'style.use('better')' at the top of every script

Sorry, I don't mean to suggest that we should punt on this indefinitely. I just think that smaller, more focused PRs are easier for everyone involved. (Plus, I don't think I want to lead the refactor :)

I'd definitely be willing to help and/or spearhead the proposed rcParams refactoring, to keep momentum going on this -- though I may not be the most qualified person to tackle a big API change like this.

I think this could be split into two PRs: One just to refactor rcsetup.py and the rc parts of __init__.py into something a bit cleaner but with no API changes, and then the second part would be separating styles from config params. That's how I would do it at least.

@tacaswell
Copy link
Member

I understand the concern, but it's too bad -- once a style with better defaults matures, it will be a shame to have to write 'style.use('better')' at the top of every script.

This is far preferable than having to go back and touch every existing script that is broken by the 'better' defaults.

I am in favor of treating anything more than near trivial tweaks to defaults as a major api break (like bump to 2.0). If a change requires re-generating a test image, you are breaking the api, and I suspect that this will require a majority of the test images to be re-generated.

That said, the ability easily save and set the styling is first order good, in part as protection against the defaults changing.

[edit: gah I know grammer, I swear]

@dpsanders
Copy link

As far as I can see, the style module is still unusable from matplotlib master.
Does anybody have a fix to register it -- @mdboom ?

@ChrisBeaumont
Copy link
Contributor

@dpsanders @tonysyu you probably want to add matplotlib.style to the list in setupext.Matplotlib.get_packages, and style/stylelib/* to get_package_data in the same class (though I don't know if styles belong in mpl-data instead)

@ChrisBeaumont
Copy link
Contributor

@tacaswell I don't think that is the issue being discussed here. Instead, this is about enabling a kind of opt-in behavior where someone can specify a default style in their own matplotlibrc. That would make it easier for MPL to develop alternative sets of rcParams that people could use by default if they want, without breaking test images or legacy user code.

@dpsanders
Copy link

@pelson Great, thanks -- I tried that, but got the following error from python setup.py build:

error: package directory 'lib/matplotlib/stylematplotlib/testing' does not exist

Any ideas?

@tonysyu
Copy link
Contributor Author

tonysyu commented Nov 17, 2013

Tests are almost passing. Maybe Python 2.6 has an issue with a context-manager returning a generator?

@mdboom
Copy link
Member

mdboom commented Nov 18, 2013

I've made a PR against this one with a Python 2.6 fix

@tonysyu
Copy link
Contributor Author

tonysyu commented Nov 18, 2013

Woot! Tests passing!

mdboom added a commit that referenced this pull request Nov 18, 2013
Add easy style sheet selection
@mdboom mdboom merged commit 0e7daad into matplotlib:master Nov 18, 2013
@mdboom
Copy link
Member

mdboom commented Nov 18, 2013

Merged. Thanks, Tony. This was a big one!

@ChrisBeaumont
Copy link
Contributor

Phew! That was harder than I expected, but I'm glad this is now part of MPL

@tonysyu
Copy link
Contributor Author

tonysyu commented Nov 19, 2013

Thanks to everyone for all the improvements and fixes. It definitely took longer than expected, but it's great to have this in master. On to the rcparams refactor! (I'm not volunteering to lead that charge, but I'm happy to help where I can :).

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

Successfully merging this pull request may close these issues.

None yet