Skip to content

Commit b02c22d

Browse files
authored
Merge pull request #11905 from jklymak/doc-add-colormap-manipulation
DOC: colormap-manipulation tutorial
2 parents bd254da + e9cda7f commit b02c22d

File tree

5 files changed

+248
-11
lines changed

5 files changed

+248
-11
lines changed

.flake8

+2-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ per-file-ignores =
8686
tutorials/advanced/transforms_tutorial.py: E402, E501
8787
tutorials/colors/colormaps.py: E501
8888
tutorials/colors/colors.py: E402
89+
tutorials/colors/colormap-manipulation.py: E402
8990
tutorials/intermediate/artists.py: E402, E501
9091
tutorials/intermediate/constrainedlayout_guide.py: E402, E501
9192
tutorials/intermediate/gridspec.py: E402, E501
@@ -114,6 +115,7 @@ per-file-ignores =
114115
examples/color/color_demo.py: E402
115116
examples/color/colorbar_basics.py: E402
116117
examples/color/colormap_reference.py: E402
118+
examples/color/custom_cmap.py: E402
117119
examples/color/named_colors.py: E402
118120
examples/event_handling/data_browser.py: E501
119121
examples/event_handling/path_editor.py: E501
@@ -129,7 +131,6 @@ per-file-ignores =
129131
examples/images_contours_and_fields/contourf_demo.py: E402, E501
130132
examples/images_contours_and_fields/contourf_hatching.py: E402
131133
examples/images_contours_and_fields/contourf_log.py: E402
132-
examples/images_contours_and_fields/custom_cmap.py: E402
133134
examples/images_contours_and_fields/demo_bboximage.py: E402
134135
examples/images_contours_and_fields/image_clip_path.py: E402
135136
examples/images_contours_and_fields/image_demo.py: E402

examples/images_contours_and_fields/custom_cmap.py renamed to examples/color/custom_cmap.py

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
Creating a colormap from a list of colors
44
=========================================
55
6+
For more detail on creating and manipulating colormaps see
7+
:doc:`/tutorials/colors/colormap-manipulation`.
8+
69
Creating a :doc:`colormap </tutorials/colors/colormaps>`
710
from a list of colors can be done with the
811
:meth:`~.colors.LinearSegmentedColormap.from_list` method of

lib/matplotlib/cm.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
"""
22
Builtin colormaps, colormap handling utilities, and the `ScalarMappable` mixin.
33
4-
See :doc:`/gallery/color/colormap_reference` for a list of builtin colormaps.
5-
See :doc:`/tutorials/colors/colormaps` for an in-depth discussion of colormaps.
4+
.. seealso::
5+
6+
:doc:`/gallery/color/colormap_reference` for a list of builtin
7+
colormaps.
8+
9+
:doc:`/tutorials/colors/colormap-manipulation` for examples of how to
10+
make colormaps and
11+
12+
:doc:`/tutorials/colors/colormaps` an in-depth discussion of
13+
choosing colormaps.
14+
15+
:doc:`/tutorials/colors/colormapnorms` for more details about data
16+
normalization
17+
18+
619
"""
720

821
import functools

lib/matplotlib/colors.py

