"""Convenience functions that don't directly deal with plotting or bootstrap computations are placed here."""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/API/misc_tools.ipynb.

# %% auto 0
__all__ = ['merge_two_dicts', 'unpack_and_add', 'print_greeting', 'get_varname', 'get_unique_categories', 'get_params',
           'get_kwargs', 'get_color_palette', 'initialize_fig', 'get_plot_groups', 'add_counts_to_ticks',
           'extract_contrast_plotting_ticks', 'set_xaxis_ticks_and_lims', 'show_legend', 'gardner_altman_adjustments',
           'draw_zeroline', 'redraw_independent_spines', 'redraw_dependent_spines', 'extract_group_summaries',
           'color_picker', 'prepare_bars_for_plot']

# %% ../nbs/API/misc_tools.ipynb 4
import datetime as dt
import numpy as np
from numpy import repeat
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib
import matplotlib.axes as axes

# %% ../nbs/API/misc_tools.ipynb 5
def merge_two_dicts(
    x: dict, y: dict
) -> dict:  # A dictionary containing a union of all keys in both original dicts.
    """
    Given two dicts, merge them into a new dict as a shallow copy.
    Any overlapping keys in `y` will override the values in `x`.

    Taken from [here](https://stackoverflow.com/questions/38987/
    how-to-merge-two-python-dictionaries-in-a-single-expression)

    """
    z = x.copy()
    z.update(y)
    return z


def unpack_and_add(l, c):
    """Convenience function to allow me to add to an existing list
    without altering that list."""
    t = [a for a in l]
    t.append(c)
    return t


def print_greeting():
    """
    Generates a greeting message based on the current time, along with the version information of DABEST.

    This function dynamically generates a greeting ('Good morning', 'Good afternoon', 'Good evening')
    based on the current system time. It also retrieves and displays the version of DABEST (Data Analysis
    using Bootstrap-Coupled ESTimation). The message includes a header with the DABEST version and the
    current time formatted in a user-friendly manner.

    Returns:
    str: A formatted string containing the greeting message, DABEST version, and current time.
    """
    from .__init__ import __version__

    line1 = "DABEST v{}".format(__version__)
    header = "".join(repeat("=", len(line1)))
    spacer = "".join(repeat(" ", len(line1)))

    now = dt.datetime.now()
    if 0 < now.hour < 12:
        greeting = "Good morning!"
    elif 12 < now.hour < 18:
        greeting = "Good afternoon!"
    else:
        greeting = "Good evening!"

    current_time = "The current time is {}.".format(now.ctime())

    return "\n".join([line1, header, spacer, greeting, current_time])


def get_varname(obj):
    matching_vars = [k for k, v in globals().items() if v is obj]
    if len(matching_vars) > 0:
        return matching_vars[0]
    return ""
	

def get_unique_categories(names):
    """
    Extract unique categories from various input types.
    """
    if isinstance(names, list):
        return names
    if isinstance(names, np.ndarray):
        return names  # numpy.unique() returns a sorted array
    elif isinstance(names, (pd.Categorical, pd.Series)):
        return names.cat.categories if hasattr(names, 'cat') else names.unique()
    else:
        # For dict_keys and other iterables
        return np.unique(list(names))

def get_params(
        effectsize_df: object, 
        plot_kwargs: dict,
        sankey_kwargs: dict,
        barplot_kwargs: dict
    ):
    """
    Extracts parameters from the `effectsize_df` and `plot_kwargs` objects for use in the plotter function.
    
    Parameters
    ----------
    effectsize_df : object
        A `dabest` EffectSizeDataFrame object.
    plot_kwargs : dict
        Kwargs passed to the plot function.
    sankey kwargs : dict
        Kwargs relating to the sankey diagram plots
    barplot_kwargs : dict
        Kwargs relating to the barplot
    """
    plot_data = effectsize_df._plot_data
    xvar = effectsize_df.xvar
    yvar = effectsize_df.yvar
    is_paired = effectsize_df.is_paired
    delta2 = effectsize_df.delta2
    is_mini_meta = effectsize_df.is_mini_meta
    effect_size = effectsize_df.effect_size
    proportional = effectsize_df.is_proportional
    results = effectsize_df.results
    dabest_obj = effectsize_df.dabest_obj
    all_plot_groups = dabest_obj._all_plot_groups
    idx = dabest_obj.idx
    x1_level = dabest_obj.x1_level
    experiment_label = dabest_obj.experiment_label
    
    if effect_size not in ["mean_diff", "hedges_g"] or not delta2:
        show_delta2 = False
    else:
        show_delta2 = plot_kwargs["show_delta2"]

    if effect_size != "mean_diff" or not is_mini_meta:
        show_mini_meta = False
    else:
        show_mini_meta = plot_kwargs["show_mini_meta"]

    if show_delta2 and show_mini_meta: raise ValueError("`show_delta2` and `show_mini_meta` cannot be True at the same time.")

   # Horizontal
    horizontal = plot_kwargs["horizontal"]

    # Disable Gardner-Altman plotting if any of the idxs comprise of more than
    # two groups or if it is a delta-delta plot.
    float_contrast = plot_kwargs["float_contrast"]
    if len(idx) > 1 or len(idx[0]) > 2:
        float_contrast = False

    if effect_size in ["cliffs_delta"]:
        float_contrast = False

    if show_delta2 or show_mini_meta or horizontal:
        float_contrast = False

    if not is_paired:
        show_pairs = False
    else:
        show_pairs = plot_kwargs["show_pairs"]

    # Group summaries
    group_summaries = plot_kwargs["group_summaries"]
    group_summaries = None if barplot_kwargs['errorbar'] is not None else group_summaries

    # Contrast Axes kwargs
    ci_type = plot_kwargs["ci_type"]
    if ci_type not in ["bca", "pct"]:
        raise ValueError("Invalid `ci_type`. Must be either 'bca' or 'pct'.")
        
    # Boolean for showing Baseline Curve
    show_baseline_ec = plot_kwargs["show_baseline_ec"]

    # Sankey details
    # We need to extract the `sankey` and `flow` from the kwargs
    # to use for varying different kinds of paired proportional plots
    # We also don't want to pop the parameter from the kwargs
    one_sankey = (
        False if is_paired is not None else None
    )  # Flag to indicate if only one sankey is plotted.
    two_col_sankey = (
        True if proportional and not one_sankey and sankey_kwargs["sankey"] and not sankey_kwargs["flow"] else False
    )

    # Asymmetric side for swarmplots
    asymmetric_side = (
        plot_kwargs["swarm_side"]  # Default asymmetric side is right
        if plot_kwargs["swarm_side"] is not None
        else "right" if not horizontal
        else "left"
    )  
    # Whether to show sample sizes with ticklabels
    show_sample_size = plot_kwargs["show_sample_size"]
        
    return (dabest_obj, plot_data, xvar, yvar, is_paired, effect_size, proportional, all_plot_groups, 
            idx, show_delta2, show_mini_meta, float_contrast, show_pairs, group_summaries, 
            horizontal, results, ci_type, x1_level, experiment_label, show_baseline_ec, 
            one_sankey, two_col_sankey, asymmetric_side, show_sample_size)

