# Copyright (c) 2017, Los Alamos National Security, LLC (LANS)
# and the University Corporation for Atmospheric Research (UCAR).
#
# Unless noted otherwise source code is licensed under the BSD license.
# Additional copyright and license information can be found in the LICENSE file
# distributed with this code, or at http://mpas-dev.github.com/license.html
#
"""
Plotting utilities, including routines for plotting:
* time series (and comparing with reference data sets)
* remapped horizontal fields (and comparing with reference data sets)
* vertical sections on native grid
* NINO34 time series and spectra
"""
# Authors
# -------
# Xylar Asay-Davis, Milena Veneziani, Luke Van Roekel, Greg Streletz
from __future__ import absolute_import, division, print_function, \
unicode_literals
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.colors as cols
import xarray as xr
import pandas as pd
from mpl_toolkits.basemap import Basemap
from matplotlib.ticker import FuncFormatter, FixedLocator
import numpy as np
from functools import partial
from mpl_toolkits.axes_grid1 import make_axes_locatable
from mpas_analysis.shared.timekeeping.utility import days_to_datetime, \
date_to_days
from mpas_analysis.shared.constants import constants
from six.moves import configparser
import cmocean
[docs]def timeseries_analysis_plot(config, dsvalues, N, title, xlabel, ylabel,
fileout, calendar, lineColors=None,
lineStyles=None, markers=None, lineWidths=None,
legendText=None, maxPoints=None,
titleFontSize=None, figsize=(15, 6), dpi=None,
firstYearXTicks=None, yearStrideXTicks=None,
maxXTicks=20, obsMean=None, obsUncertainty=None,
obsLegend=None, legendLocation='lower left'):
"""
Plots the list of time series data sets and stores the result in an image
file.
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
dsvalues : list of xarray DataSets
the data set(s) to be plotted
N : int
the numer of time points over which to perform a moving average
title : str
the title of the plot
xlabel, ylabel : str
axis labels
fileout : str
the file name to be written
calendar : str
the calendar to use for formatting the time axis
lineColors, lineStyles, markers, legendText : list of str, optional
control line color, style, marker, and corresponding legend
text. Default is black, solid line with no marker, and no legend.
lineWidths : list of float, optional
control line width. Default is 1.0.
maxPoints : list of {None, int}, optional
the approximate maximum number of time points to use in a time series.
This can be helpful for reducing the number of symbols plotted if
plotting with markers. Otherwise the markers become indistinguishable
from each other.
titleFontSize : int, optional
the size of the title font
figsize : tuple of float, optional
the size of the figure in inches
dpi : int, optional
the number of dots per inch of the figure, taken from section ``plot``
option ``dpi`` in the config file by default
firstYearXTicks : int, optional
The year of the first tick on the x axis. By default, the first time
entry is the first tick.
yearStrideXTicks : int, optional
The number of years between x ticks. By default, the stride is chosen
automatically to have ``maxXTicks`` tick marks or fewer.
maxXTicks : int, optional
the maximum number of tick marks that will be allowed along the x axis.
This may need to be adjusted depending on the figure size and aspect
ratio.
obsMean, obsUncertainty : list of float, optional
Mean values and uncertainties for observations to be plotted as error
bars. The two lists must have the same number of elements.
obsLegend : list of str, optional
The label in the legend for each element in ``obsMean`` (and
``obsUncertainty``)
legendLocation : str, optional
The location of the legend (see ``pyplot.legend()`` for details)
"""
# Authors
# -------
# Xylar Asay-Davis, Milena Veneziani, Stephen Price
if dpi is None:
dpi = config.getint('plot', 'dpi')
plt.figure(figsize=figsize, dpi=dpi)
minDays = []
maxDays = []
plotLegend = False
for dsIndex in range(len(dsvalues)):
dsvalue = dsvalues[dsIndex]
if dsvalue is None:
continue
if N == 1 or N is None:
mean = dsvalue
else:
mean = pd.Series.rolling(dsvalue.to_pandas(), N,
center=True).mean()
mean = xr.DataArray.from_series(mean)
minDays.append(mean.Time.min())
maxDays.append(mean.Time.max())
if maxPoints is not None and maxPoints[dsIndex] is not None:
nTime = mean.sizes['Time']
if maxPoints[dsIndex] < nTime:
stride = int(round(nTime/float(maxPoints[dsIndex])))
mean = mean.isel(Time=slice(0, None, stride))
if legendText is None:
label = None
else:
label = legendText[dsIndex]
plotLegend = True
if lineColors is None:
color = 'k'
else:
color = lineColors[dsIndex]
if lineStyles is None:
linestyle = '-'
else:
linestyle = lineStyles[dsIndex]
if markers is None:
marker = None
else:
marker = markers[dsIndex]
if lineWidths is None:
linewidth = 1.
else:
linewidth = lineWidths[dsIndex]
plt.plot(mean['Time'].values, mean.values, color=color,
linestyle=linestyle, marker=marker, linewidth=linewidth,
label=label)
if obsMean is not None:
obsCount = len(obsMean)
assert(len(obsUncertainty) == obsCount)
# space the observations along the time line, leaving gaps at either
# end
start = np.amin(minDays)
end = np.amax(maxDays)
obsTimes = np.linspace(start, end, obsCount+2)[1:-1]
obsSymbols = ['o', '^', 's', 'D', '*']
for iObs in range(obsCount):
if obsMean[iObs] is not None:
plt.errorbar(obsTimes[iObs], obsMean[iObs],
yerr=obsUncertainty[iObs],
fmt=obsSymbols[np.mod(iObs, len(obsSymbols))],
ecolor='k',
capthick=2, label=obsLegend[iObs])
if plotLegend and len(dsvalues) > 1:
plt.legend(loc=legendLocation)
ax = plt.gca()
if titleFontSize is None:
titleFontSize = config.get('plot', 'titleFontSize')
axis_font = {'size': config.get('plot', 'axisFontSize')}
title_font = {'size': titleFontSize,
'color': config.get('plot', 'titleFontColor'),
'weight': config.get('plot', 'titleFontWeight')}
if firstYearXTicks is not None:
minDays = date_to_days(year=firstYearXTicks, calendar=calendar)
plot_xtick_format(calendar, minDays, maxDays, maxXTicks,
yearStride=yearStrideXTicks)
# Add a y=0 line if y ranges between positive and negative values
yaxLimits = ax.get_ylim()
if yaxLimits[0]*yaxLimits[1] < 0:
x = ax.get_xlim()
plt.plot(x, np.zeros(np.size(x)), 'k-', linewidth=1.2, zorder=1)
if title is not None:
plt.title(title, **title_font)
if xlabel is not None:
plt.xlabel(xlabel, **axis_font)
if ylabel is not None:
plt.ylabel(ylabel, **axis_font)
if fileout is not None:
plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1)
if not config.getboolean('plot', 'displayToScreen'):
plt.close()
[docs]def timeseries_analysis_plot_polar(config, dsvalues, N, title,
fileout, lineColors=None, lineStyles=None,
markers=None, lineWidths=None,
legendText=None, titleFontSize=None,
figsize=(15, 6), dpi=None):
"""
Plots the list of time series data sets on a polar plot and stores
the result in an image file.
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
dsvalues : list of xarray DataSets
the data set(s) to be plotted
N : int
the numer of time points over which to perform a moving average
title : str
the title of the plot
fileout : str
the file name to be written
lineColors, lineStyles, markers, legendText : list of str, optional
control line color, style, marker, and corresponding legend
text. Default is black, solid line with no marker, and no legend.
lineWidths : list of float, optional
control line width. Default is 1.0.
titleFontSize : int, optional
the size of the title font
figsize : tuple of float, optional
the size of the figure in inches
dpi : int, optional
the number of dots per inch of the figure, taken from section ``plot``
option ``dpi`` in the config file by default
"""
# Authors
# -------
# Adrian K. Turner, Xylar Asay-Davis
if dpi is None:
dpi = config.getint('plot', 'dpi')
plt.figure(figsize=figsize, dpi=dpi)
minDays = []
maxDays = []
plotLegend = False
for dsIndex in range(len(dsvalues)):
dsvalue = dsvalues[dsIndex]
if dsvalue is None:
continue
mean = pd.Series.rolling(dsvalue.to_pandas(), N, center=True).mean()
mean = xr.DataArray.from_series(mean)
minDays.append(mean.Time.min())
maxDays.append(mean.Time.max())
if legendText is None:
label = None
else:
label = legendText[dsIndex]
plotLegend = True
if lineColors is None:
color = 'k'
else:
color = lineColors[dsIndex]
if lineStyles is None:
linestyle = '-'
else:
linestyle = lineStyles[dsIndex]
if markers is None:
marker = None
else:
marker = markers[dsIndex]
if lineWidths is None:
linewidth = 1.
else:
linewidth = lineWidths[dsIndex]
plt.polar((mean['Time']/365.0)*np.pi*2.0, mean, color=color,
linestyle=linestyle, marker=marker, linewidth=linewidth,
label=label)
if plotLegend and len(dsvalues) > 1:
plt.legend(loc='lower left')
ax = plt.gca()
# set azimuthal axis formatting
majorTickLocs = np.zeros(12)
minorTickLocs = np.zeros(12)
majorTickLocs[0] = 0.0
minorTickLocs[0] = (constants.daysInMonth[0] * np.pi) / 365.0
for month in range(1, 12):
majorTickLocs[month] = majorTickLocs[month-1] + \
((constants.daysInMonth[month-1] * np.pi * 2.0) / 365.0)
minorTickLocs[month] = minorTickLocs[month-1] + \
(((constants.daysInMonth[month-1] +
constants.daysInMonth[month]) * np.pi) / 365.0)
ax.set_xticks(majorTickLocs)
ax.set_xticklabels([])
ax.set_xticks(minorTickLocs, minor=True)
ax.set_xticklabels(constants.abrevMonthNames, minor=True)
if titleFontSize is None:
titleFontSize = config.get('plot', 'titleFontSize')
title_font = {'size': titleFontSize,
'color': config.get('plot', 'titleFontColor'),
'weight': config.get('plot', 'titleFontWeight')}
if title is not None:
plt.title(title, **title_font)
if fileout is not None:
plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1)
if not config.getboolean('plot', 'displayToScreen'):
plt.close()
[docs]def plot_polar_comparison(
config,
Lons,
Lats,
modelArray,
refArray,
diffArray,
colorMapSectionName,
fileout,
title=None,
plotProjection='npstere',
latmin=50.0,
lon0=0,
modelTitle='Model',
refTitle='Observations',
diffTitle='Model-Observations',
cbarlabel='units',
titleFontSize=None,
figsize=None,
dpi=None,
vertical=False):
"""
Plots a data set around either the north or south pole.
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
Lons, Lats : float arrays
longitude and latitude arrays
modelArray, refArray : float arrays
model and observational or reference run data sets
diffArray : float array
difference between modelArray and refArray
colorMapSectionName : str
section name in ``config`` where color map info can be found.
fileout : str
the file name to be written
title : str, optional
the subtitle of the plot
plotProjection : str, optional
Basemap projection for the plot
modelTitle : str, optional
title of the model panel
refTitle : str, optional
title of the observations or reference run panel
diffTitle : str, optional
title of the difference (bias) panel
cbarlabel : str, optional
label on the colorbar
titleFontSize : int, optional
size of the title font
figsize : tuple of float, optional
the size of the figure in inches. If ``None``, the figure size is
``(8, 22)`` if ``vertical == True`` and ``(22, 8)`` otherwise.
dpi : int, optional
the number of dots per inch of the figure, taken from section ``plot``
option ``dpi`` in the config file by default
vertical : bool, optional
whether the subplots should be stacked vertically rather than
horizontally
"""
# Authors
# -------
# Xylar Asay-Davis, Milena Veneziani
def do_subplot(ax, field, title, colormap, norm, levels, ticks, contours,
lineWidth, lineColor):
"""
Make a subplot within the figure.
"""
m = Basemap(projection=plotProjection, boundinglat=latmin,
lon_0=lon0, resolution='l', ax=ax)
x, y = m(Lons, Lats) # compute map proj coordinates
ax.set_title(title, y=1.06, **axis_font)
m.drawcoastlines()
m.fillcontinents(color='grey', lake_color='white')
m.drawparallels(np.arange(-80., 81., 10.))
m.drawmeridians(np.arange(-180., 181., 20.),
labels=[True, False, True, True])
if levels is None:
plotHandle = m.pcolormesh(x, y, field, cmap=colormap, norm=norm)
else:
plotHandle = m.contourf(x, y, field, cmap=colormap, norm=norm,
levels=levels)
if contours is not None:
matplotlib.rcParams['contour.negative_linestyle'] = 'solid'
m.contour(x, y, field, levels=contours, colors=lineColor,
linewidths=lineWidth)
cbar = m.colorbar(plotHandle, location='right', pad="3%",
spacing='uniform', ticks=ticks, boundaries=levels)
cbar.set_label(cbarlabel)
if dpi is None:
dpi = config.getint('plot', 'dpi')
dictModelRef = setup_colormap(config, colorMapSectionName, suffix='Result')
dictDiff = setup_colormap(config, colorMapSectionName, suffix='Difference')
if refArray is None:
if figsize is None:
figsize = (8, 8.5)
subplots = [111]
elif vertical:
if figsize is None:
figsize = (8, 22)
subplots = [311, 312, 313]
else:
if figsize is None:
figsize = (22, 8.5)
subplots = [131, 132, 133]
fig = plt.figure(figsize=figsize, dpi=dpi)
if (title is not None):
if titleFontSize is None:
titleFontSize = config.get('plot', 'titleFontSize')
title_font = {'size': titleFontSize,
'color': config.get('plot', 'titleFontColor'),
'weight': config.get('plot', 'titleFontWeight')}
fig.suptitle(title, y=0.95, **title_font)
axis_font = {'size': config.get('plot', 'axisFontSize')}
ax = plt.subplot(subplots[0])
do_subplot(ax=ax, field=modelArray, title=modelTitle, **dictModelRef)
if refArray is not None:
ax = plt.subplot(subplots[1])
do_subplot(ax=ax, field=refArray, title=refTitle, **dictModelRef)
ax = plt.subplot(subplots[2])
do_subplot(ax=ax, field=diffArray, title=diffTitle, **dictDiff)
plt.tight_layout(pad=4.)
if vertical:
plt.subplots_adjust(top=0.9)
if (fileout is not None):
plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1)
if not config.getboolean('plot', 'displayToScreen'):
plt.close()
[docs]def plot_global_comparison(
config,
Lons,
Lats,
modelArray,
refArray,
diffArray,
colorMapSectionName,
fileout,
title=None,
modelTitle='Model',
refTitle='Observations',
diffTitle='Model-Observations',
cbarlabel='units',
titleFontSize=None,
figsize=None,
dpi=None,
lineWidth=1,
lineColor='black'):
"""
Plots a data set as a longitude/latitude map.
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
Lons, Lats : float arrays
longitude and latitude arrays
modelArray, refArray : float arrays
model and observational or reference run data sets
diffArray : float array
difference between modelArray and refArray
colorMapSectionName : str
section name in ``config`` where color map info can be found.
fileout : str
the file name to be written
title : str, optional
the subtitle of the plot
modelTitle : str, optional
title of the model panel
refTitle : str, optional
title of the observations or reference run panel
diffTitle : str, optional
title of the difference (bias) panel
cbarlabel : str, optional
label on the colorbar
titleFontSize : int, optional
size of the title font
figsize : tuple of float, optional
the size of the figure in inches
dpi : int, optional
the number of dots per inch of the figure, taken from section ``plot``
option ``dpi`` in the config file by default
lineWidth : int, optional
the line width of contour lines (if specified)
lineColor : str, optional
the color contour lines (if specified)
"""
# Authors
# -------
# Xylar Asay-Davis, Milena Veneziani
def plot_panel(title, array, colormap, norm, levels, ticks, contours,
lineWidth, lineColor):
plt.title(title, y=1.06, **axis_font)
m.drawcoastlines()
m.fillcontinents(color='grey', lake_color='white')
m.drawparallels(np.arange(-80., 80., 20.),
labels=[True, False, False, False])
m.drawmeridians(np.arange(-180., 180., 60.),
labels=[False, False, False, True])
if levels is None:
plotHandle = m.pcolormesh(x, y, array, cmap=colormap, norm=norm)
else:
plotHandle = m.contourf(x, y, array, cmap=colormap, norm=norm,
levels=levels, extend='both')
if contours is not None:
matplotlib.rcParams['contour.negative_linestyle'] = 'solid'
m.contour(x, y, array, levels=contours, colors=lineColor,
linewidths=lineWidth)
cbar = m.colorbar(plotHandle, location='right', pad="5%",
spacing='uniform', ticks=ticks, boundaries=ticks)
cbar.set_label(cbarlabel)
# set up figure
if dpi is None:
dpi = config.getint('plot', 'dpi')
if figsize is None:
# set the defaults, depending on if we have 1 or 3 panels
if refArray is None:
figsize = (8, 5)
else:
figsize = (8, 13)
fig = plt.figure(figsize=figsize, dpi=dpi)
if (title is not None):
if titleFontSize is None:
titleFontSize = config.get('plot', 'titleFontSize')
title_font = {'size': titleFontSize,
'color': config.get('plot', 'titleFontColor'),
'weight': config.get('plot', 'titleFontWeight')}
fig.suptitle(title, y=0.95, **title_font)
axis_font = {'size': config.get('plot', 'axisFontSize')}
m = Basemap(projection='cyl', llcrnrlat=-85, urcrnrlat=86, llcrnrlon=-180,
urcrnrlon=181, resolution='l')
x, y = m(Lons, Lats) # compute map proj coordinates
dictModelRef = setup_colormap(config, colorMapSectionName, suffix='Result')
dictDiff = setup_colormap(config, colorMapSectionName, suffix='Difference')
if refArray is not None:
plt.subplot(3, 1, 1)
plot_panel(modelTitle, modelArray, **dictModelRef)
if refArray is not None:
plt.subplot(3, 1, 2)
plot_panel(refTitle, refArray, **dictModelRef)
plt.subplot(3, 1, 3)
plot_panel(diffTitle, diffArray, **dictDiff)
if (fileout is not None):
plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1)
if not config.getboolean('plot', 'displayToScreen'):
plt.close()
def plot_polar_projection_comparison(
config,
x,
y,
landMask,
modelArray,
refArray,
diffArray,
fileout,
colorMapSectionName,
title=None,
modelTitle='Model',
refTitle='Observations',
diffTitle='Model-Observations',
cbarlabel='units',
titleFontSize=None,
figsize=None,
dpi=None,
vertical=False):
"""
Plots a data set as a longitude/latitude map.
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
x, y : numpy ndarrays
1D x and y arrays defining the projection grid
landMask : numpy ndarrays
model and observational or reference run data sets
modelArray, refArray : numpy ndarrays
model and observational or reference run data sets
diffArray : float array
difference between modelArray and refArray
fileout : str
the file name to be written
colorMapSectionName : str
section name in ``config`` where color map info can be found.
title : str, optional
the subtitle of the plot
plotProjection : str, optional
Basemap projection for the plot
modelTitle : str, optional
title of the model panel
refTitle : str, optional
title of the observations or reference run panel
diffTitle : str, optional
title of the difference (bias) panel
cbarlabel : str, optional
label on the colorbar
titleFontSize : int, optional
size of the title font
figsize : tuple of float, optional
the size of the figure in inches. If ``None``, the figure size is
``(8, 22)`` if ``vertical == True`` and ``(22, 8)`` otherwise.
dpi : int, optional
the number of dots per inch of the figure, taken from section ``plot``
option ``dpi`` in the config file by default
vertical : bool, optional
whether the subplots should be stacked vertically rather than
horizontally
"""
# Authors
# -------
# Xylar Asay-Davis
def plot_panel(ax, title, array, colormap, norm, levels, ticks, contours,
lineWidth, lineColor):
plt.title(title, y=1.06, **axis_font)
if levels is None:
plotHandle = plt.pcolormesh(x, y, array, cmap=colormap, norm=norm)
else:
plotHandle = plt.contourf(xCenter, yCenter, array, cmap=colormap,
norm=norm, levels=levels, extend='both')
plt.pcolormesh(x, y, landMask, cmap=landColorMap)
plt.contour(xCenter, yCenter, landMask.mask, (0.5,), colors='k',
linewidths=0.5)
if contours is not None:
matplotlib.rcParams['contour.negative_linestyle'] = 'solid'
plt.contour(x, y, array, levels=contours, colors=lineColor,
linewidths=lineWidth)
# create an axes on the right side of ax. The width of cax will be 5%
# of ax and the padding between cax and ax will be fixed at 0.05 inch.
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
cbar = plt.colorbar(plotHandle, cax=cax)
cbar.set_label(cbarlabel)
if ticks is not None:
cbar.set_ticks(ticks)
cbar.set_ticklabels(['{}'.format(tick) for tick in ticks])
ax.axis('off')
ax.set_aspect('equal')
ax.autoscale(tight=True)
# set up figure
if dpi is None:
dpi = config.getint('plot', 'dpi')
if refArray is None:
if figsize is None:
figsize = (8, 7.5)
subplots = [111]
elif vertical:
if figsize is None:
figsize = (8, 22)
subplots = [311, 312, 313]
else:
if figsize is None:
figsize = (22, 7.5)
subplots = [131, 132, 133]
dictModelRef = setup_colormap(config, colorMapSectionName, suffix='Result')
dictDiff = setup_colormap(config, colorMapSectionName, suffix='Difference')
# set up figure
fig = plt.figure(figsize=figsize, dpi=dpi)
if (title is not None):
if titleFontSize is None:
titleFontSize = config.get('plot', 'titleFontSize')
title_font = {'size': titleFontSize,
'color': config.get('plot', 'titleFontColor'),
'weight': config.get('plot', 'titleFontWeight')}
fig.suptitle(title, y=0.95, **title_font)
axis_font = {'size': config.get('plot', 'axisFontSize')}
# set up land colormap
colorList = [(0.8, 0.8, 0.8), (0.8, 0.8, 0.8)]
landColorMap = cols.LinearSegmentedColormap.from_list('land', colorList)
# locations of centers for contour plots
xCenter = 0.5*(x[1:] + x[0:-1])
yCenter = 0.5*(y[1:] + y[0:-1])
ax = plt.subplot(subplots[0])
plot_panel(ax, modelTitle, modelArray, **dictModelRef)
if refArray is not None:
ax = plt.subplot(subplots[1])
plot_panel(ax, refTitle, refArray, **dictModelRef)
ax = plt.subplot(subplots[2])
plot_panel(ax, diffTitle, diffArray, **dictDiff)
if (fileout is not None):
plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1)
if not config.getboolean('plot', 'displayToScreen'):
plt.close()
[docs]def plot_1D(config, xArrays, fieldArrays, errArrays,
lineColors=None, lineStyles=None, markers=None, lineWidths=None,
legendText=None, title=None, xlabel=None, ylabel=None,
fileout='plot_1D.png',
figsize=(10, 4), dpi=None,
xLim=None,
yLim=None,
invertYAxis=False): # {{{
"""
Plots a 1D line plot with error bars if available.
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
xArrays : list of float arrays
x array (latitude, or any other x axis except time)
fieldArrays : list of float arrays
y array (any field as function of x)
errArrays : list of float arrays
error array (y errors)
lineColors, lineStyles, markers, legendText : list of str, optional
control line color, style, marker, and corresponding legend
text. Default is black, solid line with no marker, and no legend.
lineWidths : list of float, optional
control line width. Default is 1.0.
title : str, optional
title of plot
xlabel, ylabel : str, optional
label of x- and y-axis
fileout : str, optional
the file name to be written
figsize : tuple of float, optional
size of the figure in inches
dpi : int, optional
the number of dots per inch of the figure, taken from section ``plot``
option ``dpi`` in the config file by default
xLim : float array, optional
x range of plot
yLim : float array, optional
y range of plot
invertYAxis : logical, optional
if True, invert Y axis
"""
# Authors
# -------
# Mark Petersen, Milena Veneziani
# set up figure
if dpi is None:
dpi = config.getint('plot', 'dpi')
plt.figure(figsize=figsize, dpi=dpi)
plotLegend = False
for dsIndex in range(len(xArrays)):
xArray = xArrays[dsIndex]
fieldArray = fieldArrays[dsIndex]
errArray = errArrays[dsIndex]
if xArray is None:
continue
if legendText is None:
label = None
else:
label = legendText[dsIndex]
plotLegend = True
if lineColors is None:
color = 'k'
else:
color = lineColors[dsIndex]
if markers is None:
marker = None
else:
marker = markers[dsIndex]
if lineStyles is None:
linestyle = '-'
else:
linestyle = lineStyles[dsIndex]
if lineWidths is None:
linewidth = 1.
else:
linewidth = lineWidths[dsIndex]
plt.plot(xArray, fieldArray, color=color, linestyle=linestyle,
marker=marker, linewidth=linewidth, label=label)
if errArray is not None:
plt.fill_between(xArray, fieldArray, fieldArray+errArray,
facecolor=color, alpha=0.2)
plt.fill_between(xArray, fieldArray, fieldArray-errArray,
facecolor=color, alpha=0.2)
plt.grid()
plt.axhline(0.0, linestyle='-', color='k') # horizontal lines
if plotLegend and len(xArrays) > 1:
plt.legend()
axis_font = {'size': config.get('plot', 'axisFontSize')}
title_font = {'size': config.get('plot', 'titleFontSize'),
'color': config.get('plot', 'titleFontColor'),
'weight': config.get('plot', 'titleFontWeight')}
if title is not None:
plt.title(title, **title_font)
if xlabel is not None:
plt.xlabel(xlabel, **axis_font)
if ylabel is not None:
plt.ylabel(ylabel, **axis_font)
if invertYAxis:
plt.gca().invert_yaxis()
if xLim:
plt.xlim(xLim)
if yLim:
plt.ylim(yLim)
if (fileout is not None):
plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1)
if not config.getboolean('plot', 'displayToScreen'):
plt.close()
return # }}}
[docs]def plot_vertical_section(
config,
xArray,
depthArray,
fieldArray,
colorMapSectionName,
suffix='',
colorbarLabel=None,
title=None,
xlabel=None,
ylabel=None,
fileout='moc.png',
figsize=(10, 4),
dpi=None,
xLim=None,
yLim=None,
linewidths=2,
invertYAxis=True,
xArrayIsTime=False,
N=None,
firstYearXTicks=None,
yearStrideXTicks=None,
maxXTicks=20,
calendar='gregorian'): # {{{
"""
Plots a data set as a x distance (latitude, longitude,
or spherical distance) vs depth map (vertical section).
Or, if xArrayIsTime is True, plots data set on a vertical
Hovmoller plot (depth vs. time).
Parameters
----------
config : instance of ConfigParser
the configuration, containing a [plot] section with options that
control plotting
xArray : float array
x array (latitude, longitude, or spherical distance; or, time for a
Hovmoller plot)
depthArray : float array
depth array [m]
fieldArray : float array
field array to plot
colorMapSectionName : str
section name in ``config`` where color map info can be found.
suffix : str, optional
the suffix used for colorbar config options
title : str, optional
title of plot
xlabel, ylabel : str, optional
label of x- and y-axis
fileout : str, optional
the file name to be written
figsize : tuple of float, optional
size of the figure in inches
dpi : int, optional
the number of dots per inch of the figure, taken from section ``plot``
option ``dpi`` in the config file by default
xLim : float array, optional
x range of plot
yLim : float array, optional
y range of plot
linewidths : int, optional
linewidths for contours
invertYAxis : logical, optional
if True, invert Y axis
xArrayIsTime : logical, optional
if True, format X axis for time
N : int, optional
the number of points over which to perform a moving average
NOTE: this option is mostly intended for use when xArrayIsTime is True,
although it will work with other data as well. Also, the moving
average calculation is based on number of points, not actual x axis
values, so for best results, the values in the xArray should be equally
spaced.
firstYearXTicks : int, optional
The year of the first tick on the x axis. By default, the first time
entry is the first tick.
yearStrideXTicks : int, optional
The number of years between x ticks. By default, the stride is chosen
automatically to have ``maxXTicks`` tick marks or fewer.
maxXTicks : int, optional
the maximum number of tick marks that will be allowed along the x axis.
This may need to be adjusted depending on the figure size and aspect
ratio. NOTE: maxXTicks is only used if xArrayIsTime is True
calendar : str, optional
the calendar to use for formatting the time axis
NOTE: calendar is only used if xArrayIsTime is True
"""
# Authors
# -------
# Milena Veneziani, Mark Petersen, Xylar Asay-Davis, Greg Streletz
# verify that the dimensions of fieldArray are consistent with those of
# xArray and depthArray
if len(xArray) != fieldArray.shape[1]:
raise ValueError('size mismatch between xArray and fieldArray')
elif len(depthArray) != fieldArray.shape[0]:
raise ValueError('size mismatch between depthArray and fieldArray')
# set up figure
if dpi is None:
dpi = config.getint('plot', 'dpi')
plt.figure(figsize=figsize, dpi=dpi)
# compute moving averages with respect to the x dimension
if N is not None and N != 1:
movingAverageDepthSlices = []
for nVertLevel in range(len(depthArray)):
depthSlice = fieldArray[[nVertLevel]][0]
# in case it's not an xarray already
depthSlice = xr.DataArray(depthSlice)
mean = pd.Series.rolling(depthSlice.to_series(), N,
center=True).mean()
mean = xr.DataArray.from_series(mean)
mean = mean[int(N/2.0):-int(round(N/2.0)-1)]
movingAverageDepthSlices.append(mean)
xArray = xArray[int(N/2.0):-int(round(N/2.0)-1)]
fieldArray = xr.DataArray(movingAverageDepthSlices)
x, y = np.meshgrid(xArray, depthArray) # change to zMid
colormapDict = setup_colormap(config, colorMapSectionName, suffix=suffix)
cs = plt.contourf(x, y, fieldArray, cmap=colormapDict['colormap'],
norm=colormapDict['norm'],
levels=colormapDict['levels'], extend='both')
contourLevels = colormapDict['contours']
if contourLevels is not None:
if len(contourLevels) == 0:
# automatic calculation of contour levels
contourLevels = None
plt.contour(x, y, fieldArray, levels=contourLevels, colors='k',
linewidths=linewidths)
cbar = plt.colorbar(cs, orientation='vertical', spacing='uniform',
ticks=colormapDict['ticks'],
boundaries=colormapDict['ticks'])
if colorbarLabel is not None:
cbar.set_label(colorbarLabel)
axis_font = {'size': config.get('plot', 'axisFontSize')}
title_font = {'size': config.get('plot', 'titleFontSize'),
'color': config.get('plot', 'titleFontColor'),
'weight': config.get('plot', 'titleFontWeight')}
if title is not None:
plt.title(title, **title_font)
if xlabel is not None:
plt.xlabel(xlabel, **axis_font)
if ylabel is not None:
plt.ylabel(ylabel, **axis_font)
if invertYAxis:
plt.gca().invert_yaxis()
if xLim:
plt.xlim(xLim)
if yLim:
plt.ylim(yLim)
if xArrayIsTime:
if firstYearXTicks is None:
minDays = [xArray[0]]
else:
minDays = date_to_days(year=firstYearXTicks, calendar=calendar)
maxDays = [xArray[-1]]
plot_xtick_format(calendar, minDays, maxDays, maxXTicks,
yearStride=yearStrideXTicks)
if (fileout is not None):
plt.savefig(fileout, dpi=dpi, bbox_inches='tight', pad_inches=0.1)
if not config.getboolean('plot', 'displayToScreen'):
plt.close()
return # }}}
[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()
if config.has_option(configSectionName,
'colormapIndices{}'.format(suffix)):
(colormap, norm, levels, ticks) = _setup_indexed_colormap(
config, configSectionName, suffix=suffix)
elif config.has_option(configSectionName, 'normType{}'.format(suffix)):
(colormap, norm, ticks) = _setup_colormap_and_norm(
config, configSectionName, suffix=suffix)
levels = None
else:
raise ValueError('config section {} contains neither the info '
'for an indexed color map nor for computing a '
'norm'.format(configSectionName))
option = 'contourLevels{}'.format(suffix)
if config.has_option(configSectionName, option):
contours = config.getExpression(configSectionName,
option,
usenumpyfunc=True)
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 _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 _date_tick(days, pos, calendar='gregorian', includeMonth=True):
days = np.maximum(days, 0.)
date = days_to_datetime(days, calendar)
if includeMonth:
return '{:04d}-{:02d}'.format(date.year, date.month)
else:
return '{:04d}'.format(date.year)
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)
plt.register_cmap(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)
plt.register_cmap(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)
plt.register_cmap(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)
plt.register_cmap(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)
plt.register_cmap(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:
plt.register_cmap(mapName, getattr(cmocean.cm, mapName))
# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python