"""
pyrad.graph.plots
=================

Functions to plot Pyrad datasets

.. autosummary::
    :toctree: generated/

    plot_pos
    plot_pos_map
    plot_density
    plot_scatter
    plot_centroids
    plot_quantiles
    plot_histogram
    plot_histogram2
    plot_antenna_pattern
    plot_selfconsistency
    plot_selfconsistency_instrument
    plot_selfconsistency_instrument2
    plot_scatter_comp
    plot_sun_hits
    _plot_sunscan
    _plot_time_range

"""

from ..util.radar_utils import compute_quantiles_from_hist
from .plots_aux import get_colobar_label, get_field_name, get_norm
import pyart
import matplotlib.pyplot as plt
from warnings import warn

import numpy as np

try:
    import cartopy
    from cartopy.io.img_tiles import Stamen

    _CARTOPY_AVAILABLE = True
except ImportError:
    _CARTOPY_AVAILABLE = False

import matplotlib as mpl

mpl.use("Agg")

# Increase a bit font size
mpl.rcParams.update({"font.size": 16})
mpl.rcParams.update({"font.family": "sans-serif"})


def plot_pos(
    lat,
    lon,
    alt,
    fname_list,
    ax=None,
    fig=None,
    save_fig=True,
    sort_altitude="No",
    dpi=72,
    alpha=1.0,
    cb_label="height [m MSL]",
    titl="Position",
    xlabel="Lon [Deg]",
    ylabel="Lat [Deg]",
    limits=None,
    vmin=None,
    vmax=None,
):
    """
    plots a trajectory on a Cartesian surface

    Parameters
    ----------
    lat, lon, alt : float array
        Points coordinates
    fname_list : list of str
        list of names of the files where to store the plot
    fig : Figure
        Figure to add the colorbar to. If none a new figure will be created
    ax : Axis
        Axis to plot on. if fig is None a new axis will be created
    save_fig : bool
        if true save the figure if false it does not close the plot and
        returns the handle to the figure
    sort_altitude : str
        String indicating whether to sort the altitude data. Can be 'No',
        'Lowest_on_top' or 'Highest_on_top'
    dpi : int
        Pixel density
    alpha : float
        Transparency
    cb_label : str
        Color bar label
    titl : str
        Plot title
    xlabel, ylabel : str
        The labels of the X and Y axis
    limits : tupple or None
        The limits of the field to plot
    vmin, vmax : float
        The limits of the color scale

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes
    """
    if sort_altitude in ("Lowest_on_top", "Highest_on_top"):
        ind = np.argsort(alt)
        if sort_altitude == "Lowest_on_top":
            ind = ind[::-1]
        lat = lat[ind]
        lon = lon[ind]
        alt = alt[ind]

    if vmin is None:
        vmin = alt.min()
    if vmax is None:
        vmax = alt.max()
    marker = "x"
    col = alt
    cmap = "viridis"
    norm = plt.Normalize(vmin, vmax)

    if fig is None:
        fig = plt.figure(figsize=[10, 8], dpi=dpi)
        ax = fig.add_subplot(111, aspect="equal")
    else:
        ax.autoscale(False)

    cax = ax.scatter(lon, lat, c=col, marker=marker, alpha=alpha, cmap=cmap, norm=norm)

    if limits is not None:
        ax.set_xlim(limits[0], limits[1])
        ax.set_ylim(limits[2], limits[3])

    # plot colorbar
    cb = fig.colorbar(cax, orientation="horizontal")
    cb.set_label(cb_label)

    ax.set_title(titl)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)

    # Turn on the grid
    ax.grid()

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_pos_map(
    lat,
    lon,
    alt,
    fname_list,
    ax=None,
    fig=None,
    save_fig=True,
    sort_altitude="No",
    dpi=72,
    alpha=1.0,
    cb_label="height [m MSL]",
    titl="Position",
    xlabel="Lon [Deg]",
    ylabel="Lat [Deg]",
    limits=None,
    vmin=None,
    vmax=None,
    lon_step=0.3,
    lat_step=0.1,
    background_zoom=8,
):
    """
    plots a trajectory on a map

    Parameters
    ----------
    lat, lon, alt : float array
        Points coordinates
    fname_list : list of str
        list of names of the files where to store the plot
    fig : Figure
        Figure to add the colorbar to. If none a new figure will be created
    ax : Axis
        Axis to plot on. if fig is None a new axis will be created
    save_fig : bool
        if true save the figure if false it does not close the plot and
        returns the handle to the figure
    sort_altitude : str
        String indicating whether to sort the altitude data. Can be 'No',
        'Lowest_on_top' or 'Highest_on_top'
    dpi : int
        Pixel density
    alpha : float
        Transparency
    cb_label : str
        Color bar label
    titl : str
        Plot title
    xlabel, ylabel : str
        The labels of the X and Y axis
    limits : tupple or None
        The limits of the field to plot
    vmin, vmax : float
        The limits of the color scale
    lon_step, lat_step : float
        The step interval of the latitude, longitude lines to plot
    background_zoom : int
        The zoom of the background image. A higher number will give more level
        of detail at the expense of speed.

    Returns
    -------
    fname_list : list of str or
    fig, ax : tupple
        list of names of the saved plots or handle of the figure an axes

    """
    if not _CARTOPY_AVAILABLE:
        warn("Unable to plot trajectory on a map. Cartopy not available")
        return [""]

    if sort_altitude in ("Lowest_on_top", "Highest_on_top"):
        ind = np.argsort(alt)
        if sort_altitude == "Lowest_on_top":
            ind = ind[::-1]
        lat = lat[ind]
        lon = lon[ind]
        alt = alt[ind]

    if vmin is None:
        vmin = alt.min()
    if vmax is None:
        vmax = alt.max()
    marker = "x"
    col = alt
    cmap = "viridis"
    norm = plt.Normalize(vmin, vmax)

    # get background map instance
    stamen_terrain = Stamen("terrain-background")
    projection = cartopy.crs.PlateCarree()

    # set map limits and lat/lon lines
    if limits is None:
        limits = [np.min(lon), np.max(lon), np.min(lat), np.max(lat)]

    lon_lines = np.arange(limits[0], limits[1] + 1, lon_step)
    lat_lines = np.arange(limits[2], limits[3] + 1, lat_step)

    if fig is None:
        fig = plt.figure(figsize=[10, 8], dpi=dpi)

        # draw background
        ax = fig.add_subplot(111, projection=stamen_terrain.crs)
        ax.set_extent(limits, crs=projection)
        ax.add_image(stamen_terrain, background_zoom)

        # add countries
        countries = cartopy.feature.NaturalEarthFeature(
            category="cultural", name="admin_0_countries", scale="10m", facecolor="none"
        )
        ax.add_feature(countries, edgecolor="black")

        # draw grid lines and labels
        gl = ax.gridlines(xlocs=lon_lines, ylocs=lat_lines, draw_labels=True)
        gl.xlabels_top = False
        gl.ylabels_right = False

        ax.text(
            0.5,
            -0.2,
            xlabel,
            va="bottom",
            ha="center",
            rotation="horizontal",
            rotation_mode="anchor",
            transform=ax.transAxes,
        )

        ax.text(
            -0.1,
            0.55,
            ylabel,
            va="bottom",
            ha="center",
            rotation="vertical",
            rotation_mode="anchor",
            transform=ax.transAxes,
        )
    else:
        ax.autoscale(False)

    # plot data
    cax = ax.scatter(
        lon,
        lat,
        c=col,
        marker=marker,
        alpha=alpha,
        cmap=cmap,
        norm=norm,
        transform=projection,
    )

    # plot colorbar
    cb = fig.colorbar(cax, orientation="horizontal")
    cb.set_label(cb_label)

    ax.set_title(titl)

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_density(
    hist_obj,
    hist_type,
    field_name,
    ind_sweep,
    prdcfg,
    fname_list,
    quantiles=(25.0, 50.0, 75.0),
    ref_value=0.0,
    vmin=None,
    vmax=None,
):
    """
    density plot (angle-values representation)

    Parameters
    ----------
    hist_obj : histogram object
        object containing the histogram data to plot
    hist_type : str
        type of histogram (instantaneous data or cumulative)
    field_name : str
        name of the radar field to plot
    ind_sweep : int
        sweep index to plot
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot
    quantiles : array
        the quantile lines to plot
    ref_value : float
        the reference value
    vmin, vmax : float
        Minim and maximum extend of the vertical axis

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    hist_obj_aux = hist_obj.extract_sweeps([ind_sweep])
    if hist_obj_aux.scan_type == "ppi":
        ang = np.sort(hist_obj_aux.azimuth["data"])
        ind_ang = np.argsort(hist_obj_aux.azimuth["data"])
        ang_min = np.min(hist_obj_aux.azimuth["data"])
        ang_max = np.max(hist_obj_aux.azimuth["data"])
        ang_step = ang[1] - ang[0]
        field = hist_obj_aux.fields[field_name]["data"][ind_ang, :]
        labelx = "azimuth angle (degrees)"
    elif hist_obj_aux.scan_type == "rhi":
        ang = np.sort(hist_obj_aux.elevation["data"])
        ind_ang = np.argsort(hist_obj_aux.elevation["data"])
        ang_min = np.min(hist_obj_aux.elevation["data"])
        ang_max = np.max(hist_obj_aux.elevation["data"])
        ang_step = ang[1] - ang[0]
        field = hist_obj_aux.fields[field_name]["data"][ind_ang, :]
        labelx = "elevation angle (degrees)"
    else:
        field = hist_obj_aux.fields[field_name]["data"]
        ang = np.array(range(hist_obj_aux.nrays))
        ang_min = 0
        ang_max = hist_obj_aux.nrays - 1
        ang_step = ang[1] - ang[0]
        labelx = "ray number"

    # Compute quantiles for each ray
    az_percentiles = np.ma.masked_all((len(ang), len(quantiles)))
    for ray in range(len(ang)):
        _, values_ray = compute_quantiles_from_hist(
            hist_obj.range["data"], field[ray, :], quantiles=quantiles
        )
        az_percentiles[ray, :] = values_ray

    # Compute overall sweep quantiles
    _, values_sweep = compute_quantiles_from_hist(
        hist_obj.range["data"], np.ma.sum(field, axis=0), quantiles=quantiles
    )

    # mask 0 data
    field = np.ma.masked_where(field == 0, field)

    # display data
    if hist_type == "instant":
        titl = pyart.graph.common.generate_title(hist_obj_aux, field_name, ind_sweep)
    else:
        titl = (
            "{:.1f}".format(hist_obj_aux.fixed_angle["data"][0])
            + " Deg. "
            + pyart.graph.common.generate_radar_time_begin(hist_obj).strftime(
                "%Y-%m-%d"
            )
            + "\n"
            + get_field_name(hist_obj.fields[field_name], field_name)
        )
    label = "Number of Points"
    labely = get_colobar_label(hist_obj_aux.fields[field_name], field_name)

    dpi = 72
    if "dpi" in prdcfg["ppiImageConfig"]:
        dpi = prdcfg["ppiImageConfig"]["dpi"]

    fig = plt.figure(
        figsize=[prdcfg["ppiImageConfig"]["xsize"], prdcfg["ppiImageConfig"]["ysize"]],
        dpi=dpi,
    )
    ax = fig.add_subplot(111)

    # define limits of field and color map
    cmap = pyart.config.get_field_colormap(field_name)
    step = hist_obj.range["data"][1] - hist_obj.range["data"][0]
    xmin = ang_min - ang_step / 2.0
    xmax = ang_max + ang_step / 2.0
    ymin = hist_obj.range["data"][0] - step / 2.0
    ymax = hist_obj.range["data"][-1] + step / 2.0

    cax = ax.imshow(
        np.ma.transpose(field),
        origin="lower",
        cmap=cmap,
        vmin=0.0,
        vmax=np.max(field),
        extent=(xmin, xmax, ymin, ymax),
        aspect="auto",
        interpolation="none",
    )
    ax.set_ylim(bottom=vmin, top=vmax)
    ax.autoscale(False)

    # plot reference
    ax.plot(ang, np.zeros(len(ang)) + ref_value, "k--")

    # Generate colormap for quantiles
    quantile_colors = plt.cm.viridis_r(np.linspace(0, 1, len(quantiles)))

    # Plot all quantiles
    for i, q in enumerate(quantiles):
        ax.plot(
            ang,
            np.zeros(len(ang)) + values_sweep[i],
            color=quantile_colors[i],
            linestyle="--",
            label=f"{q:.1f}th quantile",
        )
        ax.plot(
            ang,
            az_percentiles[:, i],
            color=quantile_colors[i],
            linestyle="-",
            label=f"{q:.1f}th quantile",
        )

    # ax.autoscale(enable=True, axis='both', tight=True)

    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_title(titl)

    cb = fig.colorbar(cax)
    cb.set_label(label)

    metadata = "npoints: " + str(np.ma.sum(field)) + "\n"
    for i, quant in enumerate(quantiles):
        val_quant_str = "--"
        if values_sweep[i] is not np.ma.masked:
            val_quant_str = f"{values_sweep[i]:.3f}"
        metadata += f"{quant} quant: {val_quant_str}\n"

    ax.text(
        0.05,
        0.05,
        metadata,
        horizontalalignment="left",
        verticalalignment="bottom",
        transform=ax.transAxes,
    )

    # Turn on the grid
    ax.grid()

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def plot_scatter(
    bin_edges1,
    bin_edges2,
    hist_2d,
    field_name1,
    field_name2,
    fname_list,
    prdcfg,
    metadata=None,
    lin_regr=None,
    lin_regr_slope1=None,
    rad1_name="RADAR001",
    rad2_name="RADAR002",
    titl="colocated radar gates",
    cmap=None,
):
    """
    2D histogram

    Parameters
    ----------
    bin_edges1, bin_edges2 : float array2
        the bins of each field
    hist_2d : ndarray 2D
        the 2D histogram
    field_name1, field_name2 : str
        the names of each field
    fname_list : list of str
        list of names of the files where to store the plot
    prdcfg : dict
        product configuration dictionary
    metadata : str
        a string with metadata to write in the plot
    lin_regr : tupple with 2 values
        the coefficients for a linear regression
    lin_regr_slope1 : float
        the intercep point of a linear regression of slope 1
    rad1_name, rad2_name : str
        name of the radars which data is used
    titl : str
        plot title
    cmap : str or None
        name of the colormap. If None it will be choosen the default for the
        field_name

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    # mask 0 data
    hist_2d = np.ma.masked_where(hist_2d == 0, hist_2d)

    # display data
    label = "Number of Points"
    labelx = rad1_name + " " + field_name1
    labely = rad2_name + " " + field_name2

    dpi = 72
    if "dpi" in prdcfg["ppiImageConfig"]:
        dpi = prdcfg["ppiImageConfig"]["dpi"]

    fig = plt.figure(
        figsize=[prdcfg["ppiImageConfig"]["xsize"], prdcfg["ppiImageConfig"]["ysize"]],
        dpi=dpi,
    )
    ax = fig.add_subplot(111)

    if cmap is None:
        cmap = pyart.config.get_field_colormap(field_name1)

    X, Y = np.meshgrid(bin_edges1, bin_edges2)
    cax = ax.pcolormesh(
        X, Y, np.ma.transpose(hist_2d), cmap=cmap, vmin=0.0, vmax=np.max(hist_2d)
    )

    step1 = bin_edges1[1] - bin_edges1[0]
    bin_centers1 = bin_edges1[0:-1] + step1 / 2.0

    step2 = bin_edges2[1] - bin_edges2[0]
    bin_centers2 = bin_edges2[0:-1] + step2 / 2.0

    # plot reference
    if bin_edges1.size == bin_edges2.size:
        ax.plot(bin_centers1, bin_centers2, "k--")

    # plot linear regression
    if lin_regr is not None:
        ax.plot(bin_centers1, lin_regr[0] * bin_centers1 + lin_regr[1], "r")
    if lin_regr_slope1 is not None:
        ax.plot(bin_centers1, bin_centers1 + lin_regr_slope1, "g")

    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_title(titl)

    cb = fig.colorbar(cax)
    cb.set_label(label)

    if metadata is not None:
        ax.text(
            0.05,
            0.95,
            metadata,
            horizontalalignment="left",
            verticalalignment="top",
            transform=ax.transAxes,
        )

    # Make a tight layout
    fig.tight_layout()

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def plot_centroids(
    bin_edges1,
    bin_edges2,
    hist_2d,
    field_name1,
    field_name2,
    fname_list,
    prdcfg,
    titl="centroids",
    cmap=None,
    medoids_x=None,
    medoids_y=None,
    fmedoid_x=None,
    fmedoid_y=None,
):
    """
    2D histogram

    Parameters
    ----------
    bin_edges1, bin_edges2 : float array2
        the bins of each field
    hist_2d : ndarray 2D
        the 2D histogram
    field_name1, field_name2 : str
        the names of each field
    fname_list : list of str
        list of names of the files where to store the plot
    prdcfg : dict
        product configuration dictionary
    titl : str
        plot title
    cmap : str or None
        name of the colormap. If None it will be choosen the default for the
        field_name
    medoids_x, medoids_y : ndarray 1D or None
        intermediate medoids
    fmedoid_x, fmedoid_y : ndarray 1D or None
        final medoid

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    # mask 0 data
    hist_2d = np.ma.masked_where(hist_2d == 0, hist_2d)

    # display data
    label = "Number of Points"
    labelx = field_name1
    labely = field_name2

    dpi = 72
    if "dpi" in prdcfg["ppiImageConfig"]:
        dpi = prdcfg["ppiImageConfig"]["dpi"]

    fig = plt.figure(
        figsize=[prdcfg["ppiImageConfig"]["xsize"], prdcfg["ppiImageConfig"]["ysize"]],
        dpi=dpi,
    )
    ax = fig.add_subplot(111)

    if cmap is None:
        cmap = pyart.config.get_field_colormap(field_name1)

    X, Y = np.meshgrid(bin_edges1, bin_edges2)
    cax = ax.pcolormesh(
        X, Y, np.ma.transpose(hist_2d), cmap=cmap, vmin=0.0, vmax=np.max(hist_2d)
    )

    # plot intermediate medoids
    if medoids_x is not None:
        ax.scatter(medoids_x, medoids_y, c="w", marker="o")
    if fmedoid_x is not None:
        ax.plot(fmedoid_x, fmedoid_y, c="r", marker="D")

    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_title(titl)

    cb = fig.colorbar(cax)
    cb.set_label(label)

    # Make a tight layout
    fig.tight_layout()

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def plot_quantiles(
    quant,
    value,
    fname_list,
    labelx="quantile",
    labely="value",
    titl="quantile",
    vmin=None,
    vmax=None,
    dpi=72,
):
    """
    plots quantiles

    Parameters
    ----------
    quant : array
        quantiles to be plotted
    value : array
        values of each quantile
    fname_list : list of str
        list of names of the files where to store the plot
    labelx : str
        The label of the X axis
    labely : str
        The label of the Y axis
    titl : str
        The figure title
    vmin, vmax: float
        Lower/Upper limit of data values
    dpi : int
        dots per inch

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    fig, ax = plt.subplots(figsize=[10, 6], dpi=dpi)
    ax.plot(quant, value, "bx-")
    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_ylim(bottom=vmin, top=vmax)
    ax.set_title(titl)

    # Turn on the grid
    ax.grid()

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def plot_histogram(
    bin_edges,
    values,
    fname_list,
    labelx="bins",
    labely="Number of Samples",
    titl="histogram",
    binwidth_equal=False,
    dpi=72,
):
    """
    computes and plots histogram

    Parameters
    ----------
    bin_edges : array
        histogram bin edges
    values : array
        data values
    fname_list : list of str
        list of names of the files where to store the plot
    labelx : str
        The label of the X axis
    labely : str
        The label of the Y axis
    titl : str
        The figure title
    binwidth_equal : bool
        If True the bars are going to have the same width regardless of the
        actual bin size
    dpi : int
        dots per inch

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    fig, ax = plt.subplots(figsize=[10, 6], dpi=dpi)
    if binwidth_equal:
        hist, bin_edges = np.histogram(values, bins=bin_edges)
        ax.bar(np.arange(len(bin_edges) - 1.0) + 0.5, hist, width=1)
        ax.set_xticks(np.arange(len(bin_edges)))
        ax.set_xticklabels(bin_edges, rotation=45.0)
    else:
        ax.hist(values, bins=bin_edges)
    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_title(titl)

    # Make a tight layout
    fig.tight_layout()

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def plot_histogram2(
    bin_centers,
    hist,
    fname_list,
    width=None,
    labelx="bins",
    labely="Number of Samples",
    titl="histogram",
    dpi=72,
    ax=None,
    fig=None,
    save_fig=True,
    color=None,
    alpha=None,
    invert_xaxis=False,
):
    """
    plots histogram

    Parameters
    ----------
    bin_centers : array
        histogram bin centers
    hist : array
        values for each bin
    fname_list : list of str
        list of names of the files where to store the plot
    width : scalar or array-like
        the width(s) of the bars. If None it is going to be estimated from the
        distances between centers
    labelx : str
        The label of the X axis
    labely : str
        The label of the Y axis
    titl : str
        The figure title
    dpi : int
        dots per inch
    fig : Figure
        Figure to add the colorbar to. If none a new figure will be created
    ax : Axis
        Axis to plot on. if fig is None a new axis will be created
    save_fig : bool
        if true save the figure. If false it does not close the plot and
        returns the handle to the figure
    color : str
        color of the bars
    alpha : float
        parameter controling the transparency
    invert_xaxis : bool
        If true inverts the x axis

    Returns
    -------
    fname_list or fig, ax: list of str
        list of names of the created plots

    """
    if fig is None:
        fig = plt.figure(figsize=[10, 6], dpi=dpi)
        ax = fig.add_subplot(111)
    else:
        ax.autoscale(False)

    if width is None:
        width = bin_centers[1] - bin_centers[0]
    ax.bar(bin_centers, hist, width=width, color=color, alpha=alpha)
    if invert_xaxis:
        ax.invert_xaxis()

    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_title(titl)

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_antenna_pattern(
    antpattern,
    fname_list,
    labelx="Angle [Deg]",
    linear=False,
    twoway=False,
    title="Antenna Pattern",
    ymin=None,
    ymax=None,
    dpi=72,
):
    """
    plots an antenna pattern

    Parameters
    ----------
    antpattern : dict
        dictionary with the angle and the attenuation
    value : float array
        values of the time series
    fname_list : list of str
        list of names of the files where to store the plot
    labelx : str
        The label of the X axis
    linear : boolean
        if true data is in linear units
    linear : boolean
        if true data represents the two way attenuation
    titl : str
        The figure title
    ymin, ymax: float
        Lower/Upper limit of y axis
    dpi : int
        dots per inch

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    waystr = "One-way "
    bw_threshold = -3.0
    if twoway:
        waystr = "Two-way "
        bw_threshold = -6

    linstr = "Att [dB]"
    if linear:
        linstr = "Att [-]"
        mainbeam = antpattern["angle"][
            antpattern["attenuation"] >= 10.0 ** (0.1 * bw_threshold)
        ]
    else:
        mainbeam = antpattern["angle"][antpattern["attenuation"] >= bw_threshold]
    beamwidth = np.abs(np.max(mainbeam) - np.min(mainbeam))

    labely = waystr + linstr

    fig, ax = plt.subplots(figsize=[10, 6], dpi=dpi)

    ax.plot(antpattern["angle"], antpattern["attenuation"])
    ax.set_title(title)
    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_ylim(bottom=ymin, top=ymax)

    metadata = "3-dB beamwidth: " + "{:.2f}".format(float(beamwidth))
    ax.text(
        0.05,
        0.95,
        metadata,
        horizontalalignment="left",
        verticalalignment="top",
        transform=ax.transAxes,
    )

    # Make a tight layout
    fig.tight_layout()

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def plot_selfconsistency(
    zdrkdp_table,
    fname_list,
    labelx="ZDR [dB]",
    labely="KDP/Zh [(deg*m3)/(km*mm6)]",
    title="Selfconsistency in rain",
    ymin=None,
    ymax=None,
    dpi=72,
    save_fig=True,
    ax=None,
    fig=None,
):
    """
    plots a ZDR-KDP/ZH selfconsistency in rain relation

    Parameters
    ----------
    antpattern : dict
        dictionary with the angle and the attenuation
    value : float array
        values of the time series
    fname_list : list of str
        list of names of the files where to store the plot
    labelx : str
        The label of the X axis
    linear : boolean
        if true data is in linear units
    linear : boolean
        if true data represents the two way attenuation
    titl : str
        The figure title
    ymin, ymax: float
        Lower/Upper limit of y axis
    dpi : int
        dots per inch

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    if fig is None:
        fig, ax = plt.subplots(figsize=[10, 6], dpi=dpi)
    else:
        ax.autoscale(False)

    ax.plot(zdrkdp_table[0], zdrkdp_table[1])
    ax.set_title(title)
    ax.set_xlabel(labelx)
    ax.set_ylabel(labely)
    ax.set_ylim(bottom=ymin, top=ymax)

    # Make a tight layout
    fig.tight_layout()

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return (fig, ax)


def plot_selfconsistency_instrument(
    zdr,
    kdp,
    zh,
    fname_list,
    bins_zdr_step=0.05,
    bins_zdr_min=0.0,
    bins_zdr_max=6.0,
    bins_kdpzh_step=0.1,
    bins_kdpzh_min=-2.0,
    bins_kdpzh_max=20.0,
    normalize=True,
    vmin=0.0,
    vmax=0.01,
    parametrization="None",
    zdr_kdpzh_dict=None,
    retrieve_relation=True,
    plot_theoretical=True,
    dpi=72,
):
    """
    plots the ZDR-KDP/ZH relationship obtained by an instrument. The
    theoretical curve and the retrieved curve

    Parameters
    ----------
    zdr, kdp, zh : 1D ndarray
        The valid values of ZDR [dB], KDP [deg/km] and Zh [mm6/m3] collected
        by the instrument
    fname_list : list of str
        list of names of the files where to store the plot
    bins_zdr_step : float
        The step of the ZDR axis of the histogram [dB]
    bins_zdr_min, bins_zdr_max : float
        The limits of the ZDR axis of the histogram (bins center) [dB]
    bins_kdpzh_step : float
        The step of the 1e5*KDP^a/ZH^b axis of the histogram
        [(deg*m3)/(km*mm6)]
    bins_kdpzh_min, bins_kdpzh_max : float
        The limits of the 1e5*KDP^a/ZH^b axis of the histogram (bins center)
        [(deg*m3)/(km*mm6)]
    normalize : Bool
        If True the occurrence density of ZH/KDP for each ZDR bin is going to
        be represented. Otherwise it will show the number of gates at each bin
    vmin, vmax : float
        min and max values of the colorbar
    parametrization : str
        The type of parametrization for the self-consistency curves. Can be
        'None', 'Gourley', 'Wolfensberger', 'Louf', 'Gorgucci' or 'Vaccarono'.
        'None' will use tables contained in zdr_kdpzh_dict. The parametrized
        curves are obtained from literature except for Wolfensberger that was
        derived from disdrometer data obtained by MeteoSwiss and EPFL. All
        parametrizations are valid for C-band only except that of Gourley.
    zdr_kdpzh_dict : dict
        dictionary containing a look up table relating ZDR with KDP/Zh for
        different elevations and the frequency band of the radar
    retrieve_relation : boolean
        if true a zdr-kdp/zh relationship is retrieved from the data
    plot_theoretical : bool
        if true the theoretical relationship is retrieved
    dpi : int
        dots per inch

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """

    # prepare bins for histogram
    bins_zdr_centers = np.arange(
        bins_zdr_min, bins_zdr_max + bins_zdr_step, bins_zdr_step
    )
    bins_zdr_edges = np.append(
        bins_zdr_centers - bins_zdr_step / 2.0,
        bins_zdr_centers[-1] + bins_zdr_step / 2.0,
    )
    bins_kdpzh_centers = np.arange(
        bins_kdpzh_min, bins_kdpzh_max + bins_kdpzh_step, bins_kdpzh_step
    )
    bins_kdpzh_edges = np.append(
        bins_kdpzh_centers - bins_kdpzh_step / 2.0,
        bins_kdpzh_centers[-1] + bins_kdpzh_step / 2.0,
    )

    labely = "10e5*KDP/ZH [(deg*m3)/(km*mm6)"

    kdpzh_th = None
    if plot_theoretical:
        if parametrization == "None":
            if zdr_kdpzh_dict is not None and "zdr_kdpzh" in zdr_kdpzh_dict:
                zdr_th = zdr_kdpzh_dict["zdr_kdpzh"][0][0]
                kdpzh_th = 1e5 * zdr_kdpzh_dict["zdr_kdpzh"][0][1]
            else:
                warn(
                    "Unable to plot theoretical self-consistency curve. "
                    "Relationship not provided"
                )
        elif parametrization == "Gourley":
            if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
                warn(
                    "Unable to plot theoretical self-consistency curve. "
                    "Frequency band not provided"
                )
            else:
                zdr_th = np.arange(0.0, 3.5 + bins_zdr_step, bins_zdr_step)
                if zdr_kdpzh_dict["freq_band"] == "S":
                    kdpzh_th = (
                        3.696
                        - 1.963 * zdr_th
                        + 0.504 * zdr_th * zdr_th
                        - 0.051 * zdr_th * zdr_th * zdr_th
                    )
                elif zdr_kdpzh_dict["freq_band"] == "C":
                    kdpzh_th = (
                        6.746
                        - 2.970 * zdr_th
                        + 0.711 * zdr_th * zdr_th
                        - 0.079 * zdr_th * zdr_th * zdr_th
                    )
                elif zdr_kdpzh_dict["freq_band"] == "X":
                    kdpzh_th = (
                        11.74
                        - 4.020 * zdr_th
                        - 0.140 * zdr_th * zdr_th
                        + 0.130 * zdr_th * zdr_th * zdr_th
                    )
                else:
                    warn(
                        "Unable to plot theoretical self-consistency curve. "
                        "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
                    )
        elif parametrization == "Wolfensberger":
            if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
                warn(
                    "Unable to plot theoretical self-consistency curve. "
                    "Frequency band not provided"
                )
            else:
                zdr_th = np.arange(0.0, 3.5 + bins_zdr_step, bins_zdr_step)
                if zdr_kdpzh_dict["freq_band"] == "C":
                    kdpzh_th = (
                        3.199 * np.ma.exp(-7.767e-1 * zdr_th) - 0.4436 * zdr_th + 3.464
                    )
                else:
                    warn(
                        "Unable to plot theoretical self-consistency curve. "
                        "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
                    )
        elif parametrization == "Louf":
            if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
                warn(
                    "Unable to plot theoretical self-consistency curve. "
                    "Frequency band not provided"
                )
            else:
                zdr_th = np.arange(0.5, 3.5 + bins_zdr_step, bins_zdr_step)
                if zdr_kdpzh_dict["freq_band"] == "C":
                    kdpzh_th = (
                        6.607
                        - 4.577 * zdr_th
                        + 1.577 * zdr_th * zdr_th
                        - 0.23 * zdr_th * zdr_th * zdr_th
                    )
                else:
                    warn(
                        "Unable to plot theoretical self-consistency curve. "
                        "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
                    )
        elif parametrization == "Gorgucci":
            if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
                warn(
                    "Unable to plot theoretical self-consistency curve. "
                    "Frequency band not provided"
                )
            else:
                zdr_th = np.arange(0.0, 3.5 + bins_zdr_step, bins_zdr_step)
                if zdr_kdpzh_dict["freq_band"] == "C":
                    zdr_lin = np.ma.power(10.0, 0.1 * zdr_th)
                    kdpzh_th = 18.2 * np.power(zdr_lin, -1.28)
                    labely = "10e5*KDP/ZH^0.95 [(deg*m3)/(km*mm6)"
                else:
                    warn(
                        "Unable to plot theoretical self-consistency curve. "
                        "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
                    )
        elif parametrization == "Vaccarono":
            if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
                warn(
                    "Unable to plot theoretical self-consistency curve. "
                    "Frequency band not provided"
                )
            else:
                zdr_th = np.arange(0.0, 3.5 + bins_zdr_step, bins_zdr_step)
                if zdr_kdpzh_dict["freq_band"] == "C":
                    zdr_lin = np.ma.power(10.0, 0.1 * zdr_th)
                    kdpzh_th = 17.7 * np.power(zdr_lin, -2.09)
                    labely = "10e5*KDP^0.85/ZH^0.91 [(deg*m3)/(km*mm6)"
                else:
                    warn(
                        "Unable to plot theoretical self-consistency curve. "
                        "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
                    )

    if kdpzh_th is not None and parametrization == "Gorgucci":
        kdpzh = 1e5 * kdp / np.ma.power(zh, 0.95)
    elif kdpzh_th is not None and parametrization == "Vaccarono":
        kdpzh = 1e5 * np.ma.power(kdp, 0.85) / np.ma.power(zh, 0.95)
    else:
        kdpzh = 1e5 * kdp / zh

    if retrieve_relation:
        zdr_min = zdr.min()
        zdr_max = zdr.max()

        ind_min = np.where(bins_zdr_edges >= zdr_min)[0][0]
        ind_max = np.where(bins_zdr_edges < zdr_max)[0][-1]
        zdr_int = bins_zdr_edges[ind_min : ind_max + 1]
        zdr_par = bins_zdr_centers[ind_min:ind_max]
        kdpzh_par = np.ma.masked_all(zdr_par.size)
        for ind, zdr_left in enumerate(zdr_int[:-1]):
            zdr_right = zdr_int[ind + 1]
            ind_val = np.where(np.logical_and(zdr >= zdr_left, zdr < zdr_right))[0]
            if ind_val.size == 0:
                continue
            kdpzh_par[ind] = np.ma.median(kdpzh[ind_val])

    # prepare histogram
    zdr[zdr < bins_zdr_centers[0]] = bins_zdr_centers[0]
    zdr[zdr > bins_zdr_centers[-1]] = bins_zdr_centers[-1]

    kdpzh[kdpzh < bins_kdpzh_centers[0]] = bins_kdpzh_centers[0]
    kdpzh[kdpzh > bins_kdpzh_centers[-1]] = bins_kdpzh_centers[-1]

    hist2d, bins_zdr_edges, bins_kdpzh_edges = np.histogram2d(
        zdr, kdpzh, bins=[bins_zdr_edges, bins_kdpzh_edges]
    )

    hist2d = np.ma.masked_equal(hist2d, 0)

    if normalize:
        hist2d = hist2d / np.expand_dims(np.ma.sum(hist2d, axis=-1), axis=1)
        clabel = "Occurrence density\n(For each ZDR bin)"
    else:
        clabel = "Number of gates"
        vmin = None
        vmax = None

    if not retrieve_relation and not plot_theoretical:
        fname_list = _plot_time_range(
            bins_zdr_edges,
            bins_kdpzh_edges,
            hist2d,
            None,
            fname_list,
            titl="self-consistency",
            xlabel="ZDR [dB]",
            ylabel=labely,
            clabel=clabel,
            vmin=vmin,
            vmax=vmax,
            save_fig=True,
            dpi=dpi,
        )

        return fname_list

    fig, ax = _plot_time_range(
        bins_zdr_edges,
        bins_kdpzh_edges,
        hist2d,
        None,
        fname_list,
        titl="self-consistency",
        xlabel="ZDR [dB]",
        ylabel=labely,
        clabel=clabel,
        vmin=vmin,
        vmax=vmax,
        save_fig=False,
        dpi=dpi,
    )

    ax.autoscale(False)

    if retrieve_relation:
        ax.plot(zdr_par, kdpzh_par, "rx-", linewidth=3)

    if plot_theoretical and kdpzh_th is not None:
        ax.plot(zdr_th, kdpzh_th, "r", linewidth=3)

    for fname in fname_list:
        fig.savefig(fname, dpi=72)
    plt.close(fig)

    return fname_list


def plot_selfconsistency_instrument2(
    zdr,
    kdp,
    zh,
    fname_list,
    bins_zh_step=0.5,
    bins_zh_min=-30.0,
    bins_zh_max=80.0,
    normalize=True,
    vmin=0.0,
    vmax=0.01,
    parametrization="None",
    zdr_kdpzh_dict=None,
    dpi=72,
):
    """
    plots the ZDR-KDP/ZH relationship obtained by an instrument. The
    theoretical curve and the retrieved curve

    Parameters
    ----------
    zdr, kdp, zh : 1D ndarray
        The valid values of ZDR [dB], KDP [deg/km] and Zh [dBZ] collected by
        the instrument
    fname_list : list of str
        list of names of the files where to store the plot
    bins_zh_step : float
        The step of the ZH of the histogram [dB]
    bins_zh_min, bins_zh_max : float
        The limits of the ZH of the histograms (bins center) [dBZ]
    normalize : Bool
        If True the occurrence density of ZH/KDP for each ZDR bin is going to
        be represented. Otherwise it will show the number of gates at each bin
    vmin, vmax : float
        min and max values of the colorbar
    parametrization : str
        The type of parametrization for the self-consistency curves. Can be
        'None', 'Gourley', 'Wolfensberger', 'Louf', 'Gorgucci' or 'Vaccarono'.
        'None' will use tables contained in zdr_kdpzh_dict. The parametrized
        curves are obtained from literature except for Wolfensberger that was
        derived from disdrometer data obtained by MeteoSwiss and EPFL. All
        parametrizations are valid for C-band only except that of Gourley.
    zdr_kdpzh_dict : dict
        dictionary containing a look up table relating ZDR with KDP/Zh for
        different elevations and the frequency band of the radar
    dpi : int
        dots per inch

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """

    # prepare bins for histogram
    bins_zh_centers = np.arange(bins_zh_min, bins_zh_max + bins_zh_step, bins_zh_step)
    bins_zh_edges = np.append(
        bins_zh_centers - bins_zh_step / 2.0, bins_zh_centers[-1] + bins_zh_step / 2.0
    )

    if parametrization == "None":
        if zdr_kdpzh_dict is not None and "zdr_kdpzh" in zdr_kdpzh_dict:
            zdr_th_table = zdr_kdpzh_dict["zdr_kdpzh"][0][0]
            kdpzh_th_table = zdr_kdpzh_dict["zdr_kdpzh"][0][1]

            # sort values by ZDR
            ind_zdr_sorted = np.argsort(zdr)
            zh = zh[ind_zdr_sorted]
            kdp = kdp[ind_zdr_sorted]

            # get value of KDP/ZH as interpolation of look up table values
            kdpzh_th = np.interp(zdr[ind_zdr_sorted], zdr_th_table, kdpzh_th_table)

        else:
            warn("Unable to plot self-consistency curve. " "Relationship not provided")
            return None
    elif parametrization == "Gourley":
        if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Frequency band not provided"
            )
            return None
        if zdr_kdpzh_dict["freq_band"] == "S":
            kdpzh_th = 1e-5 * (
                3.696 - 1.963 * zdr + 0.504 * zdr * zdr - 0.051 * zdr * zdr * zdr
            )
        elif zdr_kdpzh_dict["freq_band"] == "C":
            kdpzh_th = 1e-5 * (
                6.746 - 2.970 * zdr + 0.711 * zdr * zdr - 0.079 * zdr * zdr * zdr
            )
        elif zdr_kdpzh_dict["freq_band"] == "X":
            kdpzh_th = 1e-5 * (
                11.74 - 4.020 * zdr - 0.140 * zdr * zdr + 0.130 * zdr * zdr * zdr
            )
        else:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
            )
            return None
    elif parametrization == "Wolfensberger":
        if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Frequency band not provided"
            )
            return None
        if zdr_kdpzh_dict["freq_band"] == "C":
            kdpzh_th = 1e-5 * (
                3.199 * np.ma.exp(-7.767e-1 * zdr) - 0.4436 * zdr + 3.464
            )
        else:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
            )
            return None
    elif parametrization == "Louf":
        if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Frequency band not provided"
            )
            return None
        if zdr_kdpzh_dict["freq_band"] == "C":
            kdpzh_th = 1e-5 * (
                6.607 - 4.577 * zdr + 1.577 * zdr * zdr - 0.23 * zdr * zdr * zdr
            )
        else:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
            )
            return None
    elif parametrization == "Gorgucci":
        if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Frequency band not provided"
            )
            return None
        if zdr_kdpzh_dict["freq_band"] == "C":
            zdr_lin = np.ma.power(10.0, 0.1 * zdr)
            kdpzh_th = 1e-5 * (18.2 * np.power(zdr_lin, -1.28))
        else:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
            )
            return None
    elif parametrization == "Vaccarono":
        if zdr_kdpzh_dict is None or "freq_band" not in zdr_kdpzh_dict:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Frequency band not provided"
            )
            return None
        if zdr_kdpzh_dict["freq_band"] == "C":
            zdr_lin = np.ma.power(10.0, 0.1 * zdr)
            kdpzh_th = 1e-5 * (17.7 * np.power(zdr_lin, -2.09))
        else:
            warn(
                "Unable to plot theoretical self-consistency curve. "
                "Unknown frequency band " + zdr_kdpzh_dict["freq_band"]
            )
            return None

    if parametrization == "Gorgucci":
        zh_th = 10.0 * np.ma.log10(np.ma.power(kdp / kdpzh_th, 1 / 0.95))
    elif kdpzh_th is not None and parametrization == "Vaccarono":
        zh_th = 10.0 * np.ma.log10(
            np.ma.power(np.ma.power(kdp, 0.85) / kdpzh_th, 1 / 0.95)
        )
    else:
        zh_th = 10.0 * np.ma.log10(kdp / kdpzh_th)

    # prepare histogram
    zh[zh < bins_zh_centers[0]] = bins_zh_centers[0]
    zh[zh > bins_zh_centers[-1]] = bins_zh_centers[-1]

    zh_th[zh_th < bins_zh_centers[0]] = bins_zh_centers[0]
    zh_th[zh_th > bins_zh_centers[-1]] = bins_zh_centers[-1]

    hist2d, bins_zh_edges, _ = np.histogram2d(
        zh, zh_th, bins=[bins_zh_edges, bins_zh_edges]
    )
    hist2d = np.ma.masked_equal(hist2d, 0)

    if normalize:
        hist2d = hist2d / np.expand_dims(np.ma.sum(hist2d, axis=-1), axis=1)
        clabel = "Occurrence density\n(For each ZH bin)"
    else:
        clabel = "Number of gates"
        vmin = None
        vmax = None

    fig, ax = _plot_time_range(
        bins_zh_edges,
        bins_zh_edges,
        hist2d,
        None,
        fname_list,
        titl="self-consistency",
        xlabel="ZH measured [dBZ]",
        ylabel="ZH selfcons [dBZ]",
        clabel=clabel,
        vmin=vmin,
        vmax=vmax,
        save_fig=False,
        dpi=dpi,
    )

    ax.autoscale(False)
    ax.plot(bins_zh_centers, bins_zh_centers, "rx-", linewidth=3)

    for fname in fname_list:
        fig.savefig(fname, dpi=72)
    plt.close(fig)

    return fname_list


def plot_scatter_comp(
    value1,
    value2,
    fname_list,
    labelx="Sensor 1",
    labely="Sensor 2",
    titl="Scatter",
    axis=None,
    metadata=None,
    dpi=72,
    ax=None,
    fig=None,
    save_fig=True,
    point_format="bx",
    write_stats=True,
):
    """
    plots the scatter between two time series

    Parameters
    ----------
    value1 : float array
        values of the first time series
    value2 : float array
        values of the second time series
    fname_list : list of str
        list of names of the files where to store the plot
    labelx : str
        The label of the X axis
    labely : str
        The label of the Y axis
    titl : str
        The figure title
    axis : str
        type of axis
    metadata : string
        a string containing metadata
    dpi : int
        dots per inch
    fig : Figure
        Figure to add the colorbar to. If none a new figure will be created
    ax : Axis
        Axis to plot on. if fig is None a new axis will be created
    save_fig : bool
        if true save the figure if false it does not close the plot and
        returns the handle to the figure
    point_format : str
        format of the scatter point
    write_stats : bool
        If set to True will write the RMSE and bias on the plot. Default is true.

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    max_value = np.ma.max([np.max(value1), np.max(value2)])

    if fig is None:
        fig, ax = plt.subplots(figsize=[7, 7], dpi=dpi)

        ax.plot(value1, value2, point_format)
        ax.set_xlabel(labelx)
        ax.set_ylabel(labely)
        ax.set_title(titl)

        if axis == "equal":
            ax.axis([0, max_value, 0, max_value])
            ax.plot([0, max_value], [0, max_value], "k--")
            ax.set(adjustable="box", aspect="equal")

        if metadata is not None:
            ax.text(
                0.05,
                0.95,
                metadata,
                horizontalalignment="left",
                verticalalignment="top",
                transform=ax.transAxes,
            )

        # turn on the grid
        ax.grid()
        # Make a tight layout
        fig.tight_layout()
    else:
        ax.autoscale(False)
        ax.plot(value1, value2, point_format)

    if write_stats:
        for txt in ax.texts:
            txt.remove()
        rmse = np.sqrt(np.nanmean((value1 - value2) ** 2))
        bias = np.nanmean(value1 - value2)

        ax.text(
            0.01,
            0.98,
            f"RMSE={rmse:2.2f}\n Bias (r-g)={bias:2.2f}",
            horizontalalignment="left",
            verticalalignment="top",
            transform=ax.transAxes,
        )

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list
    return (fig, ax)