def get_kwargs(
        plot_kwargs: dict, 
        ytick_color,
        is_paired: bool = False
    ):
    """
    Extracts the kwargs from the `plot_kwargs` object for use in the plotter function.

    Parameters
    ----------
    plot_kwargs : dict
        Kwargs passed to the plot function.
    ytick_color : str or color list
        Color of the yticks.
    is_paired : bool, optional
        A boolean flag to determine if the plot is for paired data. Default is False.
    """
    from .misc_tools import merge_two_dicts

    # Swarmplot kwargs
    default_swarmplot_kwargs = {
        "size": plot_kwargs["raw_marker_size"],
        "alpha": plot_kwargs["raw_alpha"],
        "fontsize": plot_kwargs.get("fontsize_rawxlabel"),
    }
    if plot_kwargs["swarmplot_kwargs"] is None:
        swarmplot_kwargs = default_swarmplot_kwargs
    else:
        swarmplot_kwargs = merge_two_dicts(
            default_swarmplot_kwargs, plot_kwargs["swarmplot_kwargs"]
        )

    # Barplot kwargs
    default_barplot_kwargs = {
        "estimator": np.mean, 
        "errorbar": None,
        "width": plot_kwargs["bar_width"],
        "alpha": plot_kwargs["raw_alpha"],
        "err_kws": {'color': 'black'},
        "fontsize": plot_kwargs["fontsize_rawxlabel"]
    }
    if plot_kwargs["barplot_kwargs"] is None:
        barplot_kwargs = default_barplot_kwargs
    else:
        barplot_kwargs = merge_two_dicts(
            default_barplot_kwargs, plot_kwargs["barplot_kwargs"]
        )

    # Sankey Diagram kwargs
    default_sankey_kwargs = {
        "width": 0.4,
        "align": "center",
        "sankey": True,
        "flow": True,
        "alpha": plot_kwargs['raw_alpha'],
        "rightColor": False,
        "bar_width": 0.2,
        "fontsize": plot_kwargs.get("fontsize_rawxlabel")
    }
    if plot_kwargs["sankey_kwargs"] is None:
        sankey_kwargs = default_sankey_kwargs
    else:
        sankey_kwargs = merge_two_dicts(
            default_sankey_kwargs, plot_kwargs["sankey_kwargs"]
        )

    # Violinplot kwargs.
    default_contrast_kwargs = {
        "widths": 0.5,
        "orientation": 'vertical',
        "showextrema": False,
        "showmedians": False,
        "alpha": plot_kwargs["contrast_alpha"],
        
    }
    if plot_kwargs["contrast_kwargs"] is None:
        contrast_kwargs = default_contrast_kwargs
    else:
        contrast_kwargs = merge_two_dicts(
            default_contrast_kwargs, plot_kwargs["contrast_kwargs"]
        )

    # Slopegraph kwargs.
    default_slopegraph_kwargs = {
        "linewidth": 1, 
        "alpha": plot_kwargs["raw_alpha"],
        'jitter': 0, 
        'jitter_seed': 9876543210,
    }
    if plot_kwargs["slopegraph_kwargs"] is None:
        slopegraph_kwargs = default_slopegraph_kwargs
    else:
        slopegraph_kwargs = merge_two_dicts(
            default_slopegraph_kwargs, plot_kwargs["slopegraph_kwargs"]
        )

    # Zero reference-line kwargs.
    default_reflines_kwargs = {
        "linestyle": "solid",
        "linewidth": 0.75,
        "zorder": 2,
        "color": ytick_color,
    }
    if plot_kwargs["reflines_kwargs"] is None:
        reflines_kwargs = default_reflines_kwargs
    else:
        reflines_kwargs = merge_two_dicts(
            default_reflines_kwargs, plot_kwargs["reflines_kwargs"]
        )

    # Legend kwargs.
    default_legend_kwargs = {
        "loc": "upper left", 
        "frameon": False,
    }
    if plot_kwargs["legend_kwargs"] is None:
        legend_kwargs = default_legend_kwargs
    else:
        legend_kwargs = merge_two_dicts(
            default_legend_kwargs, plot_kwargs["legend_kwargs"]
        )

    # Group summaries kwargs.
    gs_default = {
        "mean_sd", 
        "median_quartiles", 
        None
    }
    if plot_kwargs["group_summaries"] not in gs_default:
        raise ValueError(
            "group_summaries must be one of" " these: {}.".format(gs_default)
        )

    default_group_summaries_kwargs = {
                        "zorder": 3, 
                        "lw": 2, 
                        "alpha": 1 if not is_paired else 0.6,
                        'gap_width_percent': 1.5,
                        'offset': 0.1,
                        'color': None
    }
    if plot_kwargs["group_summaries_kwargs"] is None:
        group_summaries_kwargs = default_group_summaries_kwargs
    else:
        group_summaries_kwargs = merge_two_dicts(
            default_group_summaries_kwargs, plot_kwargs["group_summaries_kwargs"]
        )

    # Redraw axes kwargs.
    redraw_axes_kwargs = {
        "colors": ytick_color,
        "facecolors": ytick_color,
        "lw": 1,
        "zorder": 10,
        "clip_on": False,
    }
    
    # Delta dots kwargs.
    default_delta_dot_kwargs = {
                "color": 'k',
                "marker": "^", 
                "alpha": 0.5, 
                "zorder": 2, 
                "size": 3, 
                "side": "right"
    }
    if plot_kwargs["delta_dot_kwargs"] is None:
        delta_dot_kwargs = default_delta_dot_kwargs
    else:
        delta_dot_kwargs = merge_two_dicts(default_delta_dot_kwargs, plot_kwargs["delta_dot_kwargs"])

    # Delta text kwargs.
    default_delta_text_kwargs = {
                "alpha": 1,
                "fontsize": 10, 
                "ha": 'center', 
                "va": 'center', 
                "rotation": 0, 
                "x_coordinates": None, 
                "y_coordinates": None,
                "offset": 0
    }
    if plot_kwargs["delta_text_kwargs"] is None:
        delta_text_kwargs = default_delta_text_kwargs
    else:
        delta_text_kwargs = merge_two_dicts(default_delta_text_kwargs, plot_kwargs["delta_text_kwargs"])

    # Reference band kwargs.
    default_reference_band_kwargs = {
                    "span_ax": False,
                    "alpha": 0.15,
                    "zorder":-3
    }
    if plot_kwargs["reference_band_kwargs"] is None:
        reference_band_kwargs = default_reference_band_kwargs
    else:
        reference_band_kwargs = merge_two_dicts(default_reference_band_kwargs, plot_kwargs["reference_band_kwargs"])

    # Swarm bars kwargs.
    default_raw_bars_kwargs = {
                    "zorder":-3,
                    "alpha": 0.2
    }
    if plot_kwargs["raw_bars_kwargs"] is None:
        raw_bars_kwargs = default_raw_bars_kwargs
    else:
        raw_bars_kwargs = merge_two_dicts(default_raw_bars_kwargs, plot_kwargs["raw_bars_kwargs"])

    # Contrast bars kwargs.
    default_contrast_bars_kwargs = {
                    "zorder":-3,
                    "alpha": 0.2
    }
    if plot_kwargs["contrast_bars_kwargs"] is None:
        contrast_bars_kwargs = default_contrast_bars_kwargs
    else:
        contrast_bars_kwargs = merge_two_dicts(default_contrast_bars_kwargs, plot_kwargs["contrast_bars_kwargs"])

    # Table axes for horizontal plot kwargs.
    default_table_kwargs = {
                'show': True,
                'color' : 'yellow',
                'alpha' : 0.2,
                'fontsize' : 12,
                'text_color' : 'black', 
                'text_units' : '',
                'control_marker' : '-',
                'fontsize_label': 12,
                'label': 'Δ'
    }
    if plot_kwargs["horizontal_table_kwargs"] is None:
        table_kwargs = default_table_kwargs
    else:
        table_kwargs = merge_two_dicts(default_table_kwargs, plot_kwargs["horizontal_table_kwargs"])

    # Gridkey kwargs.
    default_gridkey_kwargs = {
                'show_es' : plot_kwargs['gridkey_show_es'],           # If True, the gridkey will show the effect size of each comparison.
                'show_Ns' : plot_kwargs['gridkey_show_Ns'],           # If True, the gridkey will show the number of observations in eachgroup.
                'merge_pairs' : plot_kwargs['gridkey_merge_pairs'],   # If True, the gridkey will merge the pairs of groups into a single cell. This is useful for when the groups are paired.
                'delimiters': plot_kwargs['gridkey_delimiters'],      # Delimiters to split the group names.
                'marker': "\u25CF",                                   # Marker for the gridkey dots.
    }
    if plot_kwargs["gridkey_kwargs"] is None:
        gridkey_kwargs = default_gridkey_kwargs
    else:
        gridkey_kwargs = merge_two_dicts(default_gridkey_kwargs, plot_kwargs["gridkey_kwargs"])

    # Effect size marker kwargs
    default_contrast_marker_kwargs = {
                'marker': 'o',
                'markersize': plot_kwargs['contrast_marker_size'],
                'color': ytick_color,
                'alpha': 1,
                'zorder': 2,
    }
    if plot_kwargs['contrast_marker_kwargs'] is None:
        contrast_marker_kwargs = default_contrast_marker_kwargs
    else:
        contrast_marker_kwargs = merge_two_dicts(default_contrast_marker_kwargs, plot_kwargs['contrast_marker_kwargs'])

    # Effect size error bar kwargs
    default_contrast_errorbar_kwargs = {
                'color': ytick_color,
                'lw': 2,
                'linestyle': '-',
                'alpha': 1,
                'zorder': 1,
    }
    if plot_kwargs['contrast_errorbar_kwargs'] is None:
        contrast_errorbar_kwargs = default_contrast_errorbar_kwargs
    else:
        contrast_errorbar_kwargs = merge_two_dicts(default_contrast_errorbar_kwargs, plot_kwargs['contrast_errorbar_kwargs'])

    # Prop sample counts kwargs
    default_prop_sample_counts_kwargs = {
                                        'color': 'k', 
                                        'zorder': 5, 
                                        'ha': 'center', 
                                        'va': 'center'
    }
    if plot_kwargs['prop_sample_counts_kwargs'] is None:
        prop_sample_counts_kwargs = default_prop_sample_counts_kwargs
    else:
        prop_sample_counts_kwargs = merge_two_dicts(default_prop_sample_counts_kwargs, plot_kwargs['prop_sample_counts_kwargs'])


    # RM Lines kwargs
    default_contrast_paired_lines_kwargs = {
        "linestyle": "-",
        "linewidth": 2,
        "zorder": -2,
        "color": 'dimgray',
        "alpha": 1
    }
    if plot_kwargs["contrast_paired_lines_kwargs"] is None:
        contrast_paired_lines_kwargs = default_contrast_paired_lines_kwargs
    else:
        contrast_paired_lines_kwargs = merge_two_dicts(default_contrast_paired_lines_kwargs, plot_kwargs["contrast_paired_lines_kwargs"])

    # Return the kwargs.
    return (swarmplot_kwargs, barplot_kwargs, sankey_kwargs, contrast_kwargs, slopegraph_kwargs, 
            reflines_kwargs, legend_kwargs, group_summaries_kwargs, redraw_axes_kwargs, delta_dot_kwargs,
            delta_text_kwargs, reference_band_kwargs, raw_bars_kwargs, contrast_bars_kwargs, table_kwargs, gridkey_kwargs,
            contrast_marker_kwargs, contrast_errorbar_kwargs, prop_sample_counts_kwargs, contrast_paired_lines_kwargs)


