-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
ENH: Add bad, under, over kwargs to Colormap #29460
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
Colormaps support giving colors for bad, under and over values on creation | ||
-------------------------------------------------------------------------- | ||
|
||
Colormaps gained keyword arguments ``bad``, ``under``, and ``over`` to | ||
specify these values on creation. Previously, these values would have to | ||
be set afterwards using one of `~.Colormap.set_bad`, `~.Colormap.set_under`, | ||
`~.Colormap.set_bad`, `~.Colormap.set_extremes`, `~.Colormap.with_extremes`. | ||
|
||
It is recommended to use the new functionality, e.g.:: | ||
|
||
cmap = ListedColormap(colors, bad="red", under="darkblue", over="purple") | ||
|
||
instead of:: | ||
|
||
cmap = ListedColormap(colors).with_extremes( | ||
bad="red", under="darkblue", over="purple") | ||
|
||
or:: | ||
|
||
cmap = ListedColormap(colors) | ||
cmap.set_bad("red") | ||
cmap.set_under("darkblue") | ||
cmap.set_over("purple") |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -718,20 +718,34 @@ class Colormap: | |
chain. | ||
""" | ||
|
||
def __init__(self, name, N=256): | ||
def __init__(self, name, N=256, *, bad=None, under=None, over=None): | ||
""" | ||
Parameters | ||
---------- | ||
name : str | ||
The name of the colormap. | ||
N : int | ||
The number of RGB quantization levels. | ||
bad : :mpltype:`color`, default: transparent | ||
The color for invalid values (NaN or masked). | ||
|
||
.. versionadded:: 3.11 | ||
|
||
under : :mpltype:`color`, default: color of the lowest value | ||
The color for low out-of-range values. | ||
|
||
.. versionadded:: 3.11 | ||
|
||
over : :mpltype:`color`, default: color of the highest value | ||
The color for high out-of-range values. | ||
|
||
.. versionadded:: 3.11 | ||
""" | ||
self.name = name | ||
self.N = int(N) # ensure that N is always int | ||
self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything. | ||
self._rgba_under = None | ||
self._rgba_over = None | ||
self._rgba_bad = (0.0, 0.0, 0.0, 0.0) if bad is None else to_rgba(bad) | ||
self._rgba_under = None if under is None else to_rgba(under) | ||
self._rgba_over = None if over is None else to_rgba(over) | ||
self._i_under = self.N | ||
self._i_over = self.N + 1 | ||
self._i_bad = self.N + 2 | ||
|
@@ -1038,43 +1052,69 @@ class LinearSegmentedColormap(Colormap): | |
segments. | ||
""" | ||
|
||
def __init__(self, name, segmentdata, N=256, gamma=1.0): | ||
def __init__(self, name, segmentdata, N=256, gamma=1.0, *, | ||
bad=None, under=None, over=None): | ||
""" | ||
Create colormap from linear mapping segments | ||
Create colormap from linear mapping segments. | ||
|
||
segmentdata argument is a dictionary with a red, green and blue | ||
entries. Each entry should be a list of *x*, *y0*, *y1* tuples, | ||
forming rows in a table. Entries for alpha are optional. | ||
Parameters | ||
---------- | ||
name : str | ||
The name of the colormap. | ||
segmentdata : dict | ||
A dictionary with keys "red", "green", "blue" for the color channels. | ||
Each entry should be a list of *x*, *y0*, *y1* tuples, forming rows | ||
in a table. Entries for alpha are optional. | ||
|
||
Example: suppose you want red to increase from 0 to 1 over | ||
the bottom half, green to do the same over the middle half, | ||
and blue over the top half. Then you would use:: | ||
|
||
{ | ||
'red': [(0.0, 0.0, 0.0), | ||
(0.5, 1.0, 1.0), | ||
(1.0, 1.0, 1.0)], | ||
'green': [(0.0, 0.0, 0.0), | ||
(0.25, 0.0, 0.0), | ||
(0.75, 1.0, 1.0), | ||
(1.0, 1.0, 1.0)], | ||
'blue': [(0.0, 0.0, 0.0), | ||
(0.5, 0.0, 0.0), | ||
(1.0, 1.0, 1.0)] | ||
} | ||
|
||
Example: suppose you want red to increase from 0 to 1 over | ||
the bottom half, green to do the same over the middle half, | ||
and blue over the top half. Then you would use:: | ||
Each row in the table for a given color is a sequence of | ||
*x*, *y0*, *y1* tuples. In each sequence, *x* must increase | ||
monotonically from 0 to 1. For any input value *z* falling | ||
between *x[i]* and *x[i+1]*, the output value of a given color | ||
will be linearly interpolated between *y1[i]* and *y0[i+1]*:: | ||
|
||
cdict = {'red': [(0.0, 0.0, 0.0), | ||
(0.5, 1.0, 1.0), | ||
(1.0, 1.0, 1.0)], | ||
row i: x y0 y1 | ||
/ | ||
/ | ||
row i+1: x y0 y1 | ||
|
||
'green': [(0.0, 0.0, 0.0), | ||
(0.25, 0.0, 0.0), | ||
(0.75, 1.0, 1.0), | ||
(1.0, 1.0, 1.0)], | ||
Hence, y0 in the first row and y1 in the last row are never used. | ||
|
||
'blue': [(0.0, 0.0, 0.0), | ||
(0.5, 0.0, 0.0), | ||
(1.0, 1.0, 1.0)]} | ||
N : int | ||
The number of RGB quantization levels. | ||
gamma : float | ||
Gamma correction factor for input distribution x of the mapping. | ||
See also https://en.wikipedia.org/wiki/Gamma_correction. | ||
bad : :mpltype:`color`, default: transparent | ||
The color for invalid values (NaN or masked). | ||
|
||
.. versionadded:: 3.11 | ||
|
||
under : :mpltype:`color`, default: color of the lowest value | ||
The color for low out-of-range values. | ||
|
||
Each row in the table for a given color is a sequence of | ||
*x*, *y0*, *y1* tuples. In each sequence, *x* must increase | ||
monotonically from 0 to 1. For any input value *z* falling | ||
between *x[i]* and *x[i+1]*, the output value of a given color | ||
will be linearly interpolated between *y1[i]* and *y0[i+1]*:: | ||
.. versionadded:: 3.11 | ||
|
||
row i: x y0 y1 | ||
/ | ||
/ | ||
row i+1: x y0 y1 | ||
over : :mpltype:`color`, default: color of the highest value | ||
The color for high out-of-range values. | ||
|
||
Hence y0 in the first row and y1 in the last row are never used. | ||
.. versionadded:: 3.11 | ||
|
||
See Also | ||
-------- | ||
|
@@ -1084,7 +1124,7 @@ def __init__(self, name, segmentdata, N=256, gamma=1.0): | |
""" | ||
# True only if all colors in map are identical; needed for contouring. | ||
self.monochrome = False | ||
super().__init__(name, N) | ||
super().__init__(name, N, bad=bad, under=under, over=over) | ||
self._segmentdata = segmentdata | ||
self._gamma = gamma | ||
|
||
|
@@ -1108,7 +1148,7 @@ def set_gamma(self, gamma): | |
self._init() | ||
|
||
@staticmethod | ||
def from_list(name, colors, N=256, gamma=1.0): | ||
def from_list(name, colors, N=256, gamma=1.0, *, bad=None, under=None, over=None): | ||
""" | ||
Create a `LinearSegmentedColormap` from a list of colors. | ||
|
||
|
@@ -1125,6 +1165,13 @@ def from_list(name, colors, N=256, gamma=1.0): | |
N : int | ||
The number of RGB quantization levels. | ||
gamma : float | ||
|
||
bad : :mpltype:`color`, default: transparent | ||
The color for invalid values (NaN or masked). | ||
under : :mpltype:`color`, default: color of the lowest value | ||
The color for low out-of-range values. | ||
over : :mpltype:`color`, default: color of the highest value | ||
The color for high out-of-range values. | ||
""" | ||
if not np.iterable(colors): | ||
raise ValueError('colors must be iterable') | ||
|
@@ -1144,7 +1191,8 @@ def from_list(name, colors, N=256, gamma=1.0): | |
"alpha": np.column_stack([vals, a, a]), | ||
} | ||
|
||
return LinearSegmentedColormap(name, cdict, N, gamma) | ||
return LinearSegmentedColormap(name, cdict, N, gamma, | ||
bad=bad, under=under, over=over) | ||
|
||
def resampled(self, lutsize): | ||
"""Return a new colormap with *lutsize* entries.""" | ||
|
@@ -1219,6 +1267,26 @@ class ListedColormap(Colormap): | |
N > len(colors) | ||
|
||
the list will be extended by repetition. | ||
|
||
.. deprecated:: 3.11 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems orthogonal to this PR? Or is this already deprecated elsewhere and this is just a doc cleanup? I'm not really following why we would need to do this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The deprecation is in #29135, it's indeed unrelated. I just noticed that I haven't added this to the parameter docs, and didn't bother to make a separate PR since I was editing that position in the docstring anyway. Sorry for the confusion. |
||
|
||
This parameter will be removed. Please instead ensure that | ||
the list of passed colors is the required length. | ||
|
||
bad : :mpltype:`color`, default: transparent | ||
The color for invalid values (NaN or masked). | ||
|
||
.. versionadded:: 3.11 | ||
|
||
under : :mpltype:`color`, default: color of the lowest value | ||
The color for low out-of-range values. | ||
|
||
.. versionadded:: 3.11 | ||
|
||
over : :mpltype:`color`, default: color of the highest value | ||
The color for high out-of-range values. | ||
|
||
.. versionadded:: 3.11 | ||
""" | ||
|
||
@_api.delete_parameter( | ||
|
@@ -1227,7 +1295,8 @@ class ListedColormap(Colormap): | |
"and will be removed in %(removal)s. Please ensure the list " | ||
"of passed colors is the required length instead." | ||
) | ||
def __init__(self, colors, name='from_list', N=None): | ||
def __init__(self, colors, name='from_list', N=None, *, | ||
bad=None, under=None, over=None): | ||
if N is None: | ||
self.colors = colors | ||
N = len(colors) | ||
|
@@ -1244,7 +1313,7 @@ def __init__(self, colors, name='from_list', N=None): | |
pass | ||
else: | ||
self.colors = [gray] * N | ||
super().__init__(name, N) | ||
super().__init__(name, N, bad=bad, under=under, over=over) | ||
|
||
def _init(self): | ||
self._lut = np.zeros((self.N + 3, 4), float) | ||
|
@@ -3748,8 +3817,7 @@ def from_levels_and_colors(levels, colors, extend='neither'): | |
data_colors = colors[color_slice] | ||
under_color = colors[0] if extend in ['min', 'both'] else 'none' | ||
over_color = colors[-1] if extend in ['max', 'both'] else 'none' | ||
cmap = ListedColormap(data_colors).with_extremes( | ||
under=under_color, over=over_color) | ||
cmap = ListedColormap(data_colors, under=under_color, over=over_color) | ||
|
||
cmap.colorbar_extend = extend | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
? (Likewise in many locations below. Hence, maybe it is not required...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. It's a bit verbose, but OTOH it's good to be explicit about this.