Create a pseudosection plot of type 2.

    For a given measurement data set, create plots that graphically show
    the data in a 2D color plot. Hereby, x and y coordinates in the plot
    are determined by the current dipole (x-axis) and voltage dipole
    (y-axis) of the corresponding measurement configurations.

    This type of rawdata plot can plot any type of measurement
    configurations, i.e., it is not restricted to certain types of
    configurations such as Dipole-dipole or Wenner configurations. However,
    spatial inferences cannot be made from the plots for all configuration
    types.

    Coordinates are generated by separately sorting the dipoles
    (current/voltage) along the first electrode, and then subsequently
    sorting by the difference (skip) between both electrodes.

    Note that this type of raw data plot does not take into account the
    real electrode spacing of the measurement setup.

    Type 2 plots can show normal and reciprocal data at the same time.
    Hereby the upper left triangle of the plot area usually contains normal
    data, and the lower right triangle contains the corresponding
    reciprocal data. Therefore a quick assessment of normal-reciprocal
    differences can be made by visually comparing the symmetry on the 1:1
    line going from the lower left corner to the upper right corner.

    Note that this interpretation usually only holds for Dipole-Dipole data
    (and the related Wenner configurations).

    Parameters
    ----------

    dataobj: ERT container|pandas.DataFrame
        Container or DataFrame with data to plot
    column: string
        Column key to plot
    ax: matplotlib.Axes object, optional
        axes object to plot to. If not provided, a new figure and axes
        object will be created and returned
    nocb: bool, optional
        if set to False, don't plot the colorbar
    cblabel: string, optional
        label for the colorbar
    cbmin: float, optional
        colorbar minimum
    cbmax: float, optional
        colorbar maximum
    xlabel: string, optional
        xlabel for the plot
    ylabel: string, optional
        ylabel for the plot
    do_not_saturate: bool, optional
        if set to True, then values outside the colorbar range will not
        saturate with the respective limit colors. Instead, values lower
        than the CB are colored "cyan" and vaues above the CB limit are
        colored "red"
    log10: bool, optional
        if set to True, plot the log10 values of the provided data

    Returns
    -------
    fig:
        figure object
    ax:
        axes object
    cb:
        colorbar object

    Examples
    --------

    You can just supply a pandas.DataFrame to the plot function:

    .. plot::
        :include-source:

        import numpy as np
        configs = np.array((
            (1, 2, 4, 3),
            (1, 2, 5, 4),
            (1, 2, 6, 5),
            (2, 3, 5, 4),
            (2, 3, 6, 5),
            (3, 4, 6, 5),
        ))
        measurements = np.random.random(configs.shape[0])
        import pandas as pd
        df = pd.DataFrame(configs, columns=['a', 'b', 'm', 'n'])
        df['measurements'] = measurements

        from reda.plotters.pseudoplots import plot_pseudosection_type2
        fig, ax, cb = plot_pseudosection_type2(
           dataobj=df,
           column='measurements',
        )

    You can also supply axes to plot to:

    .. plot::
        :include-source:

        import numpy as np
        configs = np.array((
            (1, 2, 4, 3),
            (1, 2, 5, 4),
            (1, 2, 6, 5),
            (2, 3, 5, 4),
            (2, 3, 6, 5),
            (3, 4, 6, 5),
        ))
        measurements = np.random.random(configs.shape[0])
        measurements2 = np.random.random(configs.shape[0])

        import pandas as pd
        df = pd.DataFrame(configs, columns=['a', 'b', 'm', 'n'])
        df['measurements'] = measurements
        df['measurements2'] = measurements2

        from reda.plotters.pseudoplots import plot_pseudosection_type2

        fig, axes = plt.subplots(1, 2)

        plot_pseudosection_type2(
            df,
            column='measurements',
            ax=axes[0],
            cblabel='this label',
            xlabel='xlabel',
            ylabel='ylabel',
        )
        plot_pseudosection_type2(
            df,
            column='measurements2',
            ax=axes[1],
            cblabel='measurement 2',
            xlabel='xlabel',
            ylabel='ylabel',
        )
        fig.tight_layout()

    >>> from reda.testing.containers import ERTContainer_nr
    >>> import reda.plotters.pseudoplots as ps
    >>> fig, axes, cb = ps.plot_pseudosection_type2(ERTContainer_nr, 'r')