def get_color_palette(
        plot_kwargs: dict, 
        plot_data: pd.DataFrame, 
        xvar: str, 
        show_pairs: bool, 
        idx: list, 
        all_plot_groups: list,
        delta2: bool,
        proportional: bool
    ):
    """
    Create the color palette to be used in the plotter function.

    Parameters
    ----------
    plot_kwargs : dict
        Kwargs passed to the plot function.
    plot_data : object (Dataframe)
        A dataframe of plot data.
    xvar : str
        The name of the x-axis variable.
    show_pairs : bool
        A boolean flag to determine if the plot is for paired data.
    idx : list
        A list of tuples containing the group names.
    all_plot_groups : list
        A list of all the group names.
    delta2 : bool
        A boolean flag to determine if the plot will have a delta-delta effect size.
    proportional : bool
        A boolean flag to determine if the plot is for a proportional plot.
    """
    sankey = True if proportional and show_pairs else False

    # Create color palette that will be shared across subplots.
    color_col = plot_kwargs["color_col"]
    if color_col is None:
        color_groups = pd.unique(plot_data[xvar])
        bootstraps_color_by_group = True
    else:
        if color_col not in plot_data.columns:
            raise KeyError("``{}`` is not a column in the data.".format(color_col))
        color_groups = pd.unique(plot_data[color_col])
        bootstraps_color_by_group = False
    if show_pairs:
        if plot_kwargs["custom_palette"] is not None:
            if delta2 or sankey:
                bootstraps_color_by_group = False
            else:
                bootstraps_color_by_group = True
        else:
            bootstraps_color_by_group = False

    # Handle the color palette.
    filled = True
    empty_circle = plot_kwargs["empty_circle"]
    color_by_subgroups = (
        True if empty_circle else False
    )  # boolean flag to determine if colour is being grouped by subgroup or the default
    if empty_circle:
        # Handling color_by_subgroups
        # For now, color_by_subgroups can only be True for multi-2-group and 2-group comparison
        if isinstance(idx[0], str):
            if len(idx) > 2:
                color_by_subgroups = False
        else:
            for group_i in idx:
                if len(group_i) > 2:
                    color_by_subgroups = False

        # filled is now a list, which determines the which group in idx has their dots filled for the swarmplot
        filled = []
        for i in range(len(idx)):
            filled.append(False)
            filled.extend([True] * (len(idx[i]) - 1))

    if color_col is not None:
        if sankey:
            names = [1, 0]
        else:
            names = color_groups if not color_by_subgroups else idx
    else:
        if sankey:
            names = [1, 0]
        else:
            names = all_plot_groups if not color_by_subgroups else idx

    n_groups = len(color_groups)
    custom_pal = plot_kwargs["custom_palette"]
    raw_desat = plot_kwargs["raw_desat"]
    contrast_desat = plot_kwargs["contrast_desat"]

    if custom_pal is None:
        unsat_colors = sns.color_palette(n_colors=n_groups)
        if empty_circle and not color_by_subgroups:
            unsat_colors = [sns.color_palette("gray")[3]] + unsat_colors
    else:
        if isinstance(custom_pal, dict):
            if delta2:
                groups_in_palette = {
                    k: custom_pal[k] for k in color_groups
                }
            elif proportional and not sankey: # barplots (unpaired proportional data)
                keys = list(custom_pal.keys())
                if all(k in keys for k in [1, 0]) and len(keys) == 2:
                    groups_in_palette = {
                        k: custom_pal[k] for k in [1, 0]
                    }
                    bootstraps_color_by_group = False
                else:
                    groups_in_palette = {
                        k: custom_pal[k] for k in all_plot_groups if k in color_groups
                    }
            elif sankey:
                groups_in_palette = {
                    k: custom_pal[k] for k in [1, 0]
                }
            elif color_col is None:
                groups_in_palette = {
                    k: custom_pal[k] for k in all_plot_groups if k in color_groups
                }
            else:
                raise ValueError("The `custom_palette` dictionary is not supported when `color_col` is not None.")

            names = groups_in_palette.keys()
            unsat_colors = groups_in_palette.values()

        elif isinstance(custom_pal, list):
            if sankey:
                if len(custom_pal) != 2:
                    raise ValueError("To specify a custom palette for a Sankey diagram, you must provide exactly two colors.")
                else:
                    groups_in_palette = {
                        k: custom_pal[k] for k in [1, 0]
                    }
                    names = groups_in_palette.keys()
                    unsat_colors = groups_in_palette.values()
            elif len(custom_pal) < n_groups:
                err1 = "The specified `custom_palette` has fewer colors than the number of groups."
                err2 = " Please specify a custom palette with at least {} colors.".format(n_groups)
                raise ValueError(err1 + err2)
            else:
                unsat_colors = custom_pal[0:n_groups]

        elif isinstance(custom_pal, str):
            # check it is in the list of matplotlib palettes.
            if custom_pal in plt.colormaps():
                unsat_colors = sns.color_palette(custom_pal, n_groups)
            else:
                err1 = "The specified `custom_palette` {}".format(custom_pal)
                err2 = " is not a matplotlib palette. Please check."
                raise ValueError(err1 + err2)

    if custom_pal is None and color_col is None:
        categories = get_unique_categories(names)
        raw_colors = [sns.desaturate(c, raw_desat) for c in unsat_colors]
        contrast_colors = [sns.desaturate(c, contrast_desat) for c in unsat_colors]
        if color_by_subgroups:
            plot_palette_raw = dict()
            plot_palette_contrast = dict()
            for i in range(len(idx)):
                for names_i in idx[i]:
                    plot_palette_raw[names_i] = raw_colors[i]
                    plot_palette_contrast[names_i] = contrast_colors[i]
        else:
            plot_palette_raw = dict(zip(categories, raw_colors))
            plot_palette_contrast = dict(zip(categories, contrast_colors))
    else:
        raw_colors = [sns.desaturate(c, raw_desat) for c in unsat_colors]
        contrast_colors = [sns.desaturate(c, contrast_desat) for c in unsat_colors]
        if color_by_subgroups:
            plot_palette_raw = dict()
            plot_palette_contrast = dict()
            for i in range(len(idx)):
                for names_i in idx[i]:
                    plot_palette_raw[names_i] = raw_colors[i]
                    plot_palette_contrast[names_i] = contrast_colors[i]
        else:
            plot_palette_raw = dict(zip(names, raw_colors))
            plot_palette_contrast = dict(zip(names, contrast_colors))
            plot_palette_sankey = dict(zip(names, unsat_colors))

    # For Sankey Diagram plot, each bar will have the same two colors if custom_pal is None
    # default color palette will be set to "hls"
    if custom_pal is None:
        plot_palette_sankey = None

    return (color_col, bootstraps_color_by_group, n_groups, filled, raw_colors,
            plot_palette_raw, plot_palette_contrast, plot_palette_sankey)

