Beispiel #1
0
def get_cmap_levels_auto(vmin, vmax, num_per_mag=10):
    """Initiate pseudo-log discrete colormap levels
    
    Note
    ----
        This is a beta version and aims to 
        
    Parameters
    ----------
    vmin : float
        lower end of colormap (e.g. minimum value of data)
    vmax : float
        upper value of colormap (e.g. maximum value of data)
    """
    high = float(exponent(vmax))
    low = -3. if vmin == 0 else float(exponent(vmin))
    lvls =[]
    if 1%vmax*10**(-high) == 0: 
        low+=1
        high-=1
    for mag in range(int(low), int(high)):
        lvls.extend(np.linspace(1, 10, num_per_mag-1, endpoint=0)*10**(mag))
    lvls.extend(np.linspace(1, vmax*10**(-high), num_per_mag, endpoint=1)*10**(high))
    
    return lvls
Beispiel #2
0
def get_cmap_ticks_auto(lvls, num_per_mag=3):
    """Compute cmap ticks based on cmap levels 
    
    The cmap levels may be computed automatically using 
    :func:`get_cmap_levels_auto`.
    
    Parameters
    ----------
    lvls : list
        list containing colormap levels
    num_per_mag : int
        desired number of ticks per magnitude
    """
    low = exponent(lvls[1]) # second entry (first is 0)
    vmax = lvls[-1]
    high = exponent(vmax)
    
    ticks = [0]
    if 1%vmax*10**(-high) == 0: 
        for mag in range(low, high-1):
            ticks.extend(np.linspace(1, 10, num_per_mag, endpoint=0)*10**(mag))
        ticks.extend(np.linspace(1, 10, num_per_mag+1, endpoint=1)*10**(high-1))
    else:
        for mag in range(low, high):
            ticks.extend(np.linspace(1, 10, num_per_mag, endpoint=0)*10**(mag))
        ticks.extend(np.linspace(1, vmax*10**(-high), 
                     num_per_mag+1, endpoint=1)*10**(high))
        
    return ticks
Beispiel #3
0
def calc_pseudolog_cmaplevels(vmin, vmax, add_zero=False):
    """Initiate pseudo-log discrete colormap levels

    Parameters
    ----------
    vmin : float
        lower end of colormap (e.g. minimum value of data)
    vmax : float
        upper value of colormap (e.g. maximum value of data)
    add_zero : bool
        if True, the lower bound is set to 0 (irrelevant if vmin is 0).

    Returns
    -------
    list
        list containing boundary array for discrete colormap (e.g. using
        BoundaryNorm)

    Example
    -------
    >>> vmin, vmax = 0.02, 0.75
    >>> vals = calc_pseudolog_cmaplevels(vmin, vmax, num_per_mag=10, add_zero=True)
    >>> for val in vals: print("%.4f" %val)
    0.0000
    0.0100
    0.0126
    0.0158
    0.0200
    0.0251
    0.0316
    0.0398
    0.0501
    0.0631
    0.0794
    0.1000

    """

    if vmin < 0:
        vmin = 0
    if vmin == 0:
        vmin = 1 * 10.0**(exponent(vmax) - 2)
        if not add_zero:
            add_zero = True
    elif vmax < vmin:
        raise ValueError("Error: vmax must exceed vmin")
    bounds = [0] if add_zero else []
    low = float(exponent(vmin))
    high = float(exponent(vmax))
    bounds.extend(np.arange(np.floor(vmin * 10**(-low)), 10, 1) * 10.0**(low))
    if low == high:
        return bounds

    for mag in range(int(low + 1), int(high)):
        bounds.extend(np.linspace(1, 9, 9) * 10**(mag))
    bounds.extend(np.arange(1, np.ceil(vmax * 10**(-high)), 1) * 10.0**(high))
    bounds.append(vmax)
    return bounds
