Skip to content

Commit 8201a80

Browse files
committed
Allow saving callable segmentdata, add diverging map to 'merging cmaps' demo
1 parent 0280e26 commit 8201a80

3 files changed

Lines changed: 69 additions & 26 deletions

File tree

CHANGELOG.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@ ProPlot v0.3.0 (2020-01-##)
6060
with a default ``.proplotrc`` file, change the auto-generated user ``.proplotrc``
6161
(:pr:`50`).
6262

63+
ProPlot v0.2.8 (2019-12-09)
64+
===========================
65+
.. rubric:: Deprecated
66+
67+
- Remove ``'Moisture'`` colormap (:commit:`cf8952b1`).
68+
69+
.. rubric:: Features
70+
71+
- New ``'DryWet'`` colormap is colorblind friendly (:commit:`0280e266`).
72+
73+
.. rubric:: Bug fixes
74+
75+
- Add brute force workaround for saving colormaps with *callable* segmentdata.
76+
- Save the ``cyclic`` and ``gamma`` attributes in JSON files too.
77+
6378
ProPlot v0.2.7 (2019-12-09)
6479
===========================
6580

docs/colormaps.ipynb

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@
188188
"raw_mimetype": "text/restructuredtext"
189189
},
190190
"source": [
191-
"To *merge* arbitrary colormaps, simply pass multiple positional arguments to `~proplot.styletools.Colormap`. This lets you create complex `SciVisColor <https://sciviscolor.org/home/colormoves/>`__ style colormaps right inside ProPlot, which may be desirable for complex datasets with complex statistical distributions. In the below example, we reconstruct the colormap from `this SciVisColor example <https://sciviscolor.org/wp-content/uploads/sites/14/2018/04/colormoves-icon-1.png>`__. We also *save* the result for future use by passing ``save=True`` to `~proplot.styletools.Colormap`."
191+
"To *merge* arbitrary colormaps, simply pass multiple positional arguments to `~proplot.styletools.Colormap`. This lets you create arbitrary diverging colormaps and segmented `SciVisColor <https://sciviscolor.org/home/colormoves/>`__ style colormaps right inside ProPlot. Segmented colormaps are often desirable for complex datasets with complex statistical distributions. In the below example, we create a new divering colormap and reconstruct the segmented colormap from `this SciVisColor example <https://sciviscolor.org/wp-content/uploads/sites/14/2018/04/colormoves-icon-1.png>`__. We also *save* the results for future use by passing ``save=True`` to `~proplot.styletools.Colormap`."
192192
]
193193
},
194194
{
@@ -199,23 +199,26 @@
199199
"source": [
200200
"import proplot as plot\n",
201201
"import numpy as np\n",
202-
"f, axs = plot.subplots(ncols=2, axwidth=2, span=False)\n",
202+
"f, axs = plot.subplots(ncols=3, axwidth=2, span=False)\n",
203203
"state = np.random.RandomState(51423)\n",
204-
"data = state.rand(100, 100).cumsum(axis=1)\n",
205-
"# Save colormap as \"test1.json\"\n",
206-
"cmap = plot.Colormap('Green1_r', 'Orange5', 'Blue1_r',\n",
207-
" 'Blue6', name='test1', save=True)\n",
208-
"m = axs[0].contourf(data, cmap=cmap, levels=100)\n",
209-
"f.colorbar(m, loc='b', col=1, locator='none')\n",
210-
"# Save colormap as \"test2.json\"\n",
211-
"cmap = plot.Colormap('Green1_r', 'Orange5', 'Blue1_r',\n",
212-
" 'Blue6', ratios=(1, 3, 5, 10), name='test2', save=True)\n",
213-
"m = axs[1].contourf(data, cmap=cmap, levels=100)\n",
214-
"f.colorbar(m, loc='b', col=2, locator='none')\n",
204+
"data = state.rand(50, 50).cumsum(axis=1)\n",
205+
"# Diverging colormap example\n",
206+
"title1 = 'Custom diverging map'\n",
207+
"cmap1 = plot.Colormap('Blue2_r', 'RedPurple1', name='Diverging', save=True)\n",
208+
"# SciVisColor examples\n",
209+
"title2 = 'Custom complex map'\n",
210+
"cmap2 = plot.Colormap('Green1_r', 'Orange5', 'Blue1_r',\n",
211+
" 'Blue6', name='Complex', save=True)\n",
212+
"title3 = 'SciVisColor example reproduction'\n",
213+
"cmap3 = plot.Colormap('Green1_r', 'Orange5', 'Blue1_r',\n",
214+
" 'Blue6', ratios=(1, 3, 5, 10), name='SciVisColor', save=True)\n",
215+
"# Plot examples\n",
216+
"for ax, cmap, title in zip(axs, (cmap1, cmap2, cmap3), (title1, title2, title3)):\n",
217+
" m = ax.contourf(data, cmap=cmap, levels=256)\n",
218+
" ax.colorbar(m, loc='b', locator='null', label=cmap.name)\n",
219+
" ax.format(title=title)\n",
215220
"axs.format(xlabel='xlabel', ylabel='ylabel',\n",
216-
" suptitle='Merging existing colormaps')\n",
217-
"for ax, title in zip(axs, ['Evenly spaced', 'Matching SciVisColor example']):\n",
218-
" ax.format(title=title)"
221+
" suptitle='Merging existing colormaps')"
219222
]
220223
},
221224
{

proplot/styletools.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,12 @@
256256
gamma1 : float, optional
257257
If >1, makes low saturation colors more prominent. If <1,
258258
makes high saturation colors more prominent. Similar to the
259-
`HCLWizard <http://hclwizard.org:64230/hclwizard/>`_ option.
259+
`HCLWizard <http://hclwizard.org:64230/hclwizard/>`__ option.
260260
See `make_mapping_array` for details.
261261
gamma2 : float, optional
262262
If >1, makes high luminance colors more prominent. If <1,
263263
makes low luminance colors more prominent. Similar to the
264-
`HCLWizard <http://hclwizard.org:64230/hclwizard/>`_ option.
264+
`HCLWizard <http://hclwizard.org:64230/hclwizard/>`__ option.
265265
See `make_mapping_array` for details.
266266
gamma : float, optional
267267
Use this to identically set `gamma1` and `gamma2` at once.
@@ -611,6 +611,16 @@ def make_mapping_array(N, data, gamma=1.0, inverse=False):
611611
:math:`w_i` ranges from 0 to 1 between rows ``i`` and ``i+1``.
612612
If `gamma` is float, it applies to every transition. Otherwise,
613613
its length must equal ``data.shape[0]-1``.
614+
615+
This is like the `gamma` used with matplotlib's
616+
`~matplotlib.colors.makeMappingArray`, except it controls the
617+
weighting for transitions *between* each segment data coordinate rather
618+
than the coordinates themselves. This makes more sense for
619+
`PerceptuallyUniformColormap`\\ s because they usually consist of just
620+
one linear transition for *sequential* colormaps and two linear
621+
transitions for *diverging* colormaps -- and in the latter case, it
622+
is often desirable to modify both "halves" of the colormap in the
623+
same way.
614624
inverse : bool, optional
615625
If ``True``, :math:`w_i^{\gamma_i}` is replaced with
616626
:math:`1 - (1 - w_i)^{\gamma_i}` -- that is, when `gamma` is greater
@@ -698,7 +708,6 @@ def make_mapping_array(N, data, gamma=1.0, inverse=False):
698708

699709
class _Colormap():
700710
"""Mixin class used to add some helper methods."""
701-
702711
def _get_data(self, ext):
703712
"""
704713
Returns a string containing the colormap colors for saving.
@@ -1048,13 +1057,29 @@ def save(self, path=None):
10481057
# Save channel segment data in json file
10491058
_, ext = os.path.splitext(filename)
10501059
if ext[1:] == 'json':
1060+
# Sanitize segmentdata values
1061+
# Convert np.float to builtin float, np.array to list of lists,
1062+
# and callable to list of lists. We tried encoding func.__code__
1063+
# with base64 and marshal instead, but when cmap.concatenate()
1064+
# embeds functions as keyword arguments, this seems to make it
1065+
# *impossible* to load back up the function with FunctionType
1066+
# (error message: arg 5 (closure) must be tuple). Instead use
1067+
# this brute force workaround.
10511068
data = {}
10521069
for key, value in self._segmentdata.items():
1053-
# from np.float to builtin float, and to list of lists
1054-
data[key] = np.array(value).astype(float).tolist()
1070+
if callable(value):
1071+
x = np.linspace(0, 1, 256) # just save the transitions
1072+
y = np.array([value(_) for _ in x]).squeeze()
1073+
value = np.vstack((x, y, y)).T
1074+
data[key] = np.asarray(value).astype(float).tolist()
1075+
# Add critical attributes to the dictionary
1076+
keys = ()
10551077
if isinstance(self, PerceptuallyUniformColormap):
1056-
for key in ('space', 'gamma1', 'gamma2'):
1057-
data[key] = getattr(self, '_' + key)
1078+
keys = ('cyclic', 'gamma1', 'gamma2', 'space')
1079+
elif isinstance(self, LinearSegmentedColormap):
1080+
keys = ('cyclic', 'gamma')
1081+
for key in keys:
1082+
data[key] = getattr(self, '_' + key)
10581083
with open(filename, 'w') as file:
10591084
json.dump(data, file, indent=4)
10601085
# Save lookup table colors
@@ -2681,12 +2706,12 @@ def _load_cmap_cycle(filename, cmap=False):
26812706
if ext == 'json':
26822707
with open(filename, 'r') as f:
26832708
data = json.load(f)
2709+
kw = {}
2710+
for key in ('cyclic', 'gamma', 'gamma1', 'gamma2', 'space'):
2711+
kw[key] = data.pop(key, None)
26842712
if 'red' in data:
26852713
data = LinearSegmentedColormap(name, data, N=N)
26862714
else:
2687-
kw = {}
2688-
for key in ('space', 'gamma1', 'gamma2'):
2689-
kw[key] = data.pop(key, None)
26902715
data = PerceptuallyUniformColormap(name, data, N=N, **kw)
26912716
if name[-2:] == '_r':
26922717
data = data.reversed(name[:-2])

0 commit comments

Comments
 (0)