def initialize_fig(
        plot_kwargs: dict, 
        dabest_obj: object, 
        show_delta2: bool, 
        show_mini_meta: bool, 
        is_paired: bool, 
        show_pairs: bool, 
        proportional: bool,
        float_contrast: bool,
        effect_size_type: str, 
        yvar: str, 
        horizontal: bool, 
        show_table: bool,
        color_col: str,
    ):
    """
    Initialize the figure and axes for the plotter function.

    Parameters
    ----------
    plot_kwargs : dict
        Kwargs passed to the plot function.
    dabest_obj : object (EffectSizeDataFrame)
        A `dabest` EffectSizeDataFrame object.
    show_delta2 : bool
        A boolean flag to determine if the plot will have a delta-delta effect size.
    show_mini_meta : bool
        A boolean flag to determine if the plot will have a mini-meta effect size.
    is_paired : bool
        A boolean flag to determine if the plot is for paired data.
    show_pairs : bool
        A boolean flag to determine if the plot will show the paired data.
    proportional : bool
        A boolean flag to determine if the plot is for proportional data.
    float_contrast : bool
        A boolean flag to determine if the plot is for floating contrast data.
    effect_size_type : str
        The type of effect size to be plotted.
    yvar : str
        The name of the y-axis variable.
    horizontal : bool
        A boolean flag to determine if the plot is for horizontal plotting.
    show_table : dict
        A boolean flag to determine if the table will be shown in horizontal plot.
    color_col : str
        The column name for coloring the data points.
    """
    # Params
    fig_size = plot_kwargs["fig_size"]
    face_color = plot_kwargs["face_color"]
    if plot_kwargs["face_color"] is None:
        face_color = "white"

    # Create Figure and Axes
    if fig_size is None:
        all_groups_count = np.sum([len(i) for i in dabest_obj.idx])
        # Increase the width (vertical layout) or height (horizontal layout) for delta-delta or mini-meta graph
        if show_delta2 or show_mini_meta:
            all_groups_count += 1
   
        if horizontal:
            frac = 0.3 if is_paired or show_mini_meta else 0.5
            fig_size = (7, 1 + (frac * all_groups_count))
        else:
            if is_paired and show_pairs and proportional is False:
                if color_col is not None and float_contrast:
                    frac = 0.9
                else:
                    frac = 0.8
            else:
                frac = 1
            if float_contrast:
                height_inches = 4
                each_group_width_inches = 2.5 * frac
            else:
                height_inches = 6
                each_group_width_inches = 1.5 * frac

            width_inches = each_group_width_inches * all_groups_count
            fig_size = (width_inches, height_inches) 

    init_fig_kwargs = dict(figsize=fig_size, dpi=plot_kwargs["dpi"], tight_layout=True)

    width_ratios_ga = [2.5, 1]
    h_space_cummings = (0.1 if plot_kwargs["gridkey"] is not None
                        else 0.3)

    if plot_kwargs["ax"] is not None:
        # New in v0.2.6.
        # Use inset axes to create the estimation plot inside a single axes.
        # Author: Adam L Nekimken. (PR #73)
        rawdata_axes = plot_kwargs["ax"]
        ax_position = rawdata_axes.get_position()  # [[x0, y0], [x1, y1]]

        fig = rawdata_axes.get_figure()
        fig.patch.set_facecolor(face_color)

        if horizontal:
            plot_width_ratios = [1, 0.7, 0.3]
            contrast_wspace = 0.05
            contrast_axes = rawdata_axes.inset_axes(
                    [1+contrast_wspace, 0, (plot_width_ratios[1]/plot_width_ratios[0]), 1]
                    )
            if show_table:
                table_axes = rawdata_axes.inset_axes(
                        [1+contrast_wspace+(plot_width_ratios[1]/plot_width_ratios[0]), 0, (plot_width_ratios[2]/plot_width_ratios[0]), 1]
                        )
            else:
                table_axes = None

            rawdata_axes.set_position(
                    [ax_position.x0,
                     ax_position.y0,
                     (ax_position.x1 - ax_position.x0) * (plot_width_ratios[0] / sum(plot_width_ratios)),
                     (ax_position.y1 - ax_position.y0)]
                     )
            rawdata_axes.contrast_axes = contrast_axes
            rawdata_axes.table_axes = table_axes
        
        else:
            if float_contrast:
                axins = rawdata_axes.inset_axes(
                    [1, 0, width_ratios_ga[1] / width_ratios_ga[0], 1]
                )
                rawdata_axes.set_position(  # [l, b, w, h]
                    [
                        ax_position.x0,
                        ax_position.y0,
                        (ax_position.x1 - ax_position.x0)
                        * (width_ratios_ga[0] / sum(width_ratios_ga)),
                        (ax_position.y1 - ax_position.y0),
                    ]
                )

                contrast_axes = axins
            else:
                axins = rawdata_axes.inset_axes([0, -1 - h_space_cummings, 1, 1])
                plot_height = (ax_position.y1 - ax_position.y0) / (2 + h_space_cummings)
                rawdata_axes.set_position(
                    [
                        ax_position.x0,
                        ax_position.y0 + (1 + h_space_cummings) * plot_height,
                        (ax_position.x1 - ax_position.x0),
                        plot_height,
                    ]
                )

            # Set axes
            contrast_axes = axins
            rawdata_axes.contrast_axes = axins
            table_axes = None

    else:
        # Here, we hardcode some figure parameters.
        if horizontal:
            if show_table:
                fig, axx = plt.subplots(
                    ncols=3, gridspec_kw={'width_ratios' : [1,0.7,0.3], 'wspace' : 0.05}, **init_fig_kwargs
                )
            else:
                fig, axx = plt.subplots(
                    ncols=2, gridspec_kw={'width_ratios' : [1,0.7], 'wspace' : 0.05}, **init_fig_kwargs
                )
        else:
            if float_contrast:
                fig, axx = plt.subplots(
                    ncols=2,
                    gridspec_kw={"width_ratios": width_ratios_ga, "wspace": 0},
                    **init_fig_kwargs
                )
            else:
                fig, axx = plt.subplots(
                    nrows=2, gridspec_kw={"hspace": h_space_cummings}, **init_fig_kwargs
                )
        fig.patch.set_facecolor(face_color)

        # Set axes   
        rawdata_axes = axx[0]
        contrast_axes = axx[1]
        table_axes = axx[2] if horizontal and show_table else None


    # Title
    title, fontsize_title = plot_kwargs["title"], plot_kwargs["fontsize_title"]
    if title is not None:
        if plot_kwargs["ax"] is not None:
            rawdata_axes.set_title(title, fontsize=fontsize_title)
        else:
            fig.suptitle(title, fontsize=fontsize_title)

    rawdata_axes.set_frame_on(False)
    contrast_axes.set_frame_on(False)
    if horizontal and show_table:
        table_axes.set_frame_on(False)
    
    # Swarmplot ylim (Vertical) or xlim (Horizontal)
    raw_ylim = plot_kwargs["raw_ylim"]
    if raw_ylim is not None:
        if not isinstance(raw_ylim, list) and not isinstance(raw_ylim, tuple) or len(raw_ylim) != 2:
            raise ValueError("`raw_ylim` must be a tuple/list of the lower and upper bound.")
        if horizontal:
            rawdata_axes.set_xlim(raw_ylim)
        else:
            rawdata_axes.set_ylim(raw_ylim)

    # Contrastplot ylim (Vertical) or xlim (Horizontal)
    if horizontal or not float_contrast:
        contrast_ylim, delta2_ylim = plot_kwargs["contrast_ylim"], plot_kwargs["delta2_ylim"]
        if contrast_ylim is not None or (delta2_ylim is not None and show_delta2):
            if contrast_ylim is not None:
                if delta2_ylim is not None and show_delta2:
                    if contrast_ylim != delta2_ylim:
                        raise ValueError("Please check if `contrast_ylim` and `delta2_ylim` are assigned with same values.")
            else:
                contrast_ylim = delta2_ylim

            if not isinstance(contrast_ylim, list) and not isinstance(contrast_ylim, tuple) or len(contrast_ylim) != 2:
                raise ValueError("`contrast_ylim` must be a tuple/list of the lower and upper bound.")

            if effect_size_type == "cliffs_delta":
                # Ensure the ylims for a cliffs_delta plot never exceed [-1, 1].
                l = contrast_ylim[0]
                h = contrast_ylim[1]
                low = -1 if l < -1 else l
                high = 1 if h > 1 else h
                if horizontal:
                    contrast_axes.set_xlim(low, high)
                else:
                    contrast_axes.set_ylim(low, high)
            else:
                if horizontal:
                    contrast_axes.set_xlim(contrast_ylim)
                else:
                    contrast_axes.set_ylim(contrast_ylim)

    # Set raw axes y-label.
    raw_label = plot_kwargs["raw_label"]
    if raw_label is None:
        if proportional:
            raw_label = "Proportion of success" if effect_size_type != "cohens_h" else "Value"
        else:
            raw_label = yvar 

    fontsize_rawylabel = plot_kwargs["fontsize_rawylabel"]
    if horizontal:
        rawdata_axes.set_xlabel(raw_label, fontsize=fontsize_rawylabel)
        rawdata_axes.set_ylabel("")
    else:
        rawdata_axes.set_ylabel(raw_label, fontsize=fontsize_rawylabel)
        rawdata_axes.set_xlabel("")

    # Set contrast axes y-label.
    contrast_label_dict = {
        "mean_diff": "mean difference",
        "median_diff": "median difference",
        "cohens_d": "Cohen's d",
        "hedges_g": "Hedges' g",
        "cliffs_delta": "Cliff's delta",
        "cohens_h": "Cohen's h",
    }

    if proportional and effect_size_type != "cohens_h":
        default_contrast_label = "proportion difference"
    else:
        default_contrast_label = contrast_label_dict[effect_size_type]

    if plot_kwargs["contrast_label"] is None:
        if is_paired:
            contrast_label = "Paired\n{}".format(default_contrast_label)
        else:
            contrast_label = default_contrast_label.capitalize()
    else:
        contrast_label = plot_kwargs["contrast_label"]

    fontsize_contrastylabel = plot_kwargs["fontsize_contrastylabel"]

    if horizontal:
        contrast_axes.set_xlabel(contrast_label, fontsize=fontsize_contrastylabel)
    else:
        contrast_axes.set_ylabel(contrast_label, fontsize=fontsize_contrastylabel)
        if float_contrast:
            contrast_axes.yaxis.set_label_position("right")

    return fig, rawdata_axes, contrast_axes, table_axes