Beispiel #4
0
def _format_annot_heatmap(annot, annot_fmt_rows, annot_fmt_exceed):
    _annot = []
    if not isinstance(annot_fmt_rows, list):
        annot_fmt_rows = []
        for row in annot:
            mask = row[~np.isnan(row)]
            if len(mask) == 0:  #all NaN
                annot_fmt_rows.append('')
                continue
            mask = mask[mask != 0]
            exps = exponent(mask)
            minexp = exps.min()
            if minexp < -3:
                annot_fmt_rows.append('.1E')
            elif minexp < 0:
                annot_fmt_rows.append('.{}f'.format(-minexp + 1))
            elif minexp in [0, 1]:
                annot_fmt_rows.append('.1f')
            else:
                annot_fmt_rows.append('.0f')
    if isinstance(annot_fmt_exceed, list):
        exceed_val, exceed_fmt = annot_fmt_exceed
    else:
        exceed_val, exceed_fmt = None, None

    for i, row in enumerate(annot):
        rowfmt = annot_fmt_rows[i]
        if rowfmt == '':
            row_fmt = [''] * len(row)
        else:
            #numdigits = -exps.min()
            row_fmt = []
            #lowest = np.min(row)
            #exp = exponent(lowest)
            for val in row:
                if np.isnan(val):
                    valstr = ''
                else:
                    #exp = exps(i)
                    #fmt = '.{}f'.format(numdigits)
                    if exceed_val is not None and val > exceed_val:
                        valstr = format(val, exceed_fmt)
                    else:
                        valstr = format(val, rowfmt)

                row_fmt.append(valstr)
        _annot.append(row_fmt)
    annot = np.asarray(_annot)
    return annot, annot_fmt_rows
Beispiel #5
0
def set_map_ticks(ax, xticks=None, yticks=None, add_x=True,
                  add_y=True):
    """Set or update ticks in instance of GeoAxes object (cartopy)

    Parameters
    ----------
    ax : cartopy.GeoAxes
        map axes instance
    xticks : iterable, optional
        ticks of x-axis (longitudes)
    yticks : iterable, optional
        ticks of y-axis (latitudes)
    add_x : bool
        if True, x-axis labels are added
    add_y : bool
        if True, y-axis labels are added

    Returns
    -------
    cartopy.GeoAxes
        modified axes instance
    """
    lonleft, lonright = ax.get_xlim()
    digits = 2 - exponent(lonleft)
    digits = 0 if digits < 0 else digits
    tick_format = '.%df' %digits
    if not xticks:
        num_lonticks = 7 if lonleft == -lonright else 6
        xticks = linspace(lonleft, lonright, num_lonticks)
    if not yticks:
        latleft, latright = ax.get_ylim()
        num_latticks = 7 if latleft == - latright else 6
        yticks = linspace(latleft, latright, num_latticks)
    ax.set_xticks(xticks, crs=ccrs.PlateCarree())
    ax.set_yticks(yticks, crs=ccrs.PlateCarree())

    lon_formatter = LongitudeFormatter(number_format=tick_format,
                                       degree_symbol='',
                                       dateline_direction_label=True)
    lat_formatter = LatitudeFormatter(number_format=tick_format,
                                      degree_symbol='')

    ax.xaxis.set_major_formatter(lon_formatter)
    ax.yaxis.set_major_formatter(lat_formatter)

    return ax