def plot_sun_hits(field, field_name, fname_list, prdcfg):
    """
    plots the sun hits

    Parameters
    ----------
    radar : Radar object
        object containing the radar data to plot
    field_name : str
        name of the radar field to plot
    altitude : float
        the altitude [m MSL] to be plotted
    prdcfg : dict
        dictionary containing the product configuration
    fname_list : list of str
        list of names of the files where to store the plot

    Returns
    -------
    fname_list : list of str
        list of names of the created plots

    """
    azmin = prdcfg["sunhitsImageConfig"]["azmin"]
    azmax = prdcfg["sunhitsImageConfig"]["azmax"]
    elmin = prdcfg["sunhitsImageConfig"]["elmin"]
    elmax = prdcfg["sunhitsImageConfig"]["elmax"]

    field_dict = pyart.config.get_metadata(field_name)

    # display data
    dpi = 72
    if "dpi" in prdcfg["sunhitsImageConfig"]:
        dpi = prdcfg["sunhitsImageConfig"]["dpi"]

    fig = plt.figure(
        figsize=[
            prdcfg["sunhitsImageConfig"]["xsize"],
            prdcfg["sunhitsImageConfig"]["ysize"],
        ],
        dpi=dpi,
    )
    ax = fig.add_subplot(111)
    cmap = pyart.config.get_field_colormap(field_name)
    vmin, vmax = pyart.config.get_field_limits(field_name)
    titl = (
        prdcfg["timeinfo"].strftime("%Y-%m-%d")
        + "\n"
        + get_field_name(field_dict, field_name)
    )

    cax = ax.imshow(
        field,
        extent=(azmin, azmax, elmin, elmax),
        origin="lower",
        cmap=cmap,
        vmin=vmin,
        vmax=vmax,
        interpolation="none",
    )
    ax.set_xlabel("rad_az-sun_az (deg)")
    ax.set_ylabel("rad_el-sun_el (deg)")
    ax.set_title(titl)

    # plot the colorbar and set the label.
    label = get_colobar_label(field_dict, field_name)
    cb = fig.colorbar(cax)
    cb.set_label(label)

    # Make a tight layout
    fig.tight_layout()

    for fname in fname_list:
        fig.savefig(fname, dpi=dpi)
    plt.close(fig)

    return fname_list