def get_plot_groups(
        is_paired: bool, 
        idx: list, 
        proportional: bool, 
        all_plot_groups: list
    ):
    """
    Extract the plot groups from the `idx` object for use in the plotter function.

    Parameters
    ----------
    is_paired : bool
        A boolean flag to determine if the plot is for paired data.
    idx : list
        A list of tuples containing the group names.
    proportional : bool
        A boolean flag to determine if the plot is for proportional data.
    all_plot_groups : list
        A list of all the group names.
    """

    if is_paired == "baseline":
        idx_pairs = [
            (control, test)
            for i in idx
            for control, test in zip([i[0]] * (len(i) - 1), i[1:])
        ]
        temp_idx = idx if not proportional else idx_pairs
    else:
        idx_pairs = [
            (control, test) for i in idx for control, test in zip(i[:-1], i[1:])
        ]
        temp_idx = idx if not proportional else idx_pairs

    # Determine temp_all_plot_groups based on proportional condition
    plot_groups = [item for i in temp_idx for item in i]
    temp_all_plot_groups = all_plot_groups if not proportional else plot_groups
    
    return temp_idx, temp_all_plot_groups


def add_counts_to_ticks(
        plot_data: pd.DataFrame, 
        xvar: str, 
        yvar: str, 
        rawdata_axes: axes.Axes, 
        plot_kwargs: dict, 
        flow: bool, 
        horizontal: bool
    ):
    """

    Add the counts to the raw data axes labels.

    Parameters
    ----------
    plot_data : object (Dataframe)
        A dataframe of plot data.
    xvar : str
        The name of the x-axis variable.
    yvar : str
        The name of the y-axis variable.
    rawdata_axes : object (Axes)
        The raw data axes.
    plot_kwargs : dict
        Kwargs passed to the plot function.
    flow : bool
        Whether sankey flow is enabled or not.
    horizontal : bool
        A boolean flag to determine if the plot is for horizontal plotting.
    """

    # Add the counts to the rawdata axes xticks.
    counts = plot_data.groupby(xvar, observed=False).count()[yvar]
    
    def lookup_value(text):
        try:
            return str(counts.loc[text])
        except KeyError:
            try:
                numeric_key = pd.to_numeric(text, errors='coerce')
                if pd.notnull(numeric_key):
                    return str(counts.loc[numeric_key])
            except (ValueError, KeyError):
                pass
        print(f"Key '{text}' not found in counts.")
        return "N/A"
    
    ticks_with_counts = []
    if horizontal:
        get_label, get_ticks = rawdata_axes.get_yticklabels, rawdata_axes.get_yticks
        set_label, set_major_loc_method= rawdata_axes.set_yticklabels, rawdata_axes.yaxis.set_major_locator
    else:
        get_label, get_ticks = rawdata_axes.get_xticklabels, rawdata_axes.get_xticks
        set_label, set_major_loc_method = rawdata_axes.set_xticklabels, rawdata_axes.xaxis.set_major_locator
    
    for ticklab in get_label():
        t = ticklab.get_text()

        if horizontal and not flow:
            te = t.split('v.s. ')[-1]  # Get the last line of the label
        else:
            te = t.split('\n')[-1]  # Get the last line of the label

        value = lookup_value(te)
        if horizontal:
            ticks_with_counts.append(f"{t} (N={value})")
        else:
            ticks_with_counts.append(f"{t}\n(N={value})")

    set_major_loc_method(plt.FixedLocator(get_ticks()))

    # label = ticks_with_counts if plot_kwargs['show_sample_size'] else get_label()
    # set_label(label, fontsize=plot_kwargs.get("fontsize_rawxlabel"))

    set_label(ticks_with_counts, fontsize=plot_kwargs.get("fontsize_rawxlabel"))

    # Ensure ticks are at the correct locations
    set_major_loc_method(plt.FixedLocator(get_ticks()))

def extract_contrast_plotting_ticks(
        is_paired: bool, 
        show_pairs: bool, 
        two_col_sankey: bool, 
        plot_groups: list, 
        idx: list, 
        sankey_control_group: list
    ):
    """
    Extract the contrast plotting ticks from the `idx` object for use in the plotter function.

    Parameters
    ----------
    is_paired : bool
        A boolean flag to determine if the plot is for paired data.
    show_pairs : bool
        A boolean flag to determine if the plot will show the paired data.
    two_col_sankey : bool
        A boolean flag to determine if the plot will show a two-column sankey diagram.
    plot_groups : list
        A list of the plot groups.
    idx : list
        A list of tuples containing the group names.
    sankey_control_group : list
        A list of the control group names.
    """
    # Take note of where the `control` groups are.
    ticks_to_skip_contrast = None
    ticks_to_start_twocol_sankey = None
    if is_paired == "baseline" and show_pairs:
        if two_col_sankey:
            ticks_to_skip = []
            ticks_to_plot = np.arange(0, len(plot_groups) / 2).tolist()
            ticks_to_start_twocol_sankey = np.cumsum([len(i) - 1 for i in idx]).tolist()
            ticks_to_start_twocol_sankey.pop()
            ticks_to_start_twocol_sankey.insert(0, 0)
        else:
            ticks_to_skip = np.cumsum([len(t) for t in idx])[:-1].tolist()
            ticks_to_skip.insert(0, 0)
            # Then obtain the ticks where we have to plot the effect sizes.
            ticks_to_plot = [
                t for t in range(0, len(plot_groups)) if t not in ticks_to_skip
            ]
            ticks_to_skip_contrast = np.cumsum([(len(t)) for t in idx])[:-1].tolist()
            ticks_to_skip_contrast.insert(0, 0)
    else:
        if two_col_sankey:
            ticks_to_skip = [len(sankey_control_group)]
            # Then obtain the ticks where we have to plot the effect sizes.
            ticks_to_plot = [
                t for t in range(0, len(plot_groups)) if t not in ticks_to_skip
            ]
            ticks_to_skip = []
            ticks_to_start_twocol_sankey = np.cumsum([len(i) - 1 for i in idx]).tolist()
            ticks_to_start_twocol_sankey.pop()
            ticks_to_start_twocol_sankey.insert(0, 0)
        else:
            ticks_to_skip = np.cumsum([len(t) for t in idx])[:-1].tolist()
            ticks_to_skip.insert(0, 0)
            # Then obtain the ticks where we have to plot the effect sizes.
            ticks_to_plot = [
                t for t in range(0, len(plot_groups)) if t not in ticks_to_skip
            ]
    
    ticks_for_baseline_ec = ticks_to_skip
    
    return ticks_to_skip, ticks_to_plot, ticks_for_baseline_ec, ticks_to_skip_contrast, ticks_to_start_twocol_sankey

def set_xaxis_ticks_and_lims(
        show_delta2: bool, 
        show_mini_meta: bool, 
        rawdata_axes: axes.Axes, 
        contrast_axes: axes.Axes, 
        show_pairs: bool, 
        float_contrast: bool,
        ticks_to_skip: list, 
        contrast_xtick_labels: list, 
        plot_kwargs: dict, 
        proportional: bool, 
        horizontal: bool
    ):
    """
    Set the x-axis/yaxis ticks and limits for the plotter function.

    Parameters
    ----------
    show_delta2 : bool
        A boolean flag to determine if the plot will have a delta-delta effect size.
    show_mini_meta : bool
        A boolean flag to determine if the plot will have a mini-meta effect size.
    rawdata_axes : object (Axes)
        The raw data axes.
    contrast_axes : object (Axes)
        The contrast axes.
    show_pairs : bool
        A boolean flag to determine if the plot will show the paired data.
    float_contrast : bool
        A boolean flag to determine if the plot is a GA or Cumming design.
    ticks_to_skip : list
        A list of ticks to skip.
    contrast_xtick_labels : list
        A list of contrast xtick labels.
    plot_kwargs : dict
        Kwargs passed to the plot function.
    proportional: bool
        A boolean flag to determine if the plot is a proportional plot.
    horizontal : bool
        A boolean flag to determine if the plot is for horizontal plotting.
    """

    if horizontal:
        # Ticks
        if show_delta2 is False and show_mini_meta is False:
            contrast_axes.set_yticks(rawdata_axes.get_yticks())
        else:
            temp = rawdata_axes.get_yticks()
            temp = np.append(temp, [max(temp) + 0, max(temp) + 1])
            contrast_axes.set_yticks(temp)       

        # Lims
        if show_pairs:
            max_x = contrast_axes.get_ylim()[1]
            rawdata_axes.set_ylim(-0.375, max_x)

            if proportional:
                rawdata_axes.set_ylim(-0.375, max_x+0.1)

        if show_delta2 or show_mini_meta:
            # Increase the ylim of raw data by 2
            temp = rawdata_axes.get_ylim()
            if show_pairs:
                rawdata_axes.set_ylim(temp[0], temp[1] + 0.00)
            else:
                rawdata_axes.set_ylim(temp[0], temp[1] + 1)
            contrast_axes.set_ylim(rawdata_axes.get_ylim())
        else:
            contrast_axes.set_ylim(rawdata_axes.get_ylim())
    # Vertical
    else:
        # Ticks
        if show_delta2 is False and show_mini_meta is False:
            contrast_axes.set_xticks(rawdata_axes.get_xticks())
        else:
            temp = rawdata_axes.get_xticks()
            temp = np.append(temp, [max(temp) + 1])
            contrast_axes.set_xticks(temp)

        # Lims
        if show_pairs:
            max_x = contrast_axes.get_xlim()[1]
            rawdata_axes.set_xlim(-0.375, max_x)

        if float_contrast:
            contrast_axes.set_xlim(0.5, 1.5)

        elif show_delta2:
            if show_pairs:
                rawdata_axes.set_xlim(-0.375, 4.75)
            else:
                rawdata_axes.set_xlim(-0.5, 4.75)
            contrast_axes.set_xlim(rawdata_axes.get_xlim())

        elif show_mini_meta:
            # Increase the xlim of raw data by 2
            temp = rawdata_axes.get_xlim()
            if show_pairs:
                rawdata_axes.set_xlim(temp[0], temp[1] + 0.5)
            else:
                rawdata_axes.set_xlim(temp[0], temp[1] + 1)
            contrast_axes.set_xlim(rawdata_axes.get_xlim())
        else:
            contrast_axes.set_xlim(rawdata_axes.get_xlim())

        # Properly label the contrast ticks.
        for t in ticks_to_skip:
            contrast_xtick_labels.insert(t, "")

        contrast_axes.set_xticklabels(
            contrast_xtick_labels, fontsize=plot_kwargs["fontsize_contrastxlabel"]
        )