Beispiel #6
0
def plot_scatter_aerocom(x_vals, y_vals, var_name=None, var_name_ref=None, 
                         x_name=None, y_name=None, start=None, stop=None, 
                         ts_type=None, unit=None, stations_ok=None, 
                         filter_name=None, lowlim_stats=None, 
                         highlim_stats=None, loglog=None, savefig=False, 
                         save_dir=None, save_name=None, ax=None, figsize=None, 
                         fontsize_base=10, marker='+', color='k', alpha=0.5,
                         **kwargs):
    """Method that performs a scatter plot of data in AEROCOM format
    
    Parameters
    ----------
    y_vals : ndarray
        1D array (or list) of model data points (y-axis)
    x_vals : ndarray
        1D array (or list) of observation data points (x-axis)
    var_name : :obj:`str`, optional
        name of variable that is plotted
    var_name_ref : :obj:`str`, optional
        name of variable of reference data
    x_name : :obj:`str`, optional
        Name of observation network
    y_name : :obj:`str`, optional
        Name / ID of model
    start : :obj:`str` or :obj`datetime` or similar
        start time of data
    stop : :obj:`str` or :obj`datetime` or similar
        stop time of data
    
    Returns 
    -------
    axes
        instance of :class:`matplotlib.axes`
    """

    if isinstance(y_vals, list):
        y_vals = np.asarray(y_vals)
    if isinstance(x_vals, list):
        x_vals = np.asarray(x_vals)
    try:
        VARS = const.VARS[var_name]
    except:
        VARS = const.VARS.DEFAULT
    if loglog is None:
        loglog = VARS.scat_loglog
        
    xlim = VARS['scat_xlim']
    ylim = VARS['scat_ylim']
    
    if xlim is None or ylim is None:
        low  =  np.min([np.nanmin(x_vals), np.nanmin(y_vals)])
        high =  np.max([np.nanmax(x_vals), np.nanmax(y_vals)])
        
        xlim = [low, high]
        ylim = [low, high]

    if ax is None:
        if figsize is None:
            figsize = (10,8)
        fig, ax = plt.subplots(figsize=figsize)
    if var_name is None:
        var_name = 'n/d'
    
    statistics = calc_statistics(y_vals, x_vals,
                                 lowlim_stats, highlim_stats)

    if loglog:
        ax.loglog(x_vals, y_vals, ls='none', color=color, marker=marker, 
                  alpha=alpha, **kwargs)
    else:
        ax.plot(x_vals, y_vals, ls='none', color=color, marker=marker, 
                  alpha=alpha, **kwargs)
    
    try:
        title = start_stop_str(start, stop, ts_type)
        if ts_type is not None:
            title += ' ({})'.format(ts_type)
    except:
        title = ''
    
    if not loglog:
        xlim[0] = 0
        ylim[0] = 0
    elif any(x[0] < 0 for x in [xlim, ylim]):
        
        low = 10**(float(exponent(abs(np.nanmin(y_vals))) - 1))
        xlim[0] = low
        ylim[0] = low
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    xlbl = '{}'.format(x_name)
    if var_name_ref is not None:
        xlbl += ' ({})'.format(var_name_ref)
    ax.set_xlabel(xlbl, fontsize=fontsize_base+4)
    ax.set_ylabel('{}'.format(y_name), fontsize=fontsize_base+4)
    
    ax.set_title(title, fontsize=fontsize_base+4)
    
    ax.xaxis.set_major_formatter(ScalarFormatter())
    ax.yaxis.set_major_formatter(ScalarFormatter())
    
    ax.tick_params(labelsize=fontsize_base)
    
    ax.plot(xlim, ylim, '-', color='grey')
    
    xypos =   {'var_info'       :   (0.01, .95),
               'refdata_mean'   :   (0.01, 0.90),
               'data_mean'      :   (0.01, 0.86),
               'nmb'            :   (0.01, 0.82),
               'mnmb'           :   (0.35, 0.82),
               'R'              :   (0.01, 0.78),
               'rms'            :   (0.35, 0.78),
               'R_kendall'      :   (0.01, 0.74),
               'fge'            :   (0.35, 0.74),
               'ts_type'        :   (0.8, 0.1),
               'filter_name'    :   (0.8, 0.06)}
    
    var_str = var_name# + VARS.unit_str
    _ndig = abs(exponent(statistics['refdata_mean']) - 2)
    if unit is None:
        unit = 'N/D'
    if not str(unit) in ['1', 'no_unit']:
        var_str += ' [{}]'.format(unit)

    ax.annotate("{} #: {} # st: {}".format(var_str, 
                        statistics['num_valid'], stations_ok),
                        xy=xypos['var_info'], xycoords='axes fraction', 
                        fontsize=fontsize_base+4, color='red')

    ax.annotate('Mean (x-data): {:.{}f}; Rng: [{:.{}f}, {:.{}f}]'
                .format(statistics['refdata_mean'], _ndig,
                        np.nanmin(x_vals),_ndig,
                        np.nanmax(x_vals), _ndig),
                xy=xypos['refdata_mean'], xycoords='axes fraction', 
                fontsize=fontsize_base, 
                color='red')
    
    ax.annotate('Mean (y-data): {:.{}f}; Rng: [{:.{}f}, {:.{}f}]'
                .format(statistics['data_mean'], _ndig,
                        np.nanmin(y_vals),_ndig,
                        np.nanmax(y_vals), _ndig),
                xy=xypos['data_mean'], xycoords='axes fraction', 
                fontsize=fontsize_base, 
                color='red')
    
    ax.annotate('NMB: {:.1f}%'.format(statistics['nmb']*100),
                        xy=xypos['nmb'], xycoords='axes fraction', 
                        fontsize=fontsize_base, color='red')
    
    ax.annotate('MNMB: {:.1f}%'.format(statistics['mnmb']*100),
                        xy=xypos['mnmb'], xycoords='axes fraction', 
                        fontsize=fontsize_base, color='red')
    
    ax.annotate('R (Pearson): {:.3f}'.format(statistics['R']),
                        xy=xypos['R'], xycoords='axes fraction', 
                        fontsize=fontsize_base, color='red')

    ax.annotate('RMS: {:.3f}'.format(statistics['rms']),
                        xy=xypos['rms'], xycoords='axes fraction', 
                        fontsize=fontsize_base, color='red')
    
    ax.annotate('R (Kendall): {:.3f}'.format(statistics['R_kendall']),
                        xy=xypos['R_kendall'], xycoords='axes fraction', 
                        fontsize=fontsize_base, color='red')
    
    
    ax.annotate('FGE: {:.1f}'.format(statistics['fge']),
                        xy=xypos['fge'], xycoords='axes fraction', 
                        fontsize=fontsize_base, color='red')
    # right lower part
    ax.annotate('{}'.format(ts_type),
                        xy=xypos['ts_type'], xycoords='axes fraction', 
                        ha='center', 
                        fontsize=fontsize_base, color='black')
    ax.annotate('{}'.format(filter_name),
                        xy=xypos['filter_name'], xycoords='axes fraction', 
                        ha='center', 
                        fontsize=fontsize_base, color='black')
    
    ax.set_aspect('equal')
    
    if savefig:
        if any([x is None for x in (save_dir, save_name)]):
            raise IOError
            
        fig.savefig(os.path.join(save_dir, save_name))
    return ax