+19-8
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,25 @@
66
77
This module includes functions and classes for color specification
88
conversions, and for mapping numbers to colors in a 1-D array of colors called
9-
a colormap. Colormapping typically involves two steps: a data array is first
10-
mapped onto the range 0-1 using an instance of :class:`Normalize` or of a
11-
subclass; then this number in the 0-1 range is mapped to a color using an
12-
instance of a subclass of :class:`Colormap`. Two are provided here:
13-
:class:`LinearSegmentedColormap`, which is used to generate all the built-in
14-
colormap instances, but is also useful for making custom colormaps, and
15-
:class:`ListedColormap`, which is used for generating a custom colormap from a
16-
list of color specifications.
9+
a colormap.
10+
11+
Mapping data onto colors using a colormap typically involves two steps:
12+
a data array is first mapped onto the range 0-1 using a subclass of
13+
:class:`Normalize`, then this number is mapped to a color using
14+
a subclass of :class:`Colormap`. Two are provided here:
15+
:class:`LinearSegmentedColormap`, which uses piecewise-linear interpolation
16+
to define colormaps, and :class:`ListedColormap`, which makes a colormap
17+
from a list of colors.
18+
19+
.. seealso::
20+
21+
:doc:`/tutorials/colors/colormap-manipulation` for examples of how to
22+
make colormaps and
23+
24+
:doc:`/tutorials/colors/colormaps` for a list of built-in colormaps.
25+
26+
:doc:`/tutorials/colors/colormapnorms` for more details about data
27+
normalization
1728
1829
The module also provides functions for checking whether an object can be
1930
interpreted as a color (:func:`is_color_like`), for converting such an object
+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
"""
2+
********************************
3+
Creating Colormaps in Matplotlib
4+
********************************
5+
6+
Creating and manipulating colormaps in Matplotlib is straight-forward
7+
using the class `.ListedColormap` and a Nx4 numpy array of values
8+
between 0 and 1 to represent the RGBA values of the colormap. There
9+
is also a `.LinearSegmentedColormap` class that allows colormaps to be
10+
specified with far fewer anchor points defining segments, and linearly
11+
interpolating between the anchor points.
12+
13+
Getting colormaps and accessing their values
14+
============================================
15+
16+
First, getting a named colormap, most of which are listed in
17+
:doc:`/tutorials/colors/colormaps` requires the use of
18+
`.matplotlib.cm.get_cmap`, which returns a
19+
:class:`.matplotlib.colors.ListedColormap` object. The second argument gives
20+
the size of the list of colors used to define the colormap, and below we
21+
use a modest value of 12 so there are not a lot of values to look at.
22+
"""
23+
24+
import numpy as np
25+
import matplotlib as mpl
26+
import matplotlib.pyplot as plt
27+
from matplotlib import cm
28+
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
29+
from collections import OrderedDict
30+
31+
viridis = cm.get_cmap('viridis', 12)
32+
print(viridis)
33+
34+
##############################################################################
35+
# The object ``viridis`` is a callable, that when passed a float between
36+
# 0 and 1 returns an RGBA value from the colormap:
37+
38+
print(viridis(0.56))
39+
40+
##############################################################################
41+
# The list of colors that comprise the colormap can be directly accessed using
42+
# the ``colors`` property,
43+
# or it can be acccessed indirectly by calling ``viridis`` with an array
44+
# of values matching the length of the colormap. Note that the returned list
45+
# is in the form of an RGBA Nx4 array, where N is the length of the colormap.
46+
47+
print('viridis.colors', viridis.colors)
48+
print('viridis(range(12))', viridis(range(12)))
49+
print('viridis(np.linspace(0, 1, 12))', viridis(np.linspace(0, 1, 12)))
50+
51+
##############################################################################
52+
# The colormap is a lookup table, so "oversampling" the colormap returns
53+
# nearest-neighbor interpolation (note the repeated colors in the list below)
54+
55+
print('viridis(np.linspace(0, 1, 15))', viridis(np.linspace(0, 1, 15)))
56+
57+
##############################################################################
58+
# Creating listed colormaps
59+
# =========================
60+
#
61+
# This is essential the inverse operation of the above where we supply a
62+
# Nx4 numpy array with all values between 0 and 1,
63+
# to `.ListedColormap` to make a new colormap. This means that
64+
# any numpy operations that we can do on a Nx4 array make carpentry of
65+
# new colormaps from existing colormaps quite straight forward.
66+
#
67+
# Suppose we want to make the first 25 entries of a 256-length "viridis"
68+
# colormap pink for some reason:
69+
70+
viridis = cm.get_cmap('viridis', 256)
71+
newcolors = viridis(np.linspace(0, 1, 256))
72+
pink = np.array([248/256, 24/256, 148/256, 1])
73+
newcolors[:25, :] = pink
74+
newcmp = ListedColormap(newcolors)
75+
76+
77+
def plot_examples(cms):
78+
"""
79+
helper function to plot two colormaps
80+
"""
81+
np.random.seed(19680801)
82+
data = np.random.randn(30, 30)
83+
84+
fig, axs = plt.subplots(1, 2, figsize=(6, 3), constrained_layout=True)
85+
for [ax, cmap] in zip(axs, cms):
86+
psm = ax.pcolormesh(data, cmap=cmap, rasterized=True, vmin=-4, vmax=4)
87+
fig.colorbar(psm, ax=ax)
88+
plt.show()
89+
90+
plot_examples([viridis, newcmp])
91+
92+
##############################################################################
93+
# We can easily reduce the dynamic range of a colormap; here we choose the
94+
# middle 0.5 of the colormap. However, we need to interpolate from a larger
95+
# colormap, otherwise the new colormap will have repeated values.
96+
97+
viridisBig = cm.get_cmap('viridis', 512)
98+
newcmp = ListedColormap(viridisBig(np.linspace(0.25, 0.75, 256)))
99+
plot_examples([viridis, newcmp])
100+
101+
##############################################################################
102+
# and we can easily concatenate two colormaps:
103+
104+
top = cm.get_cmap('Oranges_r', 128)
105+
bottom = cm.get_cmap('Blues', 128)
106+
107+
newcolors = np.vstack((top(np.linspace(0, 1, 128)),
108+
bottom(np.linspace(0, 1, 128))))
109+
newcmp = ListedColormap(newcolors, name='OrangeBlue')
110+
plot_examples([viridis, newcmp])
111+
112+
##############################################################################
113+
# Of course we need not start from a named colormap, we just need to create
114+
# the Nx4 array to pass to `.ListedColormap`. Here we create a
115+
# brown colormap that goes to white....
116+
117+
N = 256
118+
vals = np.ones((N, 4))
119+
vals[:, 0] = np.linspace(90/256, 1, N)
120+
vals[:, 1] = np.linspace(39/256, 1, N)
121+
vals[:, 2] = np.linspace(41/256, 1, N)
122+
newcmp = ListedColormap(vals)
123+
plot_examples([viridis, newcmp])
124+
125+
##############################################################################
126+
# Creating linear segmented colormaps
127+
# ===================================
128+
#
129+
# `.LinearSegmentedColormap` class specifies colormaps using anchor points
130+
# between which RGB(A) values are interpolated.
131+
#
132+
# The format to specify these colormaps allows discontinuities at the anchor
133+
# points. Each anchor point is specified as a row in a matrix of the
134+
# form ``[x[i] yleft[i] yright[i]]``, where ``x[i]`` is the anchor, and
135+
# ``yleft[i]`` and ``yright[i]`` are the values of the color on either
136+
# side of the anchor point.
137+
#
138+
# If there are no discontinuities, then ``yleft[i]=yright[i]``:
139+
140+
cdict = {'red': [[0.0, 0.0, 0.0],
141+
[0.5, 1.0, 1.0],
142+
[1.0, 1.0, 1.0]],
143+
'green': [[0.0, 0.0, 0.0],
144+
[0.25, 0.0, 0.0],
145+
[0.75, 1.0, 1.0],
146+
[1.0, 1.0, 1.0]],
147+
'blue': [[0.0, 0.0, 0.0],
148+
[0.5, 0.0, 0.0],
149+
[1.0, 1.0, 1.0]]}
150+
151+
152+
def plot_linearmap(cdict):
153+
newcmp = LinearSegmentedColormap('testCmap', segmentdata=cdict, N=256)
154+
rgba = newcmp(np.linspace(0, 1, 256))
155+
fig, ax = plt.subplots(figsize=(4, 3), constrained_layout=True)
156+
col = ['r', 'g', 'b']
157+
for xx in [0.25, 0.5, 0.75]:
158+
ax.axvline(xx, color='0.7', linestyle='--')
159+
for i in range(3):
160+
ax.plot(np.arange(256)/256, rgba[:, i], color=col[i])
161+
ax.set_xlabel('index')
162+
ax.set_ylabel('RGB')
163+
plt.show()
164+
165+
plot_linearmap(cdict)
166+
167+
#############################################################################
168+
# In order to make a discontinuity at an anchor point, the third column is
169+
# different than the second. The matrix for each of "red", "green", "blue",
170+
# and optionally "alpha" is set up as::
171+
#
172+
# cdict['red'] = [...
173+
# [x[i] yleft[i] yright[i]],
174+
# [x[i+1] yleft[i+1] yright[i+1]],
175+
# ...]
176+
#
177+
# and for values passed to the colormap between ``x[i]`` and ``x[i+1]``,
178+
# the interpolation is between ``yright[i]`` and ``yleft[i+1]``.
179+
#
180+
# In the example below there is a discontiuity in red at 0.5. The
181+
# interpolation between 0 and 0.5 goes from 0.3 to 1, and between 0.5 and 1
182+
# it goes from 0.9 to 1. Note that red[0, 1], and red[2, 2] are both
183+
# superfluous to the interpolation because red[0, 1] is the value to the
184+
# left of 0, and red[2, 2] is the value to the right of 1.0.
185+
186+
cdict['red'] = [[0.0, 0.0, 0.3],
187+
[0.5, 1.0, 0.9],
188+
[1.0, 1.0, 1.0]]
189+
plot_linearmap(cdict)
190+
191+
192+
#############################################################################
193+
#
194+
# ------------
195+
#
196+
# References
197+
# """"""""""
198+
#
199+
# The use of the following functions, methods, classes and modules is shown
200+
# in this example:
201+
202+
import matplotlib
203+
matplotlib.axes.Axes.pcolormesh
204+
matplotlib.figure.Figure.colorbar
205+
matplotlib.colors
206+
matplotlib.colors.LinearSegmentedColormap
207+
matplotlib.colors.ListedColormap
208+
matplotlib.cm
209+
matplotlib.cm.get_cmap

0 commit comments

Comments
 (0)