def show_legend(
        legend_labels: list, 
        legend_handles: list, 
        rawdata_axes: axes.Axes, 
        contrast_axes: axes.Axes, 
        table_axes: axes.Axes, 
        float_contrast: bool, 
        show_pairs: bool, 
        horizontal: bool, 
        legend_kwargs: dict, 
        table_kwargs: dict
    ):
    """
    Show the legend for the plotter function.

    Parameters
    ----------
    legend_labels : list
        A list of legend labels.
    legend_handles : list
        A list of legend handles.
    rawdata_axes : object (Axes)
        The raw data axes.
    contrast_axes : object (Axes)
        The contrast axes.
    table_axes : object (Axes)
        The table axes.
    float_contrast : bool
        A boolean flag to determine if the plot is GA or Cumming format.
    show_pairs : bool
        A boolean flag to determine if the plot will show the paired data.
    horizontal : bool
        A boolean flag to determine if the plot is for horizontal plotting.
    legend_kwargs : dict
        Kwargs passed to the legend function.
    """

    legend_labels_unique = np.unique(legend_labels)
    unique_idx = np.unique(legend_labels, return_index=True)[1]
    legend_handles_unique = (
        pd.Series(legend_handles, dtype="object").loc[unique_idx]
    ).tolist()

    # Location of the legend
    if "bbox_to_anchor" not in legend_kwargs.keys():
        if horizontal:
            bta = (1,1)
        else:
            if float_contrast:
                bta = (2.00, 1.02) if show_pairs else (1.5, 1.02)
            else:
                bta = (1.02, 1.0) if show_pairs else (1.0, 1.0)
        legend_kwargs.update({'bbox_to_anchor': bta})

    # Pick the ax to plot
    if horizontal:
        if table_kwargs['show']:
            axes_with_legend = table_axes
        else:
            axes_with_legend = contrast_axes
    elif float_contrast:
        axes_with_legend = contrast_axes
    else:
        axes_with_legend = rawdata_axes

    # Plot the legend
    if len(legend_handles_unique) > 0:
        leg = axes_with_legend.legend(
                            legend_handles_unique,
                            legend_labels_unique,
                            handlelength=0.5,
                            **legend_kwargs
        )
        if show_pairs:
            for line in leg.get_lines():
                line.set_linewidth(3.0)
    
def gardner_altman_adjustments(
        effect_size_type: str, 
        plot_data: pd.DataFrame, 
        xvar: str, 
        yvar: str, 
        current_control: str, 
        current_group: str,
        rawdata_axes: axes.Axes, 
        contrast_axes: axes.Axes, 
        results: pd.DataFrame, 
        current_effsize: float, 
        is_paired: bool, 
        one_sankey: bool,
        reflines_kwargs: dict, 
        redraw_axes_kwargs: dict
    ):
    """
    Aesthetic adjustments specific to Gardner-Altman plots (float_contrast=True).
    
    Parameters
    ----------
    effect_size_type : str
        The type of effect size.
    plot_data : object (Dataframe)
        A dataframe of plot data.
    xvar : str
        The name of the x-axis variable.
    yvar : str
        The name of the y-axis variable.
    current_control : str
        The name of the current control group.
    current_group : str
        The name of the current test group.
    rawdata_axes : object (Axes)
        The raw data axes.
    contrast_axes : object (Axes)
        The contrast axes.
    results : object (DataFrame)
        A dataframe of the results.
    current_effsize : float
        The current effect size.
    is_paired : bool
        A boolean flag to determine if the plot is for paired data.
    one_sankey : bool
        A boolean flag to determine if the plot is for a single sankey diagram.
    reflines_kwargs : dict
        Kwargs passed to the reference lines.
    redraw_axes_kwargs : dict
        Kwargs passed to the redraw axes.
    """
    from ._stats_tools.effsize import (
        _compute_standardizers,
        _compute_hedges_correction_factor,
    )

    og_xlim_raw, og_ylim_raw = rawdata_axes.get_xlim(), rawdata_axes.get_ylim()
    
    # Normalize ylims and despine the floating contrast axes.
    # Check that the effect size is within the swarm ylims.
    if effect_size_type in ["mean_diff", "cohens_d", "hedges_g", "cohens_h"]:
        control_group_summary = (
            plot_data.groupby(xvar, observed=False)
            .mean(numeric_only=True)
            .loc[current_control, yvar]
        )
        test_group_summary = (
            plot_data.groupby(xvar, observed=False).mean(numeric_only=True).loc[current_group, yvar]
        )
    elif effect_size_type == "median_diff":
        control_group_summary = (
            plot_data.groupby(xvar, observed=False).median(numeric_only=True).loc[current_control, yvar]
        )
        test_group_summary = (
            plot_data.groupby(xvar, observed=False).median(numeric_only=True).loc[current_group, yvar]
        )

    _, contrast_xlim_max = contrast_axes.get_xlim()

    difference = float(results.difference[0])

    if effect_size_type in ["mean_diff", "median_diff"]:
        # Align 0 of contrast_axes to reference group mean of rawdata_axes.
        # If the effect size is positive, shift the contrast axis up.
        rawdata_ylims = np.array(rawdata_axes.get_ylim())
        if current_effsize > 0:
            rightmin, rightmax = rawdata_ylims - current_effsize
        # If the effect size is negative, shift the contrast axis down.
        elif current_effsize < 0:
            rightmin, rightmax = rawdata_ylims + current_effsize
        else:
            rightmin, rightmax = rawdata_ylims

        contrast_axes.set_ylim(rightmin, rightmax)

        og_ylim_contrast = rawdata_axes.get_ylim() - np.array(control_group_summary)

        contrast_axes.set_ylim(og_ylim_contrast)
        contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max)

    elif effect_size_type in ["cohens_d", "hedges_g", "cohens_h"]:

        which_std = 1 if is_paired else 0   ############################ Unused line of code
        temp_control = np.array(plot_data[plot_data[xvar] == current_control][yvar])
        temp_test = np.array(plot_data[plot_data[xvar] == current_group][yvar])

        stds = _compute_standardizers(temp_control, temp_test)
        if is_paired:
            pooled_sd = stds[1]
        else:
            pooled_sd = stds[0]

        if effect_size_type == "hedges_g":
            gby_count = plot_data.groupby(xvar, observed=False).count()
            len_control = gby_count.loc[current_control, yvar]
            len_test = gby_count.loc[current_group, yvar]

            hg_correction_factor = _compute_hedges_correction_factor(
                len_control, len_test
            )

            ylim_scale_factor = pooled_sd / hg_correction_factor

        elif effect_size_type == "cohens_h":
            ylim_scale_factor = (
                np.mean(temp_test) - np.mean(temp_control)
            ) / difference

        else:
            ylim_scale_factor = pooled_sd

        scaled_ylim = (
            (rawdata_axes.get_ylim() - control_group_summary) / ylim_scale_factor
        ).tolist()

        contrast_axes.set_ylim(scaled_ylim)
        og_ylim_contrast = scaled_ylim

        contrast_axes.set_xlim(contrast_xlim_max - 1, contrast_xlim_max)

    if one_sankey is None:
        # Draw summary lines for control and test groups..
        for jj, axx in enumerate([rawdata_axes, contrast_axes]):
            # Draw effect size line.
            if jj == 0:
                ref = control_group_summary
                diff = test_group_summary
                effsize_line_start = 1

            elif jj == 1:
                ref = 0
                diff = ref + difference
                effsize_line_start = contrast_xlim_max - 1.1

            xlimlow, xlimhigh = axx.get_xlim()

            # Draw reference line.
            axx.hlines(
                ref,  # y-coordinates
                0,
                xlimhigh,  # x-coordinates, start and end.
                **reflines_kwargs
            )

            # Draw effect size line.
            axx.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs)
    else:
        ref = 0
        diff = ref + difference
        effsize_line_start = contrast_xlim_max - 0.9
        xlimlow, xlimhigh = contrast_axes.get_xlim()
        # Draw reference line.
        contrast_axes.hlines(
            ref,  # y-coordinates
            effsize_line_start,
            xlimhigh,  # x-coordinates, start and end.
            **reflines_kwargs
        )

        # Draw effect size line.
        contrast_axes.hlines(diff, effsize_line_start, xlimhigh, **reflines_kwargs)
    rawdata_axes.set_xlim(og_xlim_raw)  # to align the axis
    # Despine appropriately.
    sns.despine(ax=rawdata_axes, bottom=True)
    sns.despine(ax=contrast_axes, left=True, right=False)

    # Insert break between the rawdata axes and the contrast axes
    # by re-drawing the x-spine.
    rawdata_axes.hlines(
        og_ylim_raw[0],  # yindex
        rawdata_axes.get_xlim()[0],
        1.3,  # xmin, xmax
        **redraw_axes_kwargs
    )
    rawdata_axes.set_ylim(og_ylim_raw)

    contrast_axes.hlines(
        contrast_axes.get_ylim()[0],
        contrast_xlim_max - 0.8,
        contrast_xlim_max,
        **redraw_axes_kwargs
    )

