You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Like we have named colormaps, it would be useful to have named cyclers as well.
This would allow code like
or even by name:
Is it useful?
I think there's a need to efficiently switch cyclers depending on the plot.
Also, as part of this, we can provide a number of default cyclers.
Styles are already available, however I have the feeling that they are too broad as I often adapt just the colors and defining a new style for each color scheme seems a bit heavy-weight.
Apart from the places where a cycler is explicitly used, this could also be used for other methods with a colors argument, e.g. plt.pie(data, colors='desert').
What would be needed?
Similar to colormaps:
register_cycler()
get_cycler()
a number of default cyclers.
Maybe the upstream Cycler class should get a name property.
I don't think one should introduce color cycles separate from colormaps, or inversely put, possible color cycles should be made available as ListedColormap instances.
The above being said, it is of course possible to use the colormaps to provide the colors to cycle over.
df = pd.DataFrame(np.random.rand(4,3), columns=list("ABC"))
with plt.rc_context({"axes.prop_cycle" : plt.cycler("color",plt.cm.Set3(range(3)))}):
df.plot.bar()
or equally possible
with plt.rc_context({"axes.prop_cycle" : plt.cycler("color",plt.get_cmap("Set3").colors)}):
df.plot.bar()
In view of the above I do not actually see much of a benefit introducing the option to write with plt.rc_context({"axes.prop_cycle" : plt.get_cycler("Set3") }):
instead of with plt.rc_context({"axes.prop_cycle" : plt.cycler("color",plt.cm.Set3(range(3)))}):
To simplify things in a way that would truely ease people's lifes one would rather need with plt.use_cycle("Set3"):
Seaborn does actually provide this very convenient way of setting the cycle (resulting in the same plot as above):
import seaborn as sns
sns.set_palette("Set3")
df.plot.bar()
or
with sns.color_palette("Set3"):
df.plot.bar()
Some problems one should probably tackle first:
Resolve some of the problems with ListedColormaps compared to SegmentedColormaps. It is in general not very transparent how to handle those and for non-experienced users it is a non-trivial task to get 3 colors out of a ListedColormap:
Obtain a cycler from a colormap, i.e. something like ListedColormap.as_cycler(), such that one could do
cycler = plt.get_cmap(...).as_cycler()
With this, the proposed register_cycler would not be needed at all and the proposed get_cycler() would reduce to a single line of code. If all "default cyclers" are colormaps, there is also no need for a name attribute of the Cycler class itself.
Colormaps are a mapping [0, 1] -> colors. This is conceptually different from a discrete sequence. ListedColormap is just the special case of segment-wise constant values. It does not provide expected list-like features such as indexing and iteration. We have to use ListedColormap.colors to get what we actually need. So, a ListedColormap is basically an object for another purpose, that just happens to have a name and a list of colors stored which we could use to create a cycler. That doesn't feel quite clean and intuitive to me.
There's also a good chance of users mixing up ListedColormaps and other colormaps. While plt.cycler("color", plt.get_cmap("Set3").colors) works, it wouldn't work with other colormaps such as "jet". Same with a possible .as_cycler() method. Having to know that get_cmap returns objects with different APIs is not something I want to burden on the user.
Colors are probably the most used property to cycle over. However, if we go into the direction of easily changing cycling properties, we should at least consider the more general case of having names for other cyclers as well.
I think, there is real value in cleanly distinguishing between colormaps and colorcyclers (or more general cyclers). That said, i really like the syntax of with plt.use_cycle("Set3"), maybe also sns.set_palette("Set3") - only the argument should not be a colormap name.
My point is that possible cycler names should exactly be the colormap names. Because they contain the same colors. Having the "tab10" colormap, I would want the corresponding cycler to also be accessible through the name "tab10" and not something completely different. Or the other way around, if there is a "desert" color cycle, one would want to have the corresponding colors available as a ListedColormapget_cmap("desert") as well. Why deprive the user of this option and burden them with having to manually create cyclers from colormaps and vice versa?
Users already now have to deal with get_cmap resulting in different objects depending on what they input. The burden is hence present and confuses people. But one might of course try to unify things and make this easier. This is what the first stated problem from my previous post wants to make clear.
Indeed, to use a LinearSegmentedColormap like jet as a cycler, you would need to call it differently.
with plt.rc_context({"axes.prop_cycle" : plt.cycler("color",plt.get_cmap("jet")(np.linspace(0,1,3)))}):
df.plot.bar()
So to resolve this problem, an as_cycler() method would make sense.
Up to now I was under the assumption that there is a clear distinction between LinearSegmentedColormaps and ListedColormaps in that all qualitatitive colormaps are Listed ones and all other are Segmented ones. This does not seem to be the case though and any given colormap does not even know which type it belongs to. Again, utterly confusing. But in any case, a possible implementation, although working a bit heuristically, would be
from matplotlib.colors import LinearSegmentedColormap, ListedColormap
import matplotlib._cm
def get_cycle(cmap, N=None, use_index="auto"):
if isinstance(cmap, str):
if use_index == "auto":
if cmap in ['Pastel1', 'Pastel2', 'Paired', 'Accent',
'Dark2', 'Set1', 'Set2', 'Set3',
'tab10', 'tab20', 'tab20b', 'tab20c']:
use_index=True
else:
use_index=False
cmap = plt.get_cmap(cmap)
if not N:
N = cmap.N
if use_index=="auto":
if cmap.N > 100:
use_index=False
elif isinstance(cmap, LinearSegmentedColormap):
use_index=False
elif isinstance(cmap, ListedColormap):
use_index=True
if use_index:
ind = np.arange(int(N)) % cmap.N
return plt.cycler("color",cmap(ind))
else:
colors = cmap(np.linspace(0,1,N))
return plt.cycler("color",colors)
Here the user does not need to know anything about different return types or the true differences between colormaps, get_cycle(name, number) will give him the colorcycle with number colors from name colormap.
Concerning cycler names, agreed, if you want to have a linestyle cycle you can name it differently, just not with any name that is reserved by a colormap I'd say.
@ImportanceOfBeingErnest, Matplotlib colormaps are internally all the same at the point at which they are used: a lookup table with machinery for executing the lookup. LinearSegmented versus Listed are just different ways of specifying how to generate that table. Typically (and by default) LinearSegmentedColormaps have 256 normal colors in the lookup table, so they are a good approximation of a continuum. In addition, all colormaps have 3 special values: one for masked points, one for under-range values, and one for over-range values. The lookups can use integers for direct indexing, or they can use floating point values in the 0-1 range.
@efiring That is precisely the problem. There is no distinction between qualitative colormaps like "tab10" and gradient colormaps like "viridis". Those colormaps are the same, but would need to be used in a totally different manner. If each colormap would know which kind it belongs to, usage would be simplified a lot.
Nonetheless it is possible to use both kinds of colormaps as shown above for producing cyclers. So I still think this is the way to go.
providing a get_cycler_by_name(name : str) -> cycler) function some place (maybe in matplotlib.colors?). Do we want to allow this to return any cycler? If so do we also need to provide a way to filter out keys the user does not want? Do we provide a registration mechanism for users to 'bring-their-own'? This overlaps a bit with having a color-cycle-only style and mpl.style.use, but I like the non-global-state version of this!
providing a get_color_cycler_from_colormap(cmap : Union[str, ColorMap], N=15) -> cycler function which will make a Norm and do the right slicing into a color map to get a set of colors and then stuff them into a cycler.
automatically populate 1) with the output of 2) from all of the color maps we ship / automatically fall back.
I think 1 is a good idea (pinning down those questions), 2 is a nice helper (and people are doing to do it anyway), 3 is probably not a good idea to do automatically (as not all color maps go well into a color cycle and we may not want to expose N trough number 1), but we should evaluate our color maps to populate the default set (for example, a number of our color maps started life as color cycles that got re-purposed and there are reasons to want categorical color maps).
The get_cycle method I proposed above basically does all 3 points. (Of course N is optional, if people don't like/need it, they are not forced to use it.) I do not understand the opposition against point 3. If people want 16 colors from the "viridis" colormap why make life so very hard for them???
I don't see how the proposed get_cycle defines named cycles in the absence of an already existing colormap.... Are you proposing that every cycle would need a named colormap first?
But there are lots of cycles that we have or would like to have that don't rely on a colormap; it'd be a shame if they couldn't get names, and even more overhead if the only way to define them was to register a colormap first...
The fact that there are cycles which do not have a colormap equivalent is already suboptimal. Think of it the other way around: Do you want to have a situation where a bunch of colors named "tab10" is a colormap, but the other bunch of colors named "desert" is not? Do you want users to run into the situation where they mix up names between what is provided as colorcycle and what as colormap?
I think a MEP might be overkill, but I certainly wouldn't object to one.
It's not clear to me that this should involve more general cyclers; they can be invoked via styles. They can be generated by composition.
Unification of named color lists seems like something that can be done independently.
Can we agree that I will work out a proposal? I think we're a bit talking in circles now and it may help to discuss based on some API concept and use-cases.
Can style sheets call style sheets? i.e. can you have a tableau23-cycler style that then is embedded in other style sheets? That would be another way to go with this rather than give line cyclers special handling and having yet another mysterious registry that folks need to figure out how to hack into to add their own line cycles.
I think it's inevitable to provide a registration mechanism for cyclers. But if that is completely analoguous to how colormaps are registered, the learning curve for users is pretty flat. My point here would again be that a registered cycler should automatically register its color part as a colormap. And the inverse should be true as well, any colormap that is registered by the user should become a registered color cycler.
Turning more towards general cyclers, I think an open question that would need to be discussed is how to handle compositions.
I could imagine to restrict named cyclers to a single property. A cycler named "desert" would be a colorcycle, a cycler named "triangles" would be marker cycle, a cycler named "shortstitch" would be a linestyle cycler. Such cyclers can be composed in the usual fashion, but possibly also via composed names, like "desert+triangles" or "triangles*shortstitch".
One could also allow for named composed cyclers. This makes things a bit more complicated from a user's perspective because the number of unique names grows with the power of possible properties and people will have problems to remember if some named cycler contains a certain property or not. On the other hand it feels more natural to just use a single name for the cycler you want to use.
In view of this, it might make sense to think about whether or not a name should include the number of elements of that cycler. For the + composition this seems necessary, unless all cyclers have the same number of elements. E.g. if "triangles" has 4 elements, and "shortstitch" has 3, one could get an addititve marker-linestyle-cycler via "triangles3+shortstitch".
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!
Activity
ImportanceOfBeingErnest commentedon Mar 19, 2018
A few comments:
I don't think one should introduce color cycles separate from colormaps, or inversely put, possible color cycles should be made available as
ListedColormapinstances.The above being said, it is of course possible to use the colormaps to provide the colors to cycle over.
or equally possible
In view of the above I do not actually see much of a benefit introducing the option to write
with plt.rc_context({"axes.prop_cycle" : plt.get_cycler("Set3") }):instead of
with plt.rc_context({"axes.prop_cycle" : plt.cycler("color",plt.cm.Set3(range(3)))}):To simplify things in a way that would truely ease people's lifes one would rather need
with plt.use_cycle("Set3"):Seaborn does actually provide this very convenient way of setting the cycle (resulting in the same plot as above):
or
Some problems one should probably tackle first:
Resolve some of the problems with ListedColormaps compared to SegmentedColormaps. It is in general not very transparent how to handle those and for non-experienced users it is a non-trivial task to get 3 colors out of a ListedColormap:

Obtain a cycler from a colormap, i.e. something like
ListedColormap.as_cycler(), such that one could doWith this, the proposed
register_cyclerwould not be needed at all and the proposedget_cycler()would reduce to a single line of code. If all "default cyclers" are colormaps, there is also no need for a name attribute of theCyclerclass itself.timhoffm commentedon Mar 20, 2018
@ImportanceOfBeingErnest thanks for the comments.
ListedColormapdoesn't feel quite right.My concerns are:
Colormaps are a mapping
[0, 1] -> colors. This is conceptually different from a discrete sequence.ListedColormapis just the special case of segment-wise constant values. It does not provide expected list-like features such as indexing and iteration. We have to useListedColormap.colorsto get what we actually need. So, aListedColormapis basically an object for another purpose, that just happens to have a name and a list of colors stored which we could use to create a cycler. That doesn't feel quite clean and intuitive to me.There's also a good chance of users mixing up
ListedColormapsand other colormaps. Whileplt.cycler("color", plt.get_cmap("Set3").colors)works, it wouldn't work with other colormaps such as"jet". Same with a possible.as_cycler()method. Having to know thatget_cmapreturns objects with different APIs is not something I want to burden on the user.Colors are probably the most used property to cycle over. However, if we go into the direction of easily changing cycling properties, we should at least consider the more general case of having names for other cyclers as well.
I think, there is real value in cleanly distinguishing between colormaps and colorcyclers (or more general cyclers). That said, i really like the syntax of
with plt.use_cycle("Set3"), maybe alsosns.set_palette("Set3")- only the argument should not be a colormap name.jklymak commentedon Mar 20, 2018
There is already context management for styles. Is that not very similar. Certainly you can have a style that only changes the cycler.
ImportanceOfBeingErnest commentedon Mar 20, 2018
My point is that possible cycler names should exactly be the colormap names. Because they contain the same colors. Having the
"tab10"colormap, I would want the corresponding cycler to also be accessible through the name"tab10"and not something completely different. Or the other way around, if there is a"desert"color cycle, one would want to have the corresponding colors available as aListedColormapget_cmap("desert")as well. Why deprive the user of this option and burden them with having to manually create cyclers from colormaps and vice versa?Users already now have to deal with
get_cmapresulting in different objects depending on what they input. The burden is hence present and confuses people. But one might of course try to unify things and make this easier. This is what the first stated problem from my previous post wants to make clear.Indeed, to use a LinearSegmentedColormap like
jetas a cycler, you would need to call it differently.So to resolve this problem, an
as_cycler()method would make sense.Up to now I was under the assumption that there is a clear distinction between LinearSegmentedColormaps and ListedColormaps in that all qualitatitive colormaps are Listed ones and all other are Segmented ones. This does not seem to be the case though and any given colormap does not even know which type it belongs to. Again, utterly confusing. But in any case, a possible implementation, although working a bit heuristically, would be
Here the user does not need to know anything about different return types or the true differences between colormaps,
get_cycle(name, number)will give him the colorcycle withnumbercolors fromnamecolormap.Concerning cycler names, agreed, if you want to have a linestyle cycle you can name it differently, just not with any name that is reserved by a colormap I'd say.
efiring commentedon Mar 21, 2018
@ImportanceOfBeingErnest, Matplotlib colormaps are internally all the same at the point at which they are used: a lookup table with machinery for executing the lookup. LinearSegmented versus Listed are just different ways of specifying how to generate that table. Typically (and by default) LinearSegmentedColormaps have 256 normal colors in the lookup table, so they are a good approximation of a continuum. In addition, all colormaps have 3 special values: one for masked points, one for under-range values, and one for over-range values. The lookups can use integers for direct indexing, or they can use floating point values in the 0-1 range.
ImportanceOfBeingErnest commentedon Mar 23, 2018
@efiring That is precisely the problem. There is no distinction between qualitative colormaps like
"tab10"and gradient colormaps like"viridis". Those colormaps are the same, but would need to be used in a totally different manner. If each colormap would know which kind it belongs to, usage would be simplified a lot.Nonetheless it is possible to use both kinds of colormaps as shown above for producing cyclers. So I still think this is the way to go.
ngoldbaum commentedon May 30, 2018
I agree with the original reporter of this issue that it would be nice to have a listing of predefined named color cycles.
tacaswell commentedon May 30, 2018
(moving discussion from gitter)
There seems to be 3 issues here:
get_cycler_by_name(name : str) -> cycler)function some place (maybe inmatplotlib.colors?). Do we want to allow this to return any cycler? If so do we also need to provide a way to filter out keys the user does not want? Do we provide a registration mechanism for users to 'bring-their-own'? This overlaps a bit with having a color-cycle-only style andmpl.style.use, but I like the non-global-state version of this!get_color_cycler_from_colormap(cmap : Union[str, ColorMap], N=15) -> cyclerfunction which will make a Norm and do the right slicing into a color map to get a set of colors and then stuff them into a cycler.I think 1 is a good idea (pinning down those questions), 2 is a nice helper (and people are doing to do it anyway), 3 is probably not a good idea to do automatically (as not all color maps go well into a color cycle and we may not want to expose
Ntrough number 1), but we should evaluate our color maps to populate the default set (for example, a number of our color maps started life as color cycles that got re-purposed and there are reasons to want categorical color maps).ImportanceOfBeingErnest commentedon May 30, 2018
The
get_cyclemethod I proposed above basically does all 3 points. (Of courseNis optional, if people don't like/need it, they are not forced to use it.) I do not understand the opposition against point 3. If people want 16 colors from the "viridis" colormap why make life so very hard for them???jklymak commentedon May 30, 2018
I don't see how the proposed
get_cycledefines named cycles in the absence of an already existing colormap.... Are you proposing that every cycle would need a named colormap first?ImportanceOfBeingErnest commentedon May 30, 2018
Absolutely! That is my point from the beginning.
jklymak commentedon May 30, 2018
But there are lots of cycles that we have or would like to have that don't rely on a colormap; it'd be a shame if they couldn't get names, and even more overhead if the only way to define them was to register a colormap first...
ImportanceOfBeingErnest commentedon May 30, 2018
The fact that there are cycles which do not have a colormap equivalent is already suboptimal. Think of it the other way around: Do you want to have a situation where a bunch of colors named
"tab10"is a colormap, but the other bunch of colors named"desert"is not? Do you want users to run into the situation where they mix up names between what is provided as colorcycle and what as colormap?Is this really what people want?
8 remaining items
timhoffm commentedon May 31, 2018
Is this a case for a MEP?
efiring commentedon May 31, 2018
I think a MEP might be overkill, but I certainly wouldn't object to one.
It's not clear to me that this should involve more general cyclers; they can be invoked via styles. They can be generated by composition.
Unification of named color lists seems like something that can be done independently.
timhoffm commentedon May 31, 2018
Can we agree that I will work out a proposal? I think we're a bit talking in circles now and it may help to discuss based on some API concept and use-cases.
efiring commentedon May 31, 2018
Certainly, please proceed.
jklymak commentedon May 31, 2018
Can style sheets call style sheets? i.e. can you have a tableau23-cycler style that then is embedded in other style sheets? That would be another way to go with this rather than give line cyclers special handling and having yet another mysterious registry that folks need to figure out how to hack into to add their own line cycles.
ImportanceOfBeingErnest commentedon May 31, 2018
I think it's inevitable to provide a registration mechanism for cyclers. But if that is completely analoguous to how colormaps are registered, the learning curve for users is pretty flat. My point here would again be that a registered cycler should automatically register its color part as a colormap. And the inverse should be true as well, any colormap that is registered by the user should become a registered color cycler.
Turning more towards general cyclers, I think an open question that would need to be discussed is how to handle compositions.
"desert"would be a colorcycle, a cycler named"triangles"would be marker cycle, a cycler named"shortstitch"would be a linestyle cycler. Such cyclers can be composed in the usual fashion, but possibly also via composed names, like"desert+triangles"or"triangles*shortstitch".In view of this, it might make sense to think about whether or not a name should include the number of elements of that cycler. For the
+composition this seems necessary, unless all cyclers have the same number of elements. E.g. if"triangles"has 4 elements, and"shortstitch"has 3, one could get an addititve marker-linestyle-cycler via"triangles3+shortstitch".github-actions commentedon Oct 11, 2023
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!
timhoffm commentedon Oct 11, 2023
I'll close for now as there is not general consensus on a way forward and my personal priority has shifted.