# This software is open source software available under the BSD-3 license.
#
# Copyright (c) 2019 Triad National Security, LLC. All rights reserved.
# Copyright (c) 2019 Lawrence Livermore National Security, LLC. All rights
# reserved.
# Copyright (c) 2019 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
"""
Functions for plotting remapped horizontal fields (and comparing with reference
 data sets)
"""
# 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
from mpl_toolkits.basemap import Basemap
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
from mpas_analysis.shared.plot.colormap import setup_colormap
[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 control 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 control 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)
        fieldPeriodic, LatsPeriodic, LonsPeriodic = addcyclic(field, Lats,
                                                              Lons)
        x, y = m(LonsPeriodic, LatsPeriodic)  # compute map proj coordinates
        ax.set_title(title, y=1.06, **plottitle_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, fieldPeriodic, cmap=colormap,
                                      norm=norm)
        else:
            plotHandle = m.contourf(x, y, fieldPeriodic, 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)
    plottitle_font = {'size': config.get('plot',
                                         'threePanelPlotTitleFontSize')}
    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)
    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 control 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 control 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 of 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, **plottitle_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)
    plottitle_font = {'size': config.get('plot',
                                         'threePanelPlotTitleFontSize')}
    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)
    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,
        lineWidth=0.5,
        lineColor='black',
        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 control run data sets
    modelArray, refArray : numpy ndarrays
        model and observational or control 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
    modelTitle : str, optional
        title of the model panel
    refTitle : str, optional
        title of the observations or control 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
    lineWidth : int, optional
        the line width of contour lines (if specified)
    lineColor : str, optional
        the color of contour lines (if specified)
    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, **plottitle_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')
    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)
    plottitle_font = {'size': config.get('plot',
                                         'threePanelPlotTitleFontSize')}
    # 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)
    plt.close()
# function copied from basemap because the latest version of this funciton
# is buggy but no new release seems imminent
def addcyclic(*arr, **kwargs):
    """
    Adds cyclic (wraparound) points in longitude to one or several arrays,
    the last array being longitudes in degrees. e.g.
   ``data1out, data2out, lonsout = addcyclic(data1,data2,lons)``
    ==============   ====================================================
    Keywords         Description
    ==============   ====================================================
    axis             the dimension representing longitude (default -1,
                     or right-most)
    cyclic           width of periodic domain (default 360)
    ==============   ====================================================
    """
    # get (default) keyword arguments
    axis = kwargs.get('axis', -1)
    cyclic = kwargs.get('cyclic', 360)
    # define functions
    def _addcyclic(a):
        """addcyclic function for a single data array"""
        npsel = np.ma if np.ma.is_masked(a) else np
        slicer = [slice(None)] * np.ndim(a)
        try:
            slicer[axis] = slice(0, 1)
        except IndexError:
            raise ValueError('The specified axis does not correspond to an '
                             'array dimension.')
        return npsel.concatenate((a, a[tuple(slicer)]), axis=axis)
    def _addcyclic_lon(a):
        """addcyclic function for a single longitude array"""
        # select the right numpy functions
        npsel = np.ma if np.ma.is_masked(a) else np
        # get cyclic longitudes
        clon = (np.take(a, [0], axis=axis)
                + cyclic * np.sign(np.diff(np.take(a, [0, -1], axis=axis),
                                           axis=axis)))
        # ensure the values do not exceed cyclic
        clonmod = npsel.where(clon <= cyclic, clon, np.mod(clon, cyclic))
        return npsel.concatenate((a, clonmod), axis=axis)
    # process array(s)
    if len(arr) == 1:
        return _addcyclic_lon(arr[-1])
    else:
        return list(map(_addcyclic, arr[:-1])) + [_addcyclic_lon(arr[-1])]
# vim: foldmethod=marker ai ts=4 sts=4 et sw=4 ft=python