Beispiel #7
0
def test_exponent():
    """Test method :func:`exponent` of :mod:`pyaerocom.utils`"""
    nominal = [-2, 0, 2]
    vals = [utils.exponent(.01), utils.exponent(4), utils.exponent(234)]
    npt.assert_array_equal(nominal, vals)
Beispiel #8
0
def plot_griddeddata_on_map(data,
                            lons=None,
                            lats=None,
                            var_name=None,
                            unit=None,
                            xlim=(-180, 180),
                            ylim=(-90, 90),
                            vmin=None,
                            vmax=None,
                            add_zero=False,
                            c_under=None,
                            c_over=None,
                            log_scale=True,
                            discrete_norm=True,
                            cbar_levels=None,
                            cbar_ticks=None,
                            add_cbar=True,
                            cmap=None,
                            cbar_ticks_sci=False,
                            color_theme=COLOR_THEME,
                            **kwargs):
    """Make a plot of gridded data onto a map
    
    Note
    ----
    This is a lowlevel plotting method
    
    Parameters
    ----------
    data : ndarray
        2D data array
    lons : ndarray
        longitudes of data
    lats : ndarray
        latitudes of data
    var_name : :obj:`str`, optional
        name of variable that is plotted
    xlim : tuple
        2-element tuple specifying plotted longitude range
    ylim : tuple
        2-element tuple specifying plotted latitude range
    vmin : :obj:`float`, optional
        lower value of colorbar range
    vmax : :obj:`float`, optional 
        upper value of colorbar range
    add_zero : bool
        if True and vmin is not 0, then, the colorbar is extended down to 0. 
        This may be used, e.g. for logarithmic scales that should include 0.
    c_under : :obj:`float`, optional 
        colour of data values smaller than ``vmin``
    c_over : :obj:`float`, optional 
        colour of data values exceeding ``vmax``
    log_scale : bool
        if True, the value to color mapping is done in a pseudo log scale 
        (see :func:`get_cmap_levels_auto` for implementation)
    discrete_norm : bool
        if True, color mapping will be subdivided into discrete intervals
    cbar_levels : iterable, optional
        discrete colorbar levels. Will be computed automatically, if None 
        (and applicable)
    cbar_ticks : iterable, optional
        ticks of colorbar levels. Will be computed automatically, if None 
        (and applicable)
    
    Returns
    -------
    fig
        matplotlib figure instance containing plot result. Use 
        ``fig.axes[0]`` to access the map axes instance (e.g. to modify the 
        title or lon / lat range, etc.)
    """
    kwargs['contains_cbar'] = True
    ax = init_map(xlim, ylim, color_theme=color_theme, **kwargs)
    fig = ax.figure
    from pyaerocom.griddeddata import GriddedData
    if isinstance(data, GriddedData):
        if not data.has_latlon_dims:
            from pyaerocom.exceptions import DataDimensionError
            raise DataDimensionError('Input data needs to have latitude and '
                                     'longitude dimension')
        if not data.ndim == 2:
            if not data.ndim == 3 or not 'time' in data.dimcoord_names:
                raise DataDimensionError(
                    'Input data needs to be 2 dimensional '
                    'or 3D with time being the 3rd '
                    'dimension')
            data.reorder_dimensions_tseries()

            data = data[0]

        lons = data.longitude.points
        lats = data.latitude.points
        data = data.grid.data
    elif not isinstance(data, np.ndarray) or not data.ndim == 2:
        raise IOError("Need 2D numpy array")
    elif not isinstance(lats, np.ndarray) or not isinstance(lons, np.ndarray):
        raise ValueError('Missing lats or lons input')
    if isinstance(data, np.ma.MaskedArray):
        sh = data.shape
        if data.mask.sum() == sh[0] * sh[1]:
            raise ValueError('All datapoints in input data (masked array) are '
                             'invalid')
    _loc = ax.bbox._bbox
    try:
        ax_cbar = fig.add_axes(
            [_loc.x1 + .02, _loc.y0, .02, _loc.y1 - _loc.y0])
    except Exception as e:
        ax_cbar = fig.add_axes([0.91, 0.12, .02, .8])
        print(repr(e))
    X, Y = meshgrid(lons, lats)
    dmin = np.nanmin(data)
    dmax = np.nanmax(data)

    if any([np.isnan(x) for x in [dmin, dmax]]):
        raise ValueError('Cannot plot map of data: all values are NaN')
    elif dmin == dmax:
        raise ValueError('Minimum value in data equals maximum value: '
                         '{}'.format(dmin))
    if vmin is None:
        vmin = dmin
    else:
        if vmin < 0 and log_scale:
            log_scale = False
    if vmax is None:
        vmax = dmax

    bounds = None
    if cbar_levels:  #user provided levels of colorbar explicitely
        if vmin is not None or vmax is not None:
            raise ValueError('Please provide either vmin/vmax OR cbar_levels')
        bounds = list(cbar_levels)
        low, high = bounds[0], bounds[-1]
        if add_zero and low > 0:
            bounds.insert(0, 0)  # insert zero bound
        if cmap is None or isinstance(cmap, str):
            cmap = get_cmap_maps_aerocom(color_theme, low, high)
        norm = BoundaryNorm(boundaries=bounds, ncolors=cmap.N, clip=False)
    else:
        if log_scale:  # no negative values allowed
            if vmin < 0:
                vmin = data[data > 0].min()
                if c_under is None:  #special case, set c_under to indicate that there is values below 0
                    c_under = 'r'
            if cmap is None or isinstance(cmap, str):
                cmap = get_cmap_maps_aerocom(color_theme, vmin, vmax)
            if discrete_norm:
                #to compute upper range of colour range, round up vmax
                exp = float(exponent(vmax) - 1)
                vmax_colors = ceil(vmax / 10**exp) * 10**exp
                bounds = calc_pseudolog_cmaplevels(vmin=vmin,
                                                   vmax=vmax_colors,
                                                   add_zero=add_zero)
                norm = BoundaryNorm(boundaries=bounds,
                                    ncolors=cmap.N,
                                    clip=False)

            else:
                norm = LogNorm(vmin=vmin, vmax=vmax, clip=True)
        else:
            if cmap is None or isinstance(cmap, str):
                cmap = get_cmap_maps_aerocom(color_theme, vmin, vmax)
            norm = Normalize(vmin=vmin, vmax=vmax)
    cbar_extend = "neither"
    if c_under is not None:
        cmap.set_under(c_under)
        cbar_extend = "min"
        if bounds is not None:
            bounds.insert(0, bounds[0] - bounds[1])
    if c_over is not None:
        cmap.set_over(c_over)
        if bounds is not None:
            bounds.append(bounds[-1] + bounds[-2])
        if cbar_extend == "min":
            cbar_extend = "both"
        else:
            cbar_extend = "max"

    disp = ax.pcolormesh(X, Y, data, cmap=cmap, norm=norm)
    # =============================================================================
    #     fmt = None
    #     if bounds is not None:
    #         print(bounds)
    #         min_mag = -exponent(bounds[1])
    #         min_mag = 0 if min_mag < 0 else min_mag
    #         print(min_mag)
    #         #fmt = "%." + str(min_mag) + "f"
    # =============================================================================
    if add_cbar:
        cbar = fig.colorbar(disp,
                            cmap=cmap,
                            norm=norm,
                            boundaries=bounds,
                            extend=cbar_extend,
                            cax=ax_cbar)

        if var_name is not None:
            var_str = var_name  # + VARS.unit_str
            if unit is not None:
                if not str(unit) in ['1', 'no_unit']:
                    var_str += ' [{}]'.format(unit)

            cbar.set_label(var_str)

        if cbar_ticks:
            cbar.set_ticks(cbar_ticks)
        if cbar_ticks_sci:
            lbls = []
            for lbl in cbar.ax.get_yticklabels():
                tstr = lbl.get_text()
                if bool(tstr):
                    lbls.append('{:.1e}'.format(float(tstr)))
                else:
                    lbls.append('')
            cbar.ax.set_yticklabels(lbls)

    return fig
