# This software is open source software available under the BSD-3 license.
#
# Copyright (c) 2020 Triad National Security, LLC. All rights reserved.
# Copyright (c) 2020 Lawrence Livermore National Security, LLC. All rights
# reserved.
# Copyright (c) 2020 UT-Battelle, LLC. All rights reserved.
#
# Additional copyright and license information can be found in the LICENSE file
# distributed with this code, or at
# https://raw.githubusercontent.com/MPAS-Dev/MPAS-Analysis/master/LICENSE
"""
Utilities for handling color maps and color bars
"""
# Authors
# -------
# Xylar Asay-Davis, Milena Veneziani, Luke Van Roekel, Greg Streletz
from __future__ import absolute_import, division, print_function, \
unicode_literals
import matplotlib.pyplot as plt
import matplotlib.colors as cols
import numpy as np
from matplotlib.colors import LinearSegmentedColormap
import xml.etree.ElementTree as ET
from six.moves import configparser
import cmocean
import pkg_resources
from six import string_types
import mpas_analysis.shared.plot.ScientificColourMaps5
[docs]def setup_colormap(config, configSectionName, suffix=''):
'''
Set up a colormap from the registry
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
configSectionName : str
name of config section
suffix: str, optional
suffix of colormap related options
Returns
-------
colormapDict : dict
A dictionary of colormap information.
'colormap' specifies the name of the new colormap
'norm' is a matplotlib norm object used to normalize the colormap
'levels' is an array of contour levels or ``None`` if not using indexed
color map
'ticks' is an array of values where ticks should be placed
'contours' is an array of contour values to plot or ``None`` if none
have been specified
'lineWidth' is the width of contour lines or ``None`` if not specified
'lineColor' is the color of contour lines or ``None`` if not specified
'''
# Authors
# -------
# Xylar Asay-Davis, Milena Veneziani, Greg Streletz
register_custom_colormaps()
colormapType = config.get(configSectionName,
'colormapType{}'.format(suffix))
if colormapType == 'indexed':
(colormap, norm, levels, ticks) = _setup_indexed_colormap(
config, configSectionName, suffix=suffix)
elif colormapType == 'continuous':
(colormap, norm, ticks) = _setup_colormap_and_norm(
config, configSectionName, suffix=suffix)
levels = None
else:
raise ValueError('config section {} option colormapType{} is not '
'"indexed" or "continuous"'.format(
configSectionName, suffix))
option = 'contourLevels{}'.format(suffix)
if config.has_option(configSectionName, option):
contours = config.getExpression(configSectionName,
option,
usenumpyfunc=True)
if isinstance(contours, string_types) and contours == 'none':
contours = None
else:
contours = None
option = 'contourThickness{}'.format(suffix)
if config.has_option(configSectionName, option):
lineWidth = config.getfloat(configSectionName, option)
else:
lineWidth = None
option = 'contourColor{}'.format(suffix)
if config.has_option(configSectionName, option):
lineColor = config.get(configSectionName, option)
else:
lineColor = None
return {'colormap': colormap, 'norm': norm, 'levels': levels,
'ticks': ticks, 'contours': contours, 'lineWidth': lineWidth,
'lineColor': lineColor}
def register_custom_colormaps():
name = 'ferret'
backgroundColor = (0.9, 0.9, 0.9)
red = np.array([[0, 0.6],
[0.15, 1],
[0.35, 1],
[0.65, 0],
[0.8, 0],
[1, 0.75]])
green = np.array([[0, 0],
[0.1, 0],
[0.35, 1],
[1, 0]])
blue = np.array([[0, 0],
[0.5, 0],
[0.9, 0.9],
[1, 0.9]])
colorCount = 21
colorList = np.ones((colorCount, 4), float)
colorList[:, 0] = np.interp(np.linspace(0, 1, colorCount),
red[:, 0], red[:, 1])
colorList[:, 1] = np.interp(np.linspace(0, 1, colorCount),
green[:, 0], green[:, 1])
colorList[:, 2] = np.interp(np.linspace(0, 1, colorCount),
blue[:, 0], blue[:, 1])
colorList = colorList[::-1, :]
colorMap = cols.LinearSegmentedColormap.from_list(
name, colorList, N=255)
colorMap.set_bad(backgroundColor)
_register_colormap_and_reverse(name, colorMap)
name = 'erdc_iceFire_H'
colorArray = np.array([
[-1, 4.05432e-07, 0, 5.90122e-06],
[-0.87451, 0, 0.120401, 0.302675],
[-0.74902, 0, 0.216583, 0.524574],
[-0.623529, 0.0552475, 0.345025, 0.6595],
[-0.498039, 0.128047, 0.492588, 0.720288],
[-0.372549, 0.188955, 0.641309, 0.792092],
[-0.247059, 0.327673, 0.784935, 0.873434],
[-0.121569, 0.60824, 0.892164, 0.935547],
[0.00392157, 0.881371, 0.912178, 0.818099],
[0.129412, 0.951407, 0.835621, 0.449279],
[0.254902, 0.904481, 0.690489, 0],
[0.380392, 0.85407, 0.510864, 0],
[0.505882, 0.777093, 0.33018, 0.00088199],
[0.631373, 0.672862, 0.139087, 0.00269398],
[0.756863, 0.508815, 0, 0],
[0.882353, 0.299417, 0.000366289, 0.000547829],
[1, 0.0157519, 0.00332021, 4.55569e-08]], float)
colorCount = 255
colorList = np.ones((colorCount, 4), float)
x = colorArray[:, 0]
for cIndex in range(3):
colorList[:, cIndex] = np.interp(
np.linspace(-1., 1., colorCount),
x, colorArray[:, cIndex + 1])
colorMap = cols.LinearSegmentedColormap.from_list(
name, colorList, N=255)
_register_colormap_and_reverse(name, colorMap)
name = 'erdc_iceFire_L'
colorArray = np.array([
[-1, 0.870485, 0.913768, 0.832905],
[-0.87451, 0.586919, 0.887865, 0.934003],
[-0.74902, 0.31583, 0.776442, 0.867858],
[-0.623529, 0.18302, 0.632034, 0.787722],
[-0.498039, 0.117909, 0.484134, 0.713825],
[-0.372549, 0.0507239, 0.335979, 0.654741],
[-0.247059, 0, 0.209874, 0.511832],
[-0.121569, 0, 0.114689, 0.28935],
[0.00392157, 0.0157519, 0.00332021, 4.55569e-08],
[0.129412, 0.312914, 0, 0],
[0.254902, 0.520865, 0, 0],
[0.380392, 0.680105, 0.15255, 0.0025996],
[0.505882, 0.785109, 0.339479, 0.000797922],
[0.631373, 0.857354, 0.522494, 0],
[0.756863, 0.910974, 0.699774, 0],
[0.882353, 0.951921, 0.842817, 0.478545],
[1, 0.881371, 0.912178, 0.818099]], float)
colorCount = 255
colorList = np.ones((colorCount, 4), float)
x = colorArray[:, 0]
for cIndex in range(3):
colorList[:, cIndex] = np.interp(
np.linspace(-1., 1., colorCount),
x, colorArray[:, cIndex + 1])
colorMap = cols.LinearSegmentedColormap.from_list(
name, colorList, N=255)
_register_colormap_and_reverse(name, colorMap)
name = 'BuOr'
colors1 = plt.cm.PuOr(np.linspace(0., 1, 256))
colors2 = plt.cm.RdBu(np.linspace(0, 1, 256))
# combine them and build a new colormap, just the orange from the first
# and the blue from the second
colorList = np.vstack((colors1[0:128, :], colors2[128:256, :]))
# reverse the order
colorList = colorList[::-1, :]
colorMap = cols.LinearSegmentedColormap.from_list(name, colorList)
_register_colormap_and_reverse(name, colorMap)
name = 'Maximenko'
colorArray = np.array([
[-1, 0., 0.45882352941, 0.76470588235],
[-0.666667, 0., 0.70196078431, 0.90588235294],
[-0.333333, 0.3294117647, 0.87058823529, 1.],
[0., 0.76470588235, 0.94509803921, 0.98039215686],
[0.333333, 1., 1., 0.],
[0.666667, 1., 0.29411764705, 0.],
[1, 1., 0., 0.]], float)
colorCount = 255
colorList = np.ones((colorCount, 4), float)
x = colorArray[:, 0]
for cIndex in range(3):
colorList[:, cIndex] = np.interp(
np.linspace(-1., 1., colorCount),
x, colorArray[:, cIndex + 1])
colorMap = cols.LinearSegmentedColormap.from_list(
name, colorList, N=255)
_register_colormap_and_reverse(name, colorMap)
# add the cmocean color maps
mapNames = list(cmocean.cm.cmapnames)
# don't bother with gray (already exists, I think)
mapNames.pop(mapNames.index('gray'))
for mapName in mapNames:
_register_colormap_and_reverse(mapName, getattr(cmocean.cm, mapName))
# add SciVisColor colormaps from
# https://sciviscolor.org/home/colormaps/
for mapName in ['3wave-yellow-grey-blue', '3Wbgy5',
'4wave-grey-red-green-mgreen', '5wave-yellow-brown-blue',
'blue-1', 'blue-3', 'blue-6', 'blue-8', 'blue-orange-div',
'brown-2', 'brown-5', 'brown-8', 'green-1', 'green-4',
'green-7', 'green-8', 'orange-5', 'orange-6',
'orange-green-blue-gray', 'purple-7', 'purple-8', 'red-1',
'red-3', 'red-4', 'yellow-1', 'yellow-7']:
xmlFile = pkg_resources.resource_filename(
__name__, 'SciVisColorColormaps/{}.xml'.format(mapName))
_read_xml_colormap(xmlFile, mapName)
name = 'white_cmo_deep'
# modify cmo.deep to start at white
colors2 = plt.cm.get_cmap('cmo.deep')(np.linspace(0, 1, 224))
colorCount = 32
colors1 = np.ones((colorCount, 4), float)
x = np.linspace(0., 1., colorCount+1)[0:-1]
white = [1., 1., 1., 1.]
for cIndex in range(4):
colors1[:, cIndex] = np.interp(x, [0., 1.],
[white[cIndex], colors2[0, cIndex]])
colors = np.vstack((colors1, colors2))
# generating a smoothly-varying LinearSegmentedColormap
cmap = LinearSegmentedColormap.from_list(name, colors)
_register_colormap_and_reverse(name, cmap)
def _setup_colormap_and_norm(config, configSectionName, suffix=''):
'''
Set up a colormap from the registry
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
configSectionName : str
name of config section
suffix: str, optional
suffix of colormap related options
Returns
-------
colormap : srt
new colormap
norm : ``mapplotlib.colors.Normalize``
the norm used to normalize the colormap
ticks : array of float
the tick marks on the colormap
'''
# Authors
# -------
# Xylar Asay-Davis
register_custom_colormaps()
colormap = plt.get_cmap(config.get(configSectionName,
'colormapName{}'.format(suffix)))
normType = config.get(configSectionName, 'normType{}'.format(suffix))
kwargs = config.getExpression(configSectionName,
'normArgs{}'.format(suffix))
if normType == 'symLog':
norm = cols.SymLogNorm(**kwargs)
elif normType == 'log':
norm = cols.LogNorm(**kwargs)
elif normType == 'linear':
norm = cols.Normalize(**kwargs)
else:
raise ValueError('Unsupported norm type {} in section {}'.format(
normType, configSectionName))
try:
ticks = config.getExpression(
configSectionName, 'colorbarTicks{}'.format(suffix),
usenumpyfunc=True)
except(configparser.NoOptionError):
ticks = None
return (colormap, norm, ticks)
def _setup_indexed_colormap(config, configSectionName, suffix=''):
'''
Set up a colormap from the registry
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
configSectionName : str
name of config section
suffix: str, optional
suffix of colormap related options
colorMapType
Returns
-------
colormap : srt
new colormap
norm : ``mapplotlib.colors.Normalize``
the norm used to normalize the colormap
ticks : array of float
the tick marks on the colormap
'''
# Authors
# -------
# Xylar Asay-Davis, Milena Veneziani, Greg Streletz
colormap = plt.get_cmap(config.get(configSectionName,
'colormapName{}'.format(suffix)))
indices = config.getExpression(configSectionName,
'colormapIndices{}'.format(suffix),
usenumpyfunc=True)
try:
levels = config.getExpression(
configSectionName, 'colorbarLevels{}'.format(suffix),
usenumpyfunc=True)
except(configparser.NoOptionError):
levels = None
if levels is not None:
# set under/over values based on the first/last indices in the colormap
underColor = colormap(indices[0])
overColor = colormap(indices[-1])
if len(levels) + 1 == len(indices):
# we have 2 extra values for the under/over so make the colormap
# without these values
indices = indices[1:-1]
elif len(levels) - 1 != len(indices):
# indices list must be either one element shorter
# or one element longer than colorbarLevels list
raise ValueError('length mismatch between indices and '
'colorbarLevels')
colormap = cols.ListedColormap(colormap(indices),
'colormapName{}'.format(suffix))
colormap.set_under(underColor)
colormap.set_over(overColor)
norm = cols.BoundaryNorm(levels, colormap.N)
try:
ticks = config.getExpression(
configSectionName, 'colorbarTicks{}'.format(suffix),
usenumpyfunc=True)
except(configparser.NoOptionError):
ticks = levels
return (colormap, norm, levels, ticks)
def _read_xml_colormap(xmlFile, mapName):
'''Read in an XML colormap'''
xml = ET.parse(xmlFile)
root = xml.getroot()
colormap = root.findall('ColorMap')
if len(colormap) > 0:
colormap = colormap[0]
colorDict = {'red': [], 'green': [], 'blue': []}
for point in colormap.findall('Point'):
x = float(point.get('x'))
color = [float(point.get('r')), float(point.get('g')),
float(point.get('b'))]
colorDict['red'].append((x, color[0], color[0]))
colorDict['green'].append((x, color[1], color[1]))
colorDict['blue'].append((x, color[2], color[2]))
cmap = LinearSegmentedColormap(mapName, colorDict, 256)
_register_colormap_and_reverse(mapName, cmap)
def _register_colormap_and_reverse(mapName, cmap):
if mapName not in plt.colormaps():
plt.register_cmap(mapName, cmap)
plt.register_cmap('{}_r'.format(mapName), cmap.reversed())
def _plot_color_gradients():
'''from https://matplotlib.org/tutorials/colors/colormaps.html'''
cmap_list = [m for m in plt.colormaps() if not m.endswith("_r")]
gradient = np.linspace(0, 1, 256)
gradient = np.vstack((gradient, gradient))
nrows = len(cmap_list)
fig, axes = plt.subplots(figsize=(7.2, 0.25 * nrows), nrows=nrows)
fig.subplots_adjust(top=0.99, bottom=0.01, left=0.35, right=0.99)
for ax, name in zip(axes, cmap_list):
ax.imshow(gradient, aspect='auto', cmap=plt.get_cmap(name))
pos = list(ax.get_position().bounds)
x_text = pos[0] - 0.01
y_text = pos[1] + pos[3] / 2.
fig.text(x_text, y_text, name, va='center', ha='right', fontsize=10)
# Turn off *all* ticks & spines, not just the ones with colormaps.
for ax in axes:
ax.set_axis_off()
plt.savefig('colormaps.png', dpi=100)
# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python