def _plot_sunscan(
    rad_az,
    rad_el,
    rad_data,
    sun_hits,
    field_name,
    fname_list,
    titl="AZ-EL sunscan plot",
    xlabel="Azimuth (deg)",
    ylabel="Elevation (deg)",
    clabel=None,
    vmin=None,
    vmax=None,
    figsize=(10, 8),
    save_fig=True,
    dpi=72,
):
    """
    plots a AZ-EL plot of a sunscan

    Parameters
    ----------
    rad_time : 1D array
        array containing the x dimension (typically time)
    rad_range : 1D array
        array containing the y dimension (typically range)
    rad_data : 2D array
        array containing the data to plot
    sun_hits : dict
        dictionary containing the sun hits data
    field_name : str or None
        field name. Used to define plot characteristics
    fname_list : list of str
        list of names of the files where to store the plot
    titl : str
        Plot title
    xlabel, ylabel : str
        x- and y-axis labels
    clabel : str or None
        colorbar label
    vmin, vmax : float
        min and max values of the color bar
    figsize : list
        figure size [xsize, ysize]
    save_fig : bool
        If true the figure is saved in files fname_list. Otherwise the fig and
        ax is returned
    dpi : int
        dpi

    Returns
    -------
    fname_list : list of str
        list of names of the created plots or
    fig, ax : object
        handles to fig and ax objects

    """
    # display data
    norm = None
    cmap = None
    ticks = None
    ticklabs = None
    if field_name is not None:
        field_dict = pyart.config.get_metadata(field_name)
        if clabel is None:
            clabel = get_colobar_label(field_dict, field_name)

        cmap = pyart.config.get_field_colormap(field_name)

        norm, ticks, ticklabs = get_norm(field_name)
        if vmin is None or vmax is None:
            vmin = vmax = None
            if norm is None:  # if norm is set do not override with vmin/vmax
                vmin, vmax = pyart.config.get_field_limits(field_name)
        else:
            norm = None
    else:
        if clabel is None:
            clabel = "value"
        if vmin is None:
            vmin = np.ma.min(rad_data)
        if vmax is None:
            vmax = np.ma.max(rad_data)

    fig = plt.figure(figsize=figsize, dpi=dpi)
    ax = fig.add_subplot(111)

    T, R = np.meshgrid(rad_az, rad_el)
    cax = ax.pcolormesh(
        T, R, np.ma.transpose(rad_data), cmap=cmap, vmin=vmin, vmax=vmax, norm=norm
    )
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.set_title(titl)

    # Add scatter points of detected sun hits location
    ax.scatter(sun_hits["rad_az"], sun_hits["rad_el"], s=7.0, c="red")

    cb = fig.colorbar(cax)
    if ticks is not None:
        cb.set_ticks(ticks)
    if ticklabs:
        cb.set_ticklabels(ticklabs)
    cb.set_label(clabel)

    # Make a tight layout
    fig.tight_layout()

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list
    return fig, ax