def draw_zeroline(
        ax : axes.Axes,
        horizontal : bool,
        reflines_kwargs : dict,
        extra_delta : bool,
    ):
    """
    Draw the independent axis spine lines.

    Parameters
    ----------
    ax : object (Axes)
        The contrast data axes.
    horizontal : bool
        A boolean flag to determine if the plot is for horizontal plotting.
    reflines_kwargs : dict
        Additional keyword arguments to be passed to the zeroline.
    extra_delta : bool
        A boolean flag to determine if the plot includes an extra delta (delta-delta or mini-meta).
    """
    # If 0 lies within the ylim of the contrast axes, draw a zero reference line.
    if extra_delta and not horizontal:
        contrast_xlim = [-0.5, 3.4]
        delta2_xlim = [3.6, 4.75]
        
        if ax.get_ylim()[0] < ax.get_ylim()[1]:
            contrast_lim_low, contrast_lim_high = ax.get_ylim()
        else:
            contrast_lim_high, contrast_lim_low = ax.get_ylim()

        if contrast_lim_low < 0 < contrast_lim_high:
            ax.hlines(y=0, xmin=contrast_xlim[0], xmax=contrast_xlim[1], **reflines_kwargs)
            ax.hlines(y=0, xmin=delta2_xlim[0], xmax=delta2_xlim[1], **reflines_kwargs)
    else:
        ax_lim = ax.get_xlim() if horizontal else ax.get_ylim()
        method = ax.axvline if horizontal else ax.axhline

        if ax_lim[0] < ax_lim[1]:
            contrast_lim_low, contrast_lim_high = ax_lim
        else:
            contrast_lim_high, contrast_lim_low = ax_lim

        if contrast_lim_low < 0 < contrast_lim_high:
            method(0, **reflines_kwargs)

def redraw_independent_spines(
        rawdata_axes : axes.Axes,
        contrast_axes : axes.Axes,
        horizontal : bool,
        two_col_sankey : bool,
        ticks_to_start_twocol_sankey : list,
        idx : list,
        is_paired : str,
        show_pairs : bool,
        proportional : bool,
        ticks_to_skip : list,
        temp_idx : list,
        ticks_to_skip_contrast : list,
        redraw_axes_kwargs : dict
    ):
    """
    Draw the independent axis spine lines.

    Parameters
    ----------
    rawdata_axes : object (Axes)
        The raw data axes.
    contrast_axes : object (Axes)
        The contrast axes.
    horizontal : bool
        A boolean flag to determine if the plot is for horizontal plotting.
    two_col_sankey : bool
        A boolean flag to determine if the plot is for two-col sankey.
    ticks_to_start_twocol_sankey : list
        A list of ticks to start for sankey plot.
    idx : list
        A list of indices.
    is_paired : bool
        A boolean flag to determine if the data is paired.
    show_pairs : bool
        A boolean flag to determine if pairs should be shown.
    proportional : bool
        A boolean flag to determine if the plot is proportional/binary.
    ticks_to_skip : list,
        A list of ticks to be skipped in the raw data axes.
    temp_idx : list,
        A temporary list of indices to be used for skipping ticks in the raw data axes.
    ticks_to_skip_contrast : list,
        A list of ticks to be skipped in the contrast axes.
    redraw_axes_kwargs : dict
        Kwargs passed to the redraw axes.
    """
    # Extract the ticks
    if two_col_sankey:
        rightend_ticks_raw = rightend_ticks_contrast = np.array([len(i) - 2 for i in idx]) + np.array(ticks_to_start_twocol_sankey)
        starting_ticks_raw = starting_ticks_contrast = ticks_to_start_twocol_sankey
    else:
        if is_paired == "baseline" and show_pairs:
            if proportional and is_paired is not None:
                rightend_ticks_raw = rightend_ticks_contrast = np.array([len(i) - 1 for i in idx]) + np.array(ticks_to_skip)
            else:
                rightend_ticks_raw = np.array([len(i) - 1 for i in temp_idx]) + np.array(ticks_to_skip)
                temp_length = [(len(i) - 1) * 2 - 1 for i in idx] if proportional else [(len(i) - 1) for i in idx]
                rightend_ticks_contrast = np.array(temp_length) + np.array(ticks_to_skip_contrast)
            starting_ticks_raw, starting_ticks_contrast = ticks_to_skip, ticks_to_skip_contrast
        else:
            rightend_ticks_raw = rightend_ticks_contrast = np.array([len(i) - 1 for i in idx]) + np.array(ticks_to_skip)
            starting_ticks_raw = starting_ticks_contrast = ticks_to_skip

    # Plot the spines
    if horizontal:
        sns.despine(ax=rawdata_axes, left=True)
        xlim, ylim = rawdata_axes.get_xlim(), rawdata_axes.get_ylim()
        redraw_axes_kwargs["x"] = xlim[0]
        for k, start_tick in enumerate(starting_ticks_raw):
            end_tick = rightend_ticks_raw[k]
            rawdata_axes.vlines(
                    ymin = start_tick, 
                    ymax = end_tick, 
                    **redraw_axes_kwargs
            )
        rawdata_axes.set_xlim(xlim)
        rawdata_axes.set_ylim(ylim)
        del redraw_axes_kwargs["x"] 

        # Remove y ticks and labels from the contrast axes.
        sns.despine(ax=contrast_axes, left=True)
        contrast_axes.set_yticks([])
        contrast_axes.set_yticklabels([])
    
    else:
        for ax, starting_ticks_current, rightend_ticks_current in zip(
                [rawdata_axes, contrast_axes],
                [starting_ticks_raw, starting_ticks_contrast],
                [rightend_ticks_raw, rightend_ticks_contrast],
            ):
            sns.despine(ax=ax, bottom=True)
            xlim, ylim = ax.get_xlim(), ax.get_ylim()
            redraw_axes_kwargs["y"] = ylim[0]
            for k, start_tick in enumerate(starting_ticks_current):
                end_tick = rightend_ticks_current[k]
                ax.hlines(
                    xmin=start_tick, 
                    xmax=end_tick, 
                    **redraw_axes_kwargs
                    )
            ax.set_xlim(xlim)
            ax.set_ylim(ylim)
            del redraw_axes_kwargs["y"]
            
