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
n_colors = len(levels) + ext_n - 1
pal = _color_palette(cmap, n_colors)
new_cmap, cnorm = mpl.colors.from_levels_and_colors(levels, pal, extend=extend)
# copy the old cmap name, for easier testing
new_cmap.name = getattr(cmap, "name", cmap)
# copy colors to use for bad, under, and over values in case they have been set to
# non-default values
new_cmap._rgba_bad = getattr(cmap, "_rgba_bad", new_cmap._rgba_bad)
new_cmap._rgba_under = getattr(cmap, "_rgba_under", new_cmap._rgba_under)
new_cmap._rgba_over = getattr(cmap, "_rgba_over", new_cmap._rgba_over)
This works, but is not particularly elegant, and if there are any other properties of the original cmap that we want to keep, we'd have to copy them over explicitly. @dcherian asked me to see if anyone on here could suggest a better way of modifying a cmap into a discrete cmap?
I guess its a fair question whether we should have ever provided from_levels_and_colors in the first place, since we don't use it internally. If we think we are keeping it, I'm sure a PR would be welcomed to extend the functionality to include over/under.
from_levels_and_colors takes in colors, not a colormap. Hence it has no use of over/under/bad. Of course it could take those in as arguments as well, but that does not help with the original problem here.
Potential actionable things here are:
Should over/under/bad exposed, e.g. via .get_{over/under/bad}?
Should there be a public version of Colormap.resample that returns a copy of the original colormap, just with a different number of colors?
Worth mentionning that matplotlib does not even take over/under/bad too seriously itself. E.g. neither the .reversed(), nor the ._resample() method take those into account, as seen from the below:
No, because that gives a new colormap without the original over/under/bad settings.
cmap = plt.get_cmap("cividis")
cmap.set_over("red")
print(cmap(1.1)) # prints (1.0, 0.0, 0.0, 1.0) which is "red"
cmap2 = plt.get_cmap("cividis")
print(cmap2(1.1)) # prints (1.0, 0.0, 0.0, 1.0) which is "red"
cmap3 = plt.get_cmap("cividis", lut=5)
print(cmap3(1.1)) # prints (0.995737, 0.909344, 0.217772, 1.0)
# which is the last color of cividis
It's also not really desired, since there needs to be a way to get the inbuilt colormap back even after modifications.
Builtin colormaps are created once and stored globally in cmap_d.
get_cmap just returns the element from the dictionary (probably assuming that it's immutable). But in fact, you get the builtin reference colormap object and can modify that using set_over().
Proposal for changes
IMHO this needs some cleanup, which will include breaking changes (but OTOH I expect that not many people would be affected by the breaking).
Should cmaps be immutable (including over/under/bad)? I tend to say yes to keep it simple.
get_cmap() should always return the builtin colormap. Whether that can be the same object everytime or a copy depends on 1.
over/under/bad should be treated consistently (propagated via reverse, resample).
add a public .resampled() method. Deprecate get_cmap(..., lut=...).
I am 👍 on all of @timhoffm 's proposals. For 1) we should go with an API like named tuple of new_cmap = cmap.replace(over=, under=, bad=).
We will definitely need a transition period and a public way to set a replacement color map for a built in one. I am sure that we have significant use of
I have definitely messed myself up before by accidentally setting an under/over value somewhere early-on in a Jupyter notebook and been very confused why the cmaps I was using later were looking different than the builtin only to realize I'd made this very mistake! I'd be all for returning a copy of the colormaps when calling get_cmap rather than a pointer to the object.
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!
The only open aspect is immutable colormaps. It would have been nice to have immutability designed in from the start. But that's not the world we're in. I'm inclined to say that enforcing immutability now would be too much of an API change compared to what we gain. The above changes already make it much more unlikely that changing an existing colormap will have unwanted side effects.
I therefore close this issue as done. If there was still need for a discussion on immutable colormaps, please open a new ticket for that.
Activity
jklymak commentedon Jan 22, 2020
I guess its a fair question whether we should have ever provided
from_levels_and_colorsin the first place, since we don't use it internally. If we think we are keeping it, I'm sure a PR would be welcomed to extend the functionality to include over/under.ImportanceOfBeingErnest commentedon Jan 22, 2020
from_levels_and_colorstakes in colors, not a colormap. Hence it has no use of over/under/bad. Of course it could take those in as arguments as well, but that does not help with the original problem here.Potential actionable things here are:
over/under/badexposed, e.g. via.get_{over/under/bad}?Colormap.resamplethat returns a copy of the original colormap, just with a different number of colors?Worth mentionning that matplotlib does not even take over/under/bad too seriously itself. E.g. neither the
.reversed(), nor the._resample()method take those into account, as seen from the below:anntzer commentedon Jan 25, 2020
For the getters, see recent discussion in #16075.
QuLogic commentedon Mar 10, 2020
Isn't that
get_cmap('name', lut=count)?ImportanceOfBeingErnest commentedon Mar 10, 2020
No, because that gives a new colormap without the original over/under/bad settings.
It's also not really desired, since there needs to be a way to get the inbuilt colormap back even after modifications.
jklymak commentedon Mar 10, 2020
Your second line looks like a bug to me. Why would setting over be sticky like that to the colormap name?
timhoffm commentedon Mar 10, 2020
Builtin colormaps are created once and stored globally in
cmap_d.get_cmapjust returns the element from the dictionary (probably assuming that it's immutable). But in fact, you get the builtin reference colormap object and can modify that usingset_over().Proposal for changes
IMHO this needs some cleanup, which will include breaking changes (but OTOH I expect that not many people would be affected by the breaking).
get_cmap()should always return the builtin colormap. Whether that can be the same object everytime or a copy depends on 1..resampled()method. Deprecateget_cmap(..., lut=...).tacaswell commentedon Mar 10, 2020
I am 👍 on all of @timhoffm 's proposals. For 1) we should go with an API like named tuple of
new_cmap = cmap.replace(over=, under=, bad=).We will definitely need a transition period and a public way to set a replacement color map for a built in one. I am sure that we have significant use of
anntzer commentedon Mar 10, 2020
The replace() API is basically implemented (under a different name) at #14645 ;-)
greglucas commentedon Mar 27, 2020
I have definitely messed myself up before by accidentally setting an under/over value somewhere early-on in a Jupyter notebook and been very confused why the cmaps I was using later were looking different than the builtin only to realize I'd made this very mistake! I'd be all for returning a copy of the colormaps when calling
get_cmaprather than a pointer to the object.tacaswell commentedon Mar 28, 2020
I see that I have now argued both sides of this (here being in favor of changing the behavior and in #16943 comments against changing the behavior).
github-actions commentedon Jul 7, 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 Jul 10, 2023
Most of the above aspects are implemented by now:
Colormap.set_extremes()/Colormap.with_extremes()Colormap.resampled(n)The only open aspect is immutable colormaps. It would have been nice to have immutability designed in from the start. But that's not the world we're in. I'm inclined to say that enforcing immutability now would be too much of an API change compared to what we gain. The above changes already make it much more unlikely that changing an existing colormap will have unwanted side effects.
I therefore close this issue as done. If there was still need for a discussion on immutable colormaps, please open a new ticket for that.