def _plot_time_range(
    rad_time,
    rad_range,
    rad_data,
    field_name,
    fname_list,
    titl="Time-Range plot",
    xlabel="time (s from start time)",
    ylabel="range (Km)",
    clabel=None,
    vmin=None,
    vmax=None,
    figsize=(10, 8),
    save_fig=True,
    dpi=72,
):
    """
    plots a time-range plot

    Parameters
    ----------
    rad_time : 1D array
        array containing the x dimension (typically time)
    rad_range : 1D array
        array containing the y dimension (typically range)
    rad_data : 2D array
        array containing the data to plot
    field_name : str or None
        field name. Used to define plot characteristics
    fname_list : list of str
        list of names of the files where to store the plot
    titl : str
        Plot title
    xlabel, ylabel : str
        x- and y-axis labels
    clabel : str or None
        colorbar label
    vmin, vmax : float
        min and max values of the color bar
    figsize : list
        figure size [xsize, ysize]
    save_fig : bool
        If true the figure is saved in files fname_list. Otherwise the fig and
        ax is returned
    dpi : int
        dpi

    Returns
    -------
    fname_list : list of str
        list of names of the created plots or
    fig, ax : object
        handles to fig and ax objects

    """
    # display data
    norm = None
    cmap = None
    ticks = None
    ticklabs = None
    if field_name is not None:
        field_dict = pyart.config.get_metadata(field_name)
        if clabel is None:
            clabel = get_colobar_label(field_dict, field_name)

        cmap = pyart.config.get_field_colormap(field_name)

        norm, ticks, ticklabs = get_norm(field_name)
        if vmin is None or vmax is None:
            vmin = vmax = None
            if norm is None:  # if norm is set do not override with vmin/vmax
                vmin, vmax = pyart.config.get_field_limits(field_name)
        else:
            norm = None
    else:
        if clabel is None:
            clabel = "value"
        if vmin is None:
            vmin = np.ma.min(rad_data)
        if vmax is None:
            vmax = np.ma.max(rad_data)

    fig = plt.figure(figsize=figsize, dpi=dpi)
    ax = fig.add_subplot(111)

    T, R = np.meshgrid(rad_time, rad_range)
    cax = ax.pcolormesh(
        T, R, np.ma.transpose(rad_data), cmap=cmap, vmin=vmin, vmax=vmax, norm=norm
    )
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.set_title(titl)

    cb = fig.colorbar(cax)
    if ticks is not None:
        cb.set_ticks(ticks)
    if ticklabs:
        cb.set_ticklabels(ticklabs)
    cb.set_label(clabel)

    # Make a tight layout
    fig.tight_layout()

    if save_fig:
        for fname in fname_list:
            fig.savefig(fname, dpi=dpi)
        plt.close(fig)

        return fname_list

    return fig, ax