def redraw_dependent_spines(
        rawdata_axes: axes.Axes, 
        contrast_axes: axes.Axes, 
        redraw_axes_kwargs: dict, 
        float_contrast: bool, 
        horizontal: bool,
        show_delta2: bool, 
        delta2_axes: axes.Axes
    ):
    """
    Draw the dependent axis spine lines.

    Parameters
    ----------
    rawdata_axes : object (Axes)
        The raw data axes.
    contrast_axes : object (Axes)
        The contrast axes.
    redraw_axes_kwargs : dict
        Kwargs passed to the redraw axes.
    float_contrast : bool
        A boolean flag to determine if the plot is GA or Cum
    horizontal : bool
        A boolean flag to determine if the plot is for horizontal plotting.
    show_delta2 : bool
        A boolean flag to determine if the plot will have a delta-delta effect size.
    delta2_axes : object (Axes)
        The delta2 axes.
    """

    # Because we turned the axes frame off, we also need to draw back the x-spine for both axes.
    og_xlim_raw, og_ylim_raw = rawdata_axes.get_xlim(), rawdata_axes.get_ylim()
    og_xlim_contrast, og_ylim_contrast = contrast_axes.get_xlim(), contrast_axes.get_ylim()
    if horizontal:
        for current_ax, current_ylim, current_xlim in zip((rawdata_axes, contrast_axes), (og_ylim_raw, og_ylim_contrast), 
                                                          (og_xlim_raw, og_xlim_contrast)):
            current_ax.hlines(
                current_ylim[0], 
                current_xlim[0], 
                current_xlim[1], 
                **redraw_axes_kwargs
            )    
    else:
        for current_ax, current_ylim, current_xlim in zip((rawdata_axes, contrast_axes), (og_ylim_raw, og_ylim_contrast), 
                                                          (og_xlim_raw[0], og_xlim_contrast[1] if float_contrast else og_xlim_contrast[0])):
            current_ax.vlines(
                current_xlim, 
                current_ylim[0], 
                current_ylim[1], 
                **redraw_axes_kwargs
            )

        if show_delta2:
            og_xlim_delta, og_ylim_delta = contrast_axes.get_xlim(), contrast_axes.get_ylim()
            delta2_axes.set_ylim(og_ylim_delta)
            
            delta2_axes.vlines(
                og_xlim_delta[1], 
                og_ylim_delta[0], 
                og_ylim_delta[1], 
                **redraw_axes_kwargs
            )

    for current_ax, xlim, ylim in zip([rawdata_axes, contrast_axes], [og_xlim_raw, og_xlim_contrast], [og_ylim_raw, og_ylim_contrast]):
        current_ax.set_xlim(xlim)
        current_ax.set_ylim(ylim)

def extract_group_summaries(
        proportional: bool, 
        rawdata_axes: axes.Axes, 
        asymmetric_side: str, 
        horizontal: bool, 
        bootstraps_color_by_group: bool, 
        plot_palette_raw: list, 
        all_plot_groups: list,
        n_groups: int, 
        color_col, 
        ytick_color, 
        group_summaries_kwargs: dict
    ):
    """
    Extract the group summaries for the plotter function.

    Parameters
    ----------
    proportional : bool
        A boolean flag to determine if the plot is for proportional data.
    rawdata_axes : object (Axes)
        The raw data axes.
    asymmetric_side : str
        The side of the asymmetric error bars.
    horizontal : bool
        A boolean flag to determine if the plot is for horizontal plotting.
    bootstraps_color_by_group : bool
        A boolean flag to determine if the bootstraps are colored by group.
    plot_palette_raw : list
        A list of the plot palette colors.
    all_plot_groups : list
        A list of all the plot groups.
    n_groups : int
        The number of groups.
    color_col : str
        The name of the color column.
    ytick_color : str
        The color of the y-ticks.
    group_summaries_kwargs : dict
        Kwargs passed to the group summaries.
    """
    
    from .plot_tools import get_swarm_spans

    if proportional:
        group_summaries_method = "proportional_error_bar"
        group_summaries_offset = 0
        group_summaries_line_color = "black"
    else:
        # Create list to gather xspans.
        xspans = []
        line_colors = []
        for jj, c in enumerate(rawdata_axes.collections):
            try:
                if asymmetric_side == "right":
                    # currently offset is hardcoded with value of -0.2
                    x_max_span = -0.2
                else:
                    if horizontal:
                        x_max_span = 0.1 # currently offset is hardcoded with value of 0.1
                    else:
                        _, x_max, _, _ = get_swarm_spans(c)
                        x_max_span = x_max - jj
                xspans.append(x_max_span)
            except TypeError:
                # we have got a None, so skip and move on.
                pass

            if bootstraps_color_by_group:
                line_colors.append(plot_palette_raw[all_plot_groups[jj]])

            # Break the loop since hue in Seaborn adds collections to axes and it will result in index out of range
            if jj >= n_groups - 1 and color_col is None:
                break

        if len(line_colors) != len(all_plot_groups):
            line_colors = ytick_color
            
        # hue in swarmplot would add collections to axes which will result in len(xspans) = len(all_plot_groups) + len(unique groups in hue)
        if len(xspans) > len(all_plot_groups):
            xspans = xspans[:len(all_plot_groups)]

        group_summaries_method = "gapped_lines"
        group_summaries_offset = xspans + np.array(group_summaries_kwargs["offset"])
        group_summaries_line_color = line_colors

    if group_summaries_kwargs['color'] is not None:
        group_summaries_line_color = group_summaries_kwargs.pop("color")
    group_summaries_kwargs.pop("offset")

    return group_summaries_method, group_summaries_offset, group_summaries_line_color

def color_picker(color_type: str,
                 kwargs: dict, 
                 elements: list, 
                 color_col: str, 
                 show_pairs: bool, 
                 color_palette: dict,
                 bootstraps_color_by_group: bool) -> list:
    num_of_elements = len(elements)
    colors = (
        [kwargs.pop('color')] * num_of_elements
        if kwargs.get('color', None) is not None
        else ['black'] * num_of_elements
        # if color_col is not None or show_pairs
        if color_col is not None or not bootstraps_color_by_group
        else list(color_palette.values())
    )
    if color_type in ['contrast', 'summary', 'delta_text']:
        if len(colors) == num_of_elements:
            final_colors = colors
        else:
            final_colors = []
            for tick in elements:
                final_colors.append(colors[int(tick)])
    else:
        final_colors = colors
    return final_colors


def prepare_bars_for_plot(bar_type, bar_kwargs, horizontal, plot_palette_raw, color_col, show_pairs, bootstraps_color_by_group,
                          plot_data = None, xvar = None, yvar = None,  # Raw data
                          results = None, ticks_to_plot = None, extra_delta = None, # Contrast data
                          reference_band = None, summary_axes = None, ci_type = None  # Summary data
                          ):
    from .misc_tools import color_picker
    bar_dict = {}
    if bar_type in ['raw', 'contrast']:
        if bar_type == 'raw':
            if isinstance(plot_data[xvar].dtype, pd.CategoricalDtype):
                order = pd.unique(plot_data[xvar]).categories
            else:
                order = pd.unique(plot_data[xvar])
            means = plot_data.groupby(xvar, observed=False)[yvar].mean().reindex(index=order).values
            ticks = list(range(len(order)))
        elif bar_type == 'contrast':
            means = results.difference.to_list()
            ticks = ticks_to_plot.copy()
            if extra_delta is not None:
                ticks.append(ticks[-1]+1)  # Add an extra tick
                means.append(extra_delta)

        num_of_bars = len(means)
        y_start_values, y_distances = [0]*num_of_bars, means
        x_start_values, x_distances = [num - (0.5 if horizontal else 0.25) for num in ticks], [0.5,]*num_of_bars

    elif bar_type == 'summary':
        # Begin checks        
        if not isinstance(reference_band, list):
            raise TypeError("reference_band must be a list of indices (ints).")
        if not all(isinstance(i, int) for i in reference_band):
            raise TypeError("reference_band must be a list of indices (ints).")
        if any(i >= len(results) for i in reference_band):
            raise ValueError("Index {} chosen is out of range for the contrast objects.".format([i for i in reference_band if i >= len(results)]))

        ticks = [ticks_to_plot[tick] for tick in reference_band]
        summary_xmin, summary_xmax = summary_axes.get_xlim()
        summary_ymin, summary_ymax = summary_axes.get_ylim()
        span_ax = bar_kwargs.pop("span_ax")

        x_start_values, y_start_values, x_distances, y_distances = [], [], [], []
        for summary_index in reference_band:
            summary_ci_low = results.get(ci_type+'_low')[summary_index]
            summary_ci_high = results.get(ci_type+'_high')[summary_index]   

            if span_ax == True:
                starting_location = summary_ymax if horizontal else summary_xmin
            else:
                starting_location = ticks_to_plot[summary_index]  
            x_distance = summary_ymin if horizontal else summary_xmax 

            x_start_values.append(starting_location)
            y_start_values.append(summary_ci_low)
            x_distances.append(x_distance + 1)
            y_distances.append(summary_ci_high - summary_ci_low)
    else:
        raise ValueError("Invalid bar_type. Must be 'raw' or 'contrast'.")
    
    if horizontal:
        x_start_values, y_start_values = y_start_values, x_start_values
        x_distances, y_distances = y_distances, x_distances

    for name, values in zip(['x_start_values', 'x_distances', 'y_start_values', 'y_distance'],
                            [x_start_values, x_distances, y_start_values, y_distances]
                        ):
        bar_dict[name] = values

    # Colors
    colors = color_picker(
                color_type = bar_type,
                kwargs = bar_kwargs, 
                elements = ticks_to_plot if bar_type=='contrast' else ticks, 
                color_col = color_col, 
                show_pairs = show_pairs, 
                color_palette = plot_palette_raw,
                bootstraps_color_by_group = bootstraps_color_by_group
            )
    if bar_type == 'contrast' and extra_delta is not None:
        colors.append('black')
    bar_dict['colors'] = colors

    return bar_dict, bar_kwargs