def plot_pseudosection_type2(dataobj, column, **kwargs):
    """Create a pseudosection plot of type 2.

    For a given measurement data set, create plots that graphically show
    the data in a 2D color plot. Hereby, x and y coordinates in the plot
    are determined by the current dipole (x-axis) and voltage dipole
    (y-axis) of the corresponding measurement configurations.

    This type of rawdata plot can plot any type of measurement
    configurations, i.e., it is not restricted to certain types of
    configurations such as Dipole-dipole or Wenner configurations. However,
    spatial inferences cannot be made from the plots for all configuration
    types.

    Coordinates are generated by separately sorting the dipoles
    (current/voltage) along the first electrode, and then subsequently
    sorting by the difference (skip) between both electrodes.

    Note that this type of raw data plot does not take into account the
    real electrode spacing of the measurement setup.

    Type 2 plots can show normal and reciprocal data at the same time.
    Hereby the upper left triangle of the plot area usually contains normal
    data, and the lower right triangle contains the corresponding
    reciprocal data. Therefore a quick assessment of normal-reciprocal
    differences can be made by visually comparing the symmetry on the 1:1
    line going from the lower left corner to the upper right corner.

    Note that this interpretation usually only holds for Dipole-Dipole data
    (and the related Wenner configurations).

    Parameters
    ----------

    dataobj: ERT container|pandas.DataFrame
        Container or DataFrame with data to plot
    column: string
        Column key to plot
    ax: matplotlib.Axes object, optional
        axes object to plot to. If not provided, a new figure and axes
        object will be created and returned
    nocb: bool, optional
        if set to False, don't plot the colorbar
    cblabel: string, optional
        label for the colorbar
    cbmin: float, optional
        colorbar minimum
    cbmax: float, optional
        colorbar maximum
    xlabel: string, optional
        xlabel for the plot
    ylabel: string, optional
        ylabel for the plot
    do_not_saturate: bool, optional
        if set to True, then values outside the colorbar range will not
        saturate with the respective limit colors. Instead, values lower
        than the CB are colored "cyan" and vaues above the CB limit are
        colored "red"
    log10: bool, optional
        if set to True, plot the log10 values of the provided data

    Returns
    -------
    fig:
        figure object
    ax:
        axes object
    cb:
        colorbar object

    Examples
    --------

    You can just supply a pandas.DataFrame to the plot function:

    .. plot::
        :include-source:

        import numpy as np
        configs = np.array((
            (1, 2, 4, 3),
            (1, 2, 5, 4),
            (1, 2, 6, 5),
            (2, 3, 5, 4),
            (2, 3, 6, 5),
            (3, 4, 6, 5),
        ))
        measurements = np.random.random(configs.shape[0])
        import pandas as pd
        df = pd.DataFrame(configs, columns=['a', 'b', 'm', 'n'])
        df['measurements'] = measurements

        from reda.plotters.pseudoplots import plot_pseudosection_type2
        fig, ax, cb = plot_pseudosection_type2(
           dataobj=df,
           column='measurements',
        )

    You can also supply axes to plot to:

    .. plot::
        :include-source:

        import numpy as np
        configs = np.array((
            (1, 2, 4, 3),
            (1, 2, 5, 4),
            (1, 2, 6, 5),
            (2, 3, 5, 4),
            (2, 3, 6, 5),
            (3, 4, 6, 5),
        ))
        measurements = np.random.random(configs.shape[0])
        measurements2 = np.random.random(configs.shape[0])

        import pandas as pd
        df = pd.DataFrame(configs, columns=['a', 'b', 'm', 'n'])
        df['measurements'] = measurements
        df['measurements2'] = measurements2

        from reda.plotters.pseudoplots import plot_pseudosection_type2

        fig, axes = plt.subplots(1, 2)

        plot_pseudosection_type2(
            df,
            column='measurements',
            ax=axes[0],
            cblabel='this label',
            xlabel='xlabel',
            ylabel='ylabel',
        )
        plot_pseudosection_type2(
            df,
            column='measurements2',
            ax=axes[1],
            cblabel='measurement 2',
            xlabel='xlabel',
            ylabel='ylabel',
        )
        fig.tight_layout()

    >>> from reda.testing.containers import ERTContainer_nr
    >>> import reda.plotters.pseudoplots as ps
    >>> fig, axes, cb = ps.plot_pseudosection_type2(ERTContainer_nr, 'r')

    """
    if isinstance(dataobj, pd.DataFrame):
        df = dataobj
    else:
        df = dataobj.data

    c = df[['a', 'b', 'm', 'n']].values

    AB_ids = _get_unique_identifiers(c[:, 0:2])
    MN_ids = _get_unique_identifiers(c[:, 2:4])

    ab_sorted = np.sort(c[:, 0:2], axis=1)
    mn_sorted = np.sort(c[:, 2:4], axis=1)

    AB_coords = [
        AB_ids[x] for x in
        (ab_sorted[:, 0] * 1e5 + ab_sorted[:, 1]).astype(int)
    ]
    MN_coords = [
        MN_ids[x] for x in
        (mn_sorted[:, 0] * 1e5 + mn_sorted[:, 1]).astype(int)
    ]

    # check for duplicate positions
    ABMN_coords = np.vstack((AB_coords, MN_coords)).T.copy()
    _, counts = np.unique(
        ABMN_coords.view(
            ABMN_coords.dtype.descr * 2
        ),
        return_counts=True,
    )
    # import IPython
    # IPython.embed()
    # exit()
    if np.any(counts > 1):
        print('found duplicate coordinates!')
        # duplicate_configs = np.where(counts > 1)[0]
        # print('duplicate configs:')
        # print('A B M N')
        # for i in duplicate_configs:
        #     print(c[i, :])

    # prepare matrix
    plot_values = np.squeeze(df[column].values)

    if kwargs.get('log10', False):
        plot_values = np.log10(plot_values)

    C = np.zeros((len(MN_ids.items()), len(AB_ids))) * np.nan
    C[MN_coords, AB_coords] = plot_values

    # for display purposes, reverse the first dimension
    C = C[::-1, :]

    ax = kwargs.get('ax', None)
    if ax is None:
        fig, ax = plt.subplots(1, 1, figsize=(15 / 2.54, 10 / 2.54))
    fig = ax.get_figure()

    cmap = mpl.cm.get_cmap('viridis')
    if kwargs.get('do_not_saturate', False):
        cmap.set_over(
            color='r'
        )
        cmap.set_under(
            color='c'
        )
    im = ax.matshow(
        C,
        interpolation='none',
        cmap=cmap,
        aspect='auto',
        vmin=kwargs.get('cbmin', None),
        vmax=kwargs.get('cbmax', None),
        extent=[
            0, max(AB_coords),
            0, max(MN_coords),
        ],
    )

    max_xy = max((max(AB_coords), max(MN_coords)))
    ax.plot(
        (0, max_xy),
        (0, max_xy),
        '-',
        color='k',
        linewidth=1.0,
    )

    cb = None
    if not kwargs.get('nocb', False):
        cb = fig.colorbar(im, ax=ax)
        cb.set_label(
            kwargs.get('cblabel', units.get_label(column))
        )

    ax.set_xlabel(
        kwargs.get('xlabel', 'current dipoles')
    )
    ax.set_ylabel(
        kwargs.get('ylabel', 'voltage dipoles')
    )

    return fig, ax, cb