Beispiel #9
0
def test_exponent(inputval, desired):
    """Test method :func:`exponent` of :mod:`pyaerocom.utils`"""
    assert mu.exponent(inputval) == desired
Beispiel #10
0
def plot_griddeddata_on_map(data,
                            lons,
                            lats,
                            var_name=None,
                            xlim=(-180, 180),
                            ylim=(-90, 90),
                            vmin=None,
                            vmax=None,
                            add_zero=False,
                            c_under=None,
                            c_over=None,
                            log_scale=True,
                            discrete_norm=True,
                            cbar_levels=None,
                            cbar_ticks=None,
                            color_theme=COLOR_THEME,
                            **kwargs):
    """Make a plot of gridded data onto a map
    
    Note
    ----
    This is a lowlevel plotting method
    
    Parameters
    ----------
    data : ndarray
        2D data array
    lons : ndarray
        longitudes of data
    lats : ndarray
        latitudes of data
    var_name : :obj:`str`, optional
        name of variable that is plotted
    xlim : tuple
        2-element tuple specifying plotted longitude range
    ylim : tuple
        2-element tuple specifying plotted latitude range
    vmin : :obj:`float`, optional
        lower value of colorbar range
    vmax : :obj:`float`, optional 
        upper value of colorbar range
    add_zero : bool
        if True and vmin is not 0, then, the colorbar is extended down to 0. 
        This may be used, e.g. for logarithmic scales that should include 0.
    c_under : :obj:`float`, optional 
        colour of data values smaller than ``vmin``
    c_over : :obj:`float`, optional 
        colour of data values exceeding ``vmax``
    log_scale : bool
        if True, the value to color mapping is done in a pseudo log scale 
        (see :func:`get_cmap_levels_auto` for implementation)
    discrete_norm : bool
        if True, color mapping will be subdivided into discrete intervals
    cbar_levels : iterable, optional
        discrete colorbar levels. Will be computed automatically, if None 
        (and applicable)
    cbar_ticks : iterable, optional
        ticks of colorbar levels. Will be computed automatically, if None 
        (and applicable)
    
    
    Returns
    -------
    fig
        matplotlib figure instance containing plot result. Use 
        ``fig.axes[0]`` to access the map axes instance (e.g. to modify the 
        title or lon / lat range, etc.)
    """
    kwargs['contains_cbar'] = True
    ax = init_map(xlim, ylim, color_theme=color_theme, **kwargs)
    fig = ax.figure

    if not isinstance(data, np.ndarray) or not data.ndim == 2:
        raise IOError("Need 2D numpy array")
    elif isinstance(data, np.ma.MaskedArray):
        sh = data.shape
        if data.mask.sum() == sh[0] * sh[1]:
            raise ValueError('All datapoints in input data (masked array) are '
                             'invalid')
    _loc = ax.bbox._bbox
    try:
        ax_cbar = fig.add_axes(
            [_loc.x1 + .02, _loc.y0, .02, _loc.y1 - _loc.y0])
    except Exception as e:
        ax_cbar = fig.add_axes([0.91, 0.12, .02, .8])
        print(repr(e))
    X, Y = meshgrid(lons, lats)
    cmap = copy(color_theme.cmap_map)
    if vmin is None:
        vmin = data.min()
    if vmax is None:
        vmax = data.max()

    bounds = None
    if cbar_levels:  #user provided levels of colorbar explicitely
        bounds = cbar_levels
        norm = BoundaryNorm(boundaries=bounds, ncolors=cmap.N, clip=False)
    else:
        if log_scale and discrete_norm:
            #to compute upper range of colour range, round up vmax
            exp = float(exponent(vmax) - 1)
            vmax_colors = ceil(vmax / 10**exp) * 10**exp
            bounds = calc_pseudolog_cmaplevels(vmin=vmin,
                                               vmax=vmax_colors,
                                               add_zero=add_zero)
            norm = BoundaryNorm(boundaries=bounds, ncolors=cmap.N, clip=False)
        elif log_scale:
            norm = LogNorm(vmin=vmin, vmax=vmax, clip=True)
        else:
            norm = None
    cbar_extend = "neither"
    if c_under is not None:
        cmap.set_under(c_under)
        cbar_extend = "min"
        if bounds is not None:
            bounds.insert(0, bounds[0] - bounds[1])
    if c_over is not None:
        cmap.set_over(c_over)
        if bounds is not None:
            bounds.append(bounds[-1] + bounds[-2])
        if cbar_extend == "min":
            cbar_extend = "both"
        else:
            cbar_extend = "max"

    disp = ax.pcolormesh(X, Y, data, cmap=cmap, norm=norm)

    min_mag = -exponent(bounds[1])
    min_mag = 0 if min_mag < 0 else min_mag

    cbar = fig.colorbar(disp,
                        cmap=cmap,
                        norm=norm,
                        boundaries=bounds,
                        extend=cbar_extend,
                        cax=ax_cbar,
                        format="%." + str(min_mag) + "f")

    if var_name is not None:
        cbar.set_label(var_name)

    if cbar_ticks:
        cbar.set_ticks(cbar_ticks)

    return fig