import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.cm as cmc
import seaborn as sns
from matplotlib_venn import venn3, venn2
from random import sample
from functools import reduce
import sys
from matplotlib.colors import ListedColormap
from adjustText import adjust_text
from matplotlib import rc


def volcano(d="dataframe", lfc=None, pv=None, lfc_thr=1, pv_thr=0.05, color=("green", "red"), valpha=1,
            geneid=None, genenames=None, gfont=8, dim=(6,4), r=300, ar=90, dotsize=8, markerdot="o", sign_line=False):
    general.depr_mes("bioinfokit.visuz.gene_exp.volcano")


def involcano(table="dataset_file", lfc="logFC", pv="p_values", lfc_thr=1, pv_thr=0.05, color=("green", "red"), valpha=1,
              geneid=None, genenames=None, gfont=8):
    general.depr_mes("bioinfokit.visuz.gene_exp.involcano")


def ma(table="dataset_file", lfc="logFC", ct_count="value1", st_count="value2", lfc_thr=1):
    general.depr_mes("bioinfokit.visuz.gene_exp.ma")


def corr_mat(table="p_df", corm="pearson"):
    general.depr_mes("bioinfokit.visuz.stat.corr_mat")


def screeplot(obj="pcascree"):
    general.depr_mes("bioinfokit.visuz.cluster.screeplot")


def pcaplot(x="x", y="y", z="z", labels="d_cols", var1="var1", var2="var2", var3="var3"):
    for i, varnames in enumerate(labels):
        plt.scatter(x[i], y[i])
        plt.text(x[i], y[i], varnames, fontsize=10)
    plt.xlabel("PC1 ({}%)".format(var1), fontsize=12, fontname="sans-serif")
    plt.ylabel("PC2 ({}%)".format(var2), fontsize=12, fontname="sans-serif")
    plt.tight_layout()
    plt.savefig('pcaplot_2d.png', format='png', bbox_inches='tight', dpi=300)
    plt.close()

    # for 3d plot
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    for i, varnames in enumerate(labels):
        ax.scatter(x[i], y[i], z[i])
        ax.text(x[i], y[i], z[i], varnames, fontsize=10)
    ax.set_xlabel("PC1 ({}%)".format(var1), fontsize=12, fontname="sans-serif")
    ax.set_ylabel("PC2 ({}%)".format(var2), fontsize=12, fontname="sans-serif")
    ax.set_zlabel("PC3 ({}%)".format(var3), fontsize=12, fontname="sans-serif")
    plt.tight_layout()
    plt.savefig('pcaplot_3d.png', format='png', bbox_inches='tight',  dpi=300)
    plt.close()


def hmap(table="dataset_file", cmap="seismic", scale=True, dim=(4,6), clus=True, zscore=None, xlabel=True, ylabel=True,
         tickfont=(10,10)):
    general.depr_mes("bioinfokit.visuz.gene_exp.hmap")


def venn(vennset=(1,1,1,1,1,1,1), venncolor=('#00909e', '#f67280', '#ff971d'), vennalpha=0.5,
         vennlabel=('A', 'B', 'C')):
    fig = plt.figure()
    if len(vennset) == 7:
        venn3(subsets=vennset, set_labels=vennlabel, set_colors=venncolor, alpha=vennalpha)
        plt.savefig('venn3.png', format='png', bbox_inches='tight', dpi=300)
    elif len(vennset) == 3:
        venn2(subsets=vennset, set_labels=vennlabel, set_colors=venncolor, alpha=vennalpha)
        plt.savefig('venn2.png', format='png', bbox_inches='tight', dpi=300)
    else:
        print("Error: check the set dataset")


class gene_exp:

    def __init__(self):
        pass

    def geneplot(d, geneid, lfc, lfc_thr, pv_thr, genenames, gfont, pv, gstyle):
        if genenames is not None and genenames == "deg":
            for i in d[geneid].unique():
                if (d.loc[d[geneid] == i, lfc].iloc[0] >= lfc_thr and d.loc[d[geneid] == i, pv].iloc[0] < pv_thr) or \
                        (d.loc[d[geneid] == i, lfc].iloc[0] <= -lfc_thr and d.loc[d[geneid] == i, pv].iloc[0] < pv_thr):
                    if gstyle==1:
                        plt.text(d.loc[d[geneid] == i, lfc].iloc[0], d.loc[d[geneid] == i, 'logpv_add_axy'].iloc[0], i,
                                      fontsize=gfont)
                    elif gstyle==2:
                        plt.annotate(i, xy=(d.loc[d[geneid] == i, lfc].iloc[0], d.loc[d[geneid] == i, 'logpv_add_axy'].iloc[0]),
                                     xycoords='data', xytext=(5, -15), textcoords='offset points', size=6,
                                     bbox=dict(boxstyle="round", alpha=0.1),
                                     arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.1, relpos=(0, 0)))
                    else:
                        print("Error: invalid gstyle choice")
                        sys.exit(1)
        elif genenames is not None and type(genenames) is tuple:
            for i in d[geneid].unique():
                if i in genenames:
                    if gstyle==1:
                        plt.text(d.loc[d[geneid] == i, lfc].iloc[0], d.loc[d[geneid] == i, 'logpv_add_axy'].iloc[0], i,
                                      fontsize=gfont)
                    elif gstyle==2:
                        plt.annotate(i, xy=(d.loc[d[geneid] == i, lfc].iloc[0], d.loc[d[geneid] == i, 'logpv_add_axy'].iloc[0]),
                                     xycoords='data', xytext=(5, -15), textcoords='offset points', size=6,
                                     bbox=dict(boxstyle="round", alpha=0.1),
                                     arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.1, relpos=(0, 0)))
                    else:
                        print("Error: invalid gstyle choice")
                        sys.exit(1)
        elif genenames is not None and type(genenames) is dict:
            for i in d[geneid].unique():
                if i in genenames:
                    if gstyle==1:
                        plt.text(d.loc[d[geneid] == i, lfc].iloc[0], d.loc[d[geneid] == i, 'logpv_add_axy'].iloc[0],
                                      genenames[i], fontsize=gfont)
                    elif gstyle == 2:
                        plt.annotate(genenames[i], xy=(d.loc[d[geneid] == i, lfc].iloc[0], d.loc[d[geneid] == i, 'logpv_add_axy'].iloc[0]),
                                     xycoords='data', xytext=(5, -15), textcoords='offset points', size=6,
                                     bbox=dict(boxstyle="round", alpha=0.1),
                                     arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.1, relpos=(0, 0)))
                    else:
                        print("Error: invalid gstyle choice")
                        sys.exit(1)

    def volcano(df="dataframe", lfc=None, pv=None, lfc_thr=1, pv_thr=0.05, color=("green", "grey", "red"), valpha=1,
                geneid=None, genenames=None, gfont=8, dim=(5, 5), r=300, ar=90, dotsize=8, markerdot="o",
                sign_line=False, gstyle=1, show=False, figtype='png', axtickfontsize=9,
                axtickfontname="Arial", axlabelfontsize=9, axlabelfontname="Arial", axxlabel=None,
                axylabel=None, xlm=None, ylm=None, plotlegend=False, legendpos='best',
                figname='volcano', legendanchor=None,
                legendlabels=['significant up', 'not significant', 'significant down']):
        _x = r'$ log_{2}(Fold Change)$'
        _y = r'$ -log_{10}(P-value)$'
        color = color
        # check if dataframe contains any non-numeric character
        assert general.check_for_nonnumeric(df[lfc]) == 0, 'dataframe contains non-numeric values in lfc column'
        assert general.check_for_nonnumeric(df[pv]) == 0, 'dataframe contains non-numeric values in pv column'
        # this is important to check if color or logpv exists and drop them as if you run multiple times same command
        # it may update old instance of df
        df = df.drop(['color_add_axy', 'logpv_add_axy'], axis=1, errors='ignore')
        assert len(set(color)) == 3, 'unique color must be size of 3'
        df.loc[(df[lfc] >= lfc_thr) & (df[pv] < pv_thr), 'color_add_axy'] = color[0]  # upregulated
        df.loc[(df[lfc] <= -lfc_thr) & (df[pv] < pv_thr), 'color_add_axy'] = color[2]  # downregulated
        df['color_add_axy'].fillna(color[1], inplace=True)  # intermediate
        df['logpv_add_axy'] = -(np.log10(df[pv]))
        # plot
        assign_values = {col: i for i, col in enumerate(color)}
        color_result_num = [assign_values[i] for i in df['color_add_axy']]
        assert len(set(color_result_num)) == 3, \
            'either significant or non-significant genes are missing; try to change lfc_thr or pv_thr to include ' \
            'both significant and non-significant genes'
        plt.subplots(figsize=dim)
        if plotlegend:
            s = plt.scatter(df[lfc], df['logpv_add_axy'], c=color_result_num, cmap=ListedColormap(color), alpha=valpha,
                            s=dotsize, marker=markerdot)
            assert len(legendlabels) == 3, 'legendlabels must be size of 3'
            plt.legend(handles=s.legend_elements()[0], labels=legendlabels, loc=legendpos, bbox_to_anchor=legendanchor)
        else:
            plt.scatter(df[lfc], df['logpv_add_axy'], c=color_result_num, cmap=ListedColormap(color), alpha=valpha,
                        s=dotsize, marker=markerdot)
        if sign_line:
            plt.axhline(y=-np.log10(pv_thr), linestyle='--', color='#7d7d7d', linewidth=1)
            plt.axvline(x=lfc_thr, linestyle='--', color='#7d7d7d', linewidth=1)
            plt.axvline(x=-lfc_thr, linestyle='--', color='#7d7d7d', linewidth=1)
        gene_exp.geneplot(df, geneid, lfc, lfc_thr, pv_thr, genenames, gfont, pv, gstyle)

        if axxlabel:
            _x = axxlabel
        if axylabel:
            _y = axylabel
        general.axis_labels(_x, _y, axlabelfontsize, axlabelfontname)
        general.axis_ticks(xlm, ylm, axtickfontsize, axtickfontname, ar)
        general.get_figure(show, r, figtype, figname)

    def involcano(df="dataframe", lfc="logFC", pv="p_values", lfc_thr=1, pv_thr=0.05, color=("green", "grey", "red"),
                  valpha=1, geneid=None, genenames=None, gfont=8, dim=(5, 5), r=300, ar=90, dotsize=8, markerdot="o",
                sign_line=False, gstyle=1, show=False, figtype='png', axtickfontsize=9,
               axtickfontname="Arial", axlabelfontsize=9, axlabelfontname="Arial", axxlabel=None,
                axylabel=None, xlm=None, ylm=None, plotlegend=False, legendpos='best',
                figname='involcano', legendanchor=None, legendlabels=['significant up', 'not significant', 'significant down']):
        _x = r'$ log_{2}(Fold Change)$'
        _y = r'$ -log_{10}(P-value)$'
        color = color
        assert general.check_for_nonnumeric(df[lfc]) == 0, 'dataframe contains non-numeric values in lfc column'
        assert general.check_for_nonnumeric(df[pv]) == 0, 'dataframe contains non-numeric values in pv column'
        # this is important to check if color or logpv exists and drop them as if you run multiple times same command
        # it may update old instance of df
        df = df.drop(['color_add_axy', 'logpv_add_axy'], axis=1, errors='ignore')
        assert len(set(color)) == 3, 'unique color must be size of 3'
        df.loc[(df[lfc] >= lfc_thr) & (df[pv] < pv_thr), 'color_add_axy'] = color[0]  # upregulated
        df.loc[(df[lfc] <= -lfc_thr) & (df[pv] < pv_thr), 'color_add_axy'] = color[2]  # downregulated
        df['color_add_axy'].fillna(color[1], inplace=True)  # intermediate
        df['logpv_add_axy'] = -(np.log10(df[pv]))

        # plot
        assign_values = {col: i for i, col in enumerate(color)}
        color_result_num = [assign_values[i] for i in df['color_add_axy']]
        assert len(set(color_result_num)) == 3, 'either significant or non-significant genes are missing; try to change lfc_thr or ' \
                                           'pv_thr to include  both significant and non-significant genes'
        plt.subplots(figsize=dim)
        if plotlegend:
            s = plt.scatter(df[lfc], df['logpv_add_axy'], c=color_result_num, cmap=ListedColormap(color), alpha=valpha,
                    s=dotsize, marker=markerdot)
            assert len(legendlabels) == 3, 'legendlabels must be size of 3'
            plt.legend(handles=s.legend_elements()[0], labels=legendlabels, loc=legendpos,
                       bbox_to_anchor=legendanchor)
        else:
            plt.scatter(df[lfc], df['logpv_add_axy'], c=color_result_num, cmap=ListedColormap(color), alpha=valpha,
                        s=dotsize, marker=markerdot)
        gene_exp.geneplot(df, geneid, lfc, lfc_thr, pv_thr, genenames, gfont, pv, gstyle)
        plt.gca().invert_yaxis()
        if axxlabel:
            _x = axxlabel
        if axylabel:
            _y = axylabel
        general.axis_labels(_x, _y, axlabelfontsize, axlabelfontname)
        if xlm:
            print('Error: xlm not compatible with involcano')
            sys.exit(1)
        if ylm:
            print('Error: ylm not compatible with involcano')
            sys.exit(1)
        general.axis_ticks(xlm, ylm, axtickfontsize, axtickfontname, ar)
        general.get_figure(show, r, figtype, figname)

    def ma(df="dataframe", lfc="logFC", ct_count="value1", st_count="value2", lfc_thr=1, valpha=1, dotsize=8,
           markerdot="o", dim=(6, 5), r=300, show=False, color=("green", "grey", "red"), ar=90, figtype='png', axtickfontsize=9,
           axtickfontname="Arial", axlabelfontsize=9, axlabelfontname="Arial", axxlabel=None,
           axylabel=None, xlm=None, ylm=None, fclines=False, fclinescolor='#2660a4', legendpos='best',
           figname='ma', legendanchor=None, legendlabels=['significant up', 'not significant', 'significant down'],
           plotlegend=False):
        _x, _y = 'A', 'M'
        assert general.check_for_nonnumeric(df[lfc]) == 0, 'dataframe contains non-numeric values in lfc column'
        assert general.check_for_nonnumeric(df[ct_count]) == 0, \
            'dataframe contains non-numeric values in ct_count column'
        assert general.check_for_nonnumeric(
            df[st_count]) == 0, 'dataframe contains non-numeric values in ct_count column'
        # this is important to check if color or A exists and drop them as if you run multiple times same command
        # it may update old instance of df
        df = df.drop(['color_add_axy', 'A_add_axy'], axis=1, errors='ignore')
        assert len(set(color)) == 3, 'unique color must be size of 3'
        df.loc[(df[lfc] >= lfc_thr), 'color_add_axy'] = color[0]  # upregulated
        df.loc[(df[lfc] <= -lfc_thr), 'color_add_axy'] = color[2]  # downregulated
        df['color_add_axy'].fillna(color[1], inplace=True)  # intermediate
        df['A_add_axy'] = (np.log2(df[ct_count]) + np.log2(df[st_count])) / 2
        # plot
        assign_values = {col: i for i, col in enumerate(color)}
        color_result_num = [assign_values[i] for i in df['color_add_axy']]
        assert len(
            set(color_result_num)) == 3, 'either significant or non-significant genes are missing; try to change lfc_thr' \
                                         ' to include both significant and non-significant genes'
        plt.subplots(figsize=dim)
        # plt.scatter(df['A'], df[lfc], c=df['color'], alpha=valpha, s=dotsize, marker=markerdot)
        if plotlegend:
            s = plt.scatter(df['A_add_axy'], df[lfc], c=color_result_num, cmap=ListedColormap(color),
                        alpha=valpha, s=dotsize, marker=markerdot)
            assert len(legendlabels) == 3, 'legendlabels must be size of 3'
            plt.legend(handles=s.legend_elements()[0], labels=legendlabels, loc=legendpos,
                           bbox_to_anchor=legendanchor)
        else:
            plt.scatter(df['A_add_axy'], df[lfc], c=color_result_num, cmap=ListedColormap(color),
                        alpha=valpha, s=dotsize, marker=markerdot)
        # draw a central line at M=0
        plt.axhline(y=0, color='#7d7d7d', linestyle='--')
        # draw lfc threshold lines
        if fclines:
            plt.axhline(y=lfc_thr, color=fclinescolor, linestyle='--')
            plt.axhline(y=-lfc_thr, color=fclinescolor, linestyle='--')
        if axxlabel:
            _x = axxlabel
        if axylabel:
            _y = axylabel
        general.axis_labels(_x, _y, axlabelfontsize, axlabelfontname)
        general.axis_ticks(xlm, ylm, axtickfontsize, axtickfontname, ar)
        general.get_figure(show, r, figtype, figname)

    def hmap(df="dataframe", cmap="seismic", scale=True, dim=(4, 6), rowclus=True, colclus=True, zscore=None, xlabel=True,
             ylabel=True, tickfont=(10, 10), r=300, show=False, figtype='png', figname='heatmap'):
        # df = df.set_index(d.columns[0])
        # plot heatmap without cluster
        # more cmap: https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html
        # dim = dim
        fig, hm = plt.subplots(figsize=dim)
        if rowclus and colclus:
            hm = sns.clustermap(df, cmap=cmap, cbar=scale, z_score=zscore, xticklabels=xlabel, yticklabels=ylabel,
                                figsize=dim)
            hm.ax_heatmap.set_xticklabels(hm.ax_heatmap.get_xmajorticklabels(), fontsize=tickfont[0])
            hm.ax_heatmap.set_yticklabels(hm.ax_heatmap.get_ymajorticklabels(), fontsize=tickfont[1])
            general.get_figure(show, r, figtype, figname)
        elif rowclus and colclus is False:
            hm = sns.clustermap(df, cmap=cmap, cbar=scale, z_score=zscore, xticklabels=xlabel, yticklabels=ylabel,
                                figsize=dim, row_cluster=True, col_cluster=False)
            hm.ax_heatmap.set_xticklabels(hm.ax_heatmap.get_xmajorticklabels(), fontsize=tickfont[0])
            hm.ax_heatmap.set_yticklabels(hm.ax_heatmap.get_ymajorticklabels(), fontsize=tickfont[1])
            general.get_figure(show, r, figtype, figname)
        elif colclus and rowclus is False:
            hm = sns.clustermap(df, cmap=cmap, cbar=scale, z_score=zscore, xticklabels=xlabel, yticklabels=ylabel,
                                figsize=dim, row_cluster=False, col_cluster=True)
            hm.ax_heatmap.set_xticklabels(hm.ax_heatmap.get_xmajorticklabels(), fontsize=tickfont[0])
            hm.ax_heatmap.set_yticklabels(hm.ax_heatmap.get_ymajorticklabels(), fontsize=tickfont[1])
            general.get_figure(show, r, figtype, figname)
        else:
            hm = sns.heatmap(df, cmap=cmap, cbar=scale, xticklabels=xlabel, yticklabels=ylabel)
            plt.xticks(fontsize=tickfont[0])
            plt.yticks(fontsize=tickfont[1])
            general.get_figure(show, r, figtype, figname)


class general:
    def __init__(self):
        pass

    rand_colors = ('#a7414a', '#282726', '#6a8a82', '#a37c27', '#563838', '#0584f2', '#f28a30', '#f05837',
                   '#6465a5', '#00743f', '#be9063', '#de8cf0', '#888c46', '#c0334d', '#270101', '#8d2f23',
                   '#ee6c81', '#65734b', '#14325c', '#704307', '#b5b3be', '#f67280', '#ffd082', '#ffd800',
                   '#ad62aa', '#21bf73', '#a0855b', '#5edfff', '#08ffc8', '#ca3e47', '#c9753d', '#6c5ce7')

    @staticmethod
    def get_figure(show, r, figtype, fig_name):
        if show:
            plt.show()
        else:
            plt.savefig(fig_name+'.'+figtype, format=figtype, bbox_inches='tight', dpi=r)
        plt.clf()
        plt.close()


    @staticmethod
    def axis_labels(x, y, axlabelfontsize=None, axlabelfontname=None):
        plt.xlabel(x, fontsize=axlabelfontsize, fontname=axlabelfontname)
        plt.ylabel(y, fontsize=axlabelfontsize, fontname=axlabelfontname)
        # plt.xticks(fontsize=9, fontname="sans-serif")
        # plt.yticks(fontsize=9, fontname="sans-serif")

    @staticmethod
    def axis_ticks(xlm=None, ylm=None, axtickfontsize=None, axtickfontname=None, ar=None):
        if xlm:
            plt.xlim(left=xlm[0], right=xlm[1])
            plt.xticks(np.arange(xlm[0], xlm[1], xlm[2]),  fontsize=axtickfontsize, rotation=ar, fontname=axtickfontname)
        else:
            plt.xticks(fontsize=axtickfontsize, rotation=ar, fontname=axtickfontname)

        if ylm:
            plt.ylim(bottom=ylm[0], top=ylm[1])
            plt.yticks(np.arange(ylm[0], ylm[1], ylm[2]),  fontsize=axtickfontsize, rotation=ar, fontname=axtickfontname)
        else:
            plt.yticks(fontsize=axtickfontsize, rotation=ar, fontname=axtickfontname)

    @staticmethod
    def depr_mes(func_name):
        print("This function is deprecated. Please use", func_name )
        print("Read docs at https://reneshbedre.github.io/blog/howtoinstall.html")

    @staticmethod
    def check_for_nonnumeric(pd_series=None):
        if pd.to_numeric(pd_series, errors='coerce').isna().sum() == 0:
            return 0
        else:
            return 1

    @staticmethod
    def pvalue_symbol(pv=None, symbol=None):
        if 0.05 >= pv > 0.01:
            return symbol
        elif 0.01 >= pv > 0.001:
            return 2 * symbol
        elif pv <= 0.001:
            return 3 * symbol
        else:
            return None

    @staticmethod
    def get_file_from_gd(url=None):
        get_path = 'https://drive.google.com/uc?export=download&id=' + url.split('/')[-2]
        return pd.read_csv(get_path, comment='#')


class marker:

    def __init__(self):
        pass

    def geneplot_mhat(df, markeridcol, chr, pv, gwasp, markernames, gfont, gstyle, ax):
        if markeridcol is not None:
            if markernames is not None and markernames is True:
                for i in df[markeridcol].unique():
                    if df.loc[df[markeridcol] == i, pv].iloc[0] <= gwasp:
                        if gstyle == 1:
                            plt.text(df.loc[df[markeridcol] == i, 'ind'].iloc[0], df.loc[df[markeridcol] == i, 'tpval'].iloc[0],
                                    str(i), fontsize=gfont)
                        elif gstyle == 2:
                            plt.annotate(i, xy=(df.loc[df[markeridcol] == i, 'ind'].iloc[0], df.loc[df[markeridcol] == i, 'tpval'].iloc[0]),
                                         xycoords='data', xytext=(5, -15), textcoords='offset points', size=6,
                                         bbox=dict(boxstyle="round", alpha=0.2),
                                         arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.2, relpos=(0, 0)))
            elif markernames is not None and isinstance(markernames, (tuple, list)):
                for i in df[markeridcol].unique():
                    if i in markernames:
                        if gstyle == 1:
                            plt.text(df.loc[df[markeridcol] == i, 'ind'].iloc[0], df.loc[df[markeridcol] == i, 'tpval'].iloc[0],
                                str(i), fontsize=gfont)
                        elif gstyle == 2:
                            plt.annotate(i, xy=(df.loc[df[markeridcol] == i, 'ind'].iloc[0], df.loc[df[markeridcol] == i, 'tpval'].iloc[0]),
                                         xycoords='data', xytext=(5, -15), textcoords='offset points', size=6,
                                         bbox=dict(boxstyle="round", alpha=0.2),
                                         arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.2, relpos=(0, 0)))
            elif markernames is not None and isinstance(markernames, dict):
                for i in df[markeridcol].unique():
                    if i in markernames:
                        if gstyle == 1:
                            plt.text(df.loc[df[markeridcol] == i, 'ind'].iloc[0], df.loc[df[markeridcol] == i, 'tpval'].iloc[0],
                                 markernames[i], fontsize=gfont)
                        elif gstyle == 2:
                            plt.annotate(markernames[i], xy=(
                            df.loc[df[markeridcol] == i, 'ind'].iloc[0], df.loc[df[markeridcol] == i, 'tpval'].iloc[0]),
                                         xycoords='data', xytext=(5, -15), textcoords='offset points', size=6,
                                         bbox=dict(boxstyle="round", alpha=0.2),
                                         arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.2, relpos=(0, 0)))
        else:
            raise Exception("provide 'markeridcol' parameter")

    def mhat(df="dataframe", chr=None, pv=None, color=None, dim=(6,4), r=300, ar=90, gwas_sign_line=False,
             gwasp=5E-08, dotsize=8, markeridcol=None, markernames=None, gfont=8, valpha=1, show=False, figtype='png',
             axxlabel=None, axylabel=None, axlabelfontsize=9, axlabelfontname="Arial", axtickfontsize=9,
             axtickfontname="Arial", ylm=None, gstyle=1, figname='manhatten'):

        _x, _y = 'Chromosomes', r'$ -log_{10}(P)$'
        rand_colors = ('#a7414a', '#282726', '#6a8a82', '#a37c27', '#563838', '#0584f2', '#f28a30', '#f05837',
                       '#6465a5', '#00743f', '#be9063', '#de8cf0', '#888c46', '#c0334d', '#270101', '#8d2f23',
                       '#ee6c81', '#65734b', '#14325c', '#704307', '#b5b3be', '#f67280', '#ffd082', '#ffd800',
                       '#ad62aa', '#21bf73', '#a0855b', '#5edfff', '#08ffc8', '#ca3e47', '#c9753d', '#6c5ce7',
                       '#a997df', '#513b56', '#590925', '#007fff', '#bf1363', '#f39237', '#0a3200', '#8c271e')

        # minus log10 of P-value
        df['tpval'] = -np.log10(df[pv])
        # df = df.sort_values(chr)
        # if the column contains numeric strings
        df = df.loc[pd.to_numeric(df[chr], errors='coerce').sort_values().index]
        # add indices
        df['ind'] = range(len(df))
        df_group = df.groupby(chr)
        if color is not None and len(color) == 2:
            color_1 = int(df[chr].nunique() / 2) * [color[0]]
            color_2 = int(df[chr].nunique() / 2) * [color[1]]
            if df[chr].nunique() % 2 == 0:
                color_list = list(reduce(lambda x, y: x+y, zip(color_1, color_2)))
            elif df[chr].nunique() % 2 == 1:
                color_list = list(reduce(lambda x, y: x+y, zip(color_1, color_2)))
                color_list.append(color[0])
        elif color is not None and len(color) == df[chr].nunique():
            color_list = color
        elif color is None:
            # select colors randomly from the list based in number of chr
            color_list = sample(rand_colors, df[chr].nunique())
        else:
            print("Error: in color argument")
            sys.exit(1)

        xlabels = []
        xticks = []
        fig, ax = plt.subplots(figsize=dim)
        i = 0
        for label, df1 in df.groupby(chr):
            df1.plot(kind='scatter', x='ind', y='tpval', color=color_list[i], s=dotsize, alpha=valpha, ax=ax)
            df1_max_ind = df1['ind'].iloc[-1]
            df1_min_ind = df1['ind'].iloc[0]
            xlabels.append(label)
            xticks.append((df1_max_ind - (df1_max_ind - df1_min_ind) / 2))
            i += 1

        # add GWAS significant line
        if gwas_sign_line is True:
            ax.axhline(y=-np.log10(gwasp), linestyle='--', color='#7d7d7d', linewidth=1)
        if markernames is not None:
            marker.geneplot_mhat(df, markeridcol, chr, pv, gwasp, markernames, gfont, gstyle, ax=ax)
        ax.margins(x=0)
        ax.margins(y=0)
        ax.set_xticks(xticks)
        ax.set_ylim([0, max(df['tpval'] + 1)])
        if ylm:
            ylm = np.arange(ylm[0], ylm[1], ylm[2])
        else:
            ylm = np.arange(0, max(df['tpval']+1), 1)
        ax.set_yticks(ylm)
        ax.set_xticklabels(xlabels, rotation=ar)
        # ax.set_yticklabels(ylm, fontsize=axtickfontsize, fontname=axtickfontname, rotation=ar)
        if axxlabel:
            _x = axxlabel
        if axylabel:
            _y = axylabel
        ax.set_xlabel(_x, fontsize=axlabelfontsize, fontname=axlabelfontname)
        ax.set_ylabel(_y, fontsize=axlabelfontsize, fontname=axlabelfontname)
        general.get_figure(show, r, figtype, figname)


class stat:
    def __init__(self):
        pass

    def bardot(df="dataframe", dim=(6, 4), bw=0.4, colorbar="#f2aa4cff", colordot=["#101820ff"], hbsize=4, r=300, ar=0,
               dotsize=6, valphabar=1, valphadot=1, markerdot="o", errorbar=True, show=False, ylm=None, axtickfontsize=9,
               axtickfontname="Arial", axlabelfontsize=9, axlabelfontname="Arial", yerrlw=None, yerrcw=None, axxlabel=None,
                axylabel=None, figtype='png'):
        # set axis labels to None
        _x = None
        _y = None
        xbar = np.arange(len(df.columns.to_numpy()))
        color_list_bar = colorbar
        color_list_dot = colordot
        if len(color_list_dot) == 1:
            color_list_dot = colordot*len(df.columns.to_numpy())
        plt.subplots(figsize=dim)
        if errorbar:
            plt.bar(x=xbar, height=df.describe().loc['mean'], yerr=df.sem(), width=bw, color=color_list_bar, capsize=hbsize,
                zorder=0, alpha=valphabar, error_kw={'elinewidth': yerrlw, 'capthick': yerrcw})
        else:
            plt.bar(x=xbar, height=df.describe().loc['mean'], width=bw, color=color_list_bar,
                   capsize=hbsize,
                   zorder=0, alpha=valphabar)

        plt.xticks(xbar, df.columns.to_numpy(), fontsize=axtickfontsize, rotation=ar, fontname=axtickfontname)
        if axxlabel:
            _x = axxlabel
        if axylabel:
            _y = axylabel
        general.axis_labels(_x, _y, axlabelfontsize, axlabelfontname)
        # ylm must be tuple of start, end, interval
        if ylm:
            plt.ylim(bottom=ylm[0], top=ylm[1])
            plt.yticks(np.arange(ylm[0], ylm[1], ylm[2]), fontsize=axtickfontsize, fontname=axtickfontname)
        plt.yticks(fontsize=axtickfontsize, rotation=ar, fontname=axtickfontname)
        # add dots
        for cols in range(len(df.columns.to_numpy())):
            # get markers from here https://matplotlib.org/3.1.1/api/markers_api.html
            plt.scatter(x=np.linspace(xbar[cols]-bw/2, xbar[cols]+bw/2, int(df.describe().loc['count'][cols])),
                       y=df[df.columns[cols]].dropna(), s=dotsize, color=color_list_dot[cols], zorder=1, alpha=valphadot,
                       marker=markerdot)
        general.get_figure(show, r, figtype, 'bardot')

    def regplot(df="dataframe", x=None, y=None, yhat=None, dim=(6, 4), colordot='#4a4e4d', colorline='#fe8a71', r=300,
                ar=0, dotsize=6, valphaline=1, valphadot=1, linewidth=1, markerdot="o", show=False, axtickfontsize=9,
               axtickfontname="Arial", axlabelfontsize=9, axlabelfontname="Arial", ylm=None, xlm=None, axxlabel=None,
                axylabel=None, figtype='png'):
        fig, ax = plt.subplots(figsize=dim)
        plt.scatter(df[x].to_numpy(), df[y].to_numpy(), color=colordot, s=dotsize, alpha=valphadot, marker=markerdot,
                    label='Observed data')
        plt.plot(df[x].to_numpy(), df[yhat].to_numpy(), color=colorline, linewidth=linewidth, alpha=valphaline,
                 label='Regression line')
        if axxlabel:
            x = axxlabel
        if axylabel:
            y = axylabel
        general.axis_labels(x, y, axlabelfontsize, axlabelfontname)
        general.axis_ticks(xlm, ylm, axtickfontsize, axtickfontname, ar)
        plt.legend(fontsize=9)
        general.get_figure(show, r, figtype, 'reg_plot')

    def reg_resid_plot(df="dataframe", yhat=None, resid=None, stdresid=None, dim=(6, 4), colordot='#4a4e4d',
                       colorline='#2ab7ca', r=300, ar=0, dotsize=6, valphaline=1, valphadot=1, linewidth=1,
                       markerdot="o", show=False, figtype='png'):
        fig, ax = plt.subplots(figsize=dim)
        if resid is not None:
            plt.scatter(df[yhat], df[resid], color=colordot, s=dotsize, alpha=valphadot, marker=markerdot)
            plt.axhline(y=0, color=colorline, linestyle='--', linewidth=linewidth, alpha=valphaline)
            plt.xlabel("Fitted")
            plt.ylabel("Residuals")
            general.get_figure(show, r, figtype, 'resid_plot')
        else:
            print ("Error: Provide residual data")
        if stdresid is not None:
            plt.scatter(df[yhat], df[stdresid], color=colordot, s=dotsize, alpha=valphadot, marker=markerdot)
            plt.axhline(y=0, color=colorline, linestyle='--', linewidth=linewidth, alpha=valphaline)
            plt.xlabel("Fitted")
            plt.ylabel("Standardized Residuals")
            general.get_figure(show, r, figtype, 'std_resid_plot')
        else:
            print ("Error: Provide standardized residual data")

    def corr_mat(df="dataframe", corm="pearson", cmap="seismic", r=300, show=False, dim=(6, 5), axtickfontname="Arial",
                 axtickfontsize=7, ar=90, figtype='png'):
        d_corr = df.corr(method=corm)
        plt.subplots(figsize=dim)
        plt.matshow(d_corr, vmin=-1, vmax=1, cmap=cmap)
        plt.colorbar()
        cols = list(df)
        ticks = list(range(0, len(list(df))))
        plt.xticks(ticks, cols, fontsize=axtickfontsize, fontname=axtickfontname, rotation=ar)
        plt.yticks(ticks, cols, fontsize=axtickfontsize, fontname=axtickfontname)
        general.get_figure(show, r, figtype, 'corr_mat')

    # for data with pre-calculated mean and SE
    def multi_bar(df="dataframe", dim=(5, 4), colbar=None, colerrorbar=None, bw=0.4, colorbar=None, xbarcol=None, r=300,
                  show=False, axtickfontname="Arial", axtickfontsize=9, ax_x_ticklabel=None, ar=90, figtype='png',
                  figname='multi_bar', valphabar=1, legendpos='best', errorbar=False, yerrlw=None, yerrcw=None,
                  plotlegend=False, hbsize=4, ylm=None, add_sign_line=False, pv=None,
                  sign_line_opts={'symbol': '*', 'fontsize': 8, 'linewidth':0.8, 'arrowstyle': '-', 'dist_y_pos': 2.5,
                                  'dist_y_neg': 4.2}, add_sign_symbol=False, sign_symbol_opts={'symbol': '*',
                                                                                              'fontsize': 8 },
                  dotplot=False, sub_cat=None,
                  sub_cat_opts={'y_neg_dist': 3.5, 'fontsize': 8}, sub_cat_label_dist=None):
        xbar = np.arange(df.shape[0])
        xbar_temp = xbar
        fig, ax = plt.subplots(figsize=dim)
        assert len(colbar) >= 2, "number of bar should be atleast 2"
        assert len(colbar) == len(colorbar), "number of color should be equivalent to number of column bars"
        if colbar is not None and isinstance(colbar, (tuple, list)):
            for i in range(len(colbar)):
                if errorbar:
                    ax.bar(x=xbar_temp, height=df[colbar[i]], yerr=df[colerrorbar[i]], width=bw, color=colorbar[i],
                           alpha=valphabar, capsize=hbsize, label=colbar[i], error_kw={'elinewidth': yerrlw,
                                                                                       'capthick': yerrcw})
                    xbar_temp = xbar_temp+bw
                else:
                    ax.bar(x=xbar_temp, height=df[colbar[i]], width=bw, color=colorbar[i], alpha=valphabar,
                           label=colbar[i])
                    xbar_temp = xbar_temp + bw
        ax.set_xticks(xbar+( (bw*(len(colbar)-1)) / (1+(len(colbar)-1)) ))
        if ax_x_ticklabel:
            x_ticklabel = ax_x_ticklabel
        else:
            x_ticklabel = df[xbarcol]
        ax.set_xticklabels(x_ticklabel, fontsize=axtickfontsize, rotation=ar, fontname=axtickfontname)
        # ylm must be tuple of start, end, interval
        if ylm:
            plt.ylim(bottom=ylm[0], top=ylm[1])
            plt.yticks(np.arange(ylm[0], ylm[1], ylm[2]), fontsize=axtickfontsize, fontname=axtickfontname)
        if plotlegend:
            plt.legend(loc=legendpos)

        if dotplot:
            for cols in range(len(df2['factors'].unique())):
                ax.scatter(x=np.linspace(xbar[cols] - bw / 2, xbar[cols] + bw / 2, int(reps)),
                       y=df2[(df2['factors'] == df2['factors'].unique()[cols]) & (df2['sample'] == 'M')]['value'],
                       s=dotsize, color="#7d0013", zorder=1, alpha=valphadot,
                       marker=markerdot)

        if add_sign_line:
            if len(colbar) == 2:
                for i in xbar:
                    x_pos = xbar[i]
                    x_pos_2 = xbar[i] + bw
                    y_pos = df[colbar[0]].to_numpy()[i] + df[colerrorbar[0]].to_numpy()[i]
                    y_pos_2 = df[colbar[1]].to_numpy()[i] + df[colerrorbar[1]].to_numpy()[i]
                    # only if y axis is positive
                    if y_pos > 0:
                        y_pos += 0.5
                        y_pos_2 += 0.5
                        pv_symb = general.pvalue_symbol(pv[i], sign_line_opts['symbol'])
                        if pv_symb:
                            ax.annotate('', xy=(x_pos, y_pos), xytext=(x_pos_2, y_pos),
                                        arrowprops={'connectionstyle': 'bar, armA=50, armB=50, angle=180, fraction=0 ',
                                                    'arrowstyle': sign_line_opts['arrowstyle'],
                                                    'linewidth': sign_line_opts['linewidth']})
                            ax.annotate(pv_symb, xy=(np.mean([x_pos, x_pos_2]),  max(y_pos, y_pos_2) +
                                                     sign_line_opts['dist_y_pos']),
                                        fontsize=sign_line_opts['fontsize'], ha="center")
                    else:
                        y_pos -= 0.5
                        y_pos_2 -= 0.5
                        pv_symb = general.pvalue_symbol(pv[i], sign_line_opts['symbol'])
                        if pv_symb:
                            ax.annotate('', xy=(x_pos, y_pos), xytext=(x_pos_2, y_pos),
                                        arrowprops={'connectionstyle': 'bar, armA=50, armB=50, angle=180, fraction=-1 ',
                                                    'arrowstyle': sign_line_opts['arrowstyle'],
                                                    'linewidth': sign_line_opts['linewidth']})
                            ax.annotate(pv_symb, xy=(np.mean([x_pos, x_pos_2]), min(y_pos_2, y_pos) -
                                                     sign_line_opts['dist_y_neg']),
                                        fontsize=sign_line_opts['fontsize'], ha="center")
        if add_sign_symbol:
            if len(colbar) == 2:
                for i in xbar:
                    x_pos = xbar[i]
                    x_pos_2 = xbar[i] + bw
                    # max value size factor is essential for rel pos of symbol
                    y_pos = df[colbar[0]].to_numpy()[i] + df[colerrorbar[0]].to_numpy()[i] + \
                            (max(df[colbar[0]].to_numpy()) / 20)
                    y_pos_2 = df[colbar[1]].to_numpy()[i] + df[colerrorbar[1]].to_numpy()[i] + \
                              (max(df[colbar[1]].to_numpy()) / 20)
                    # only if y axis is positive
                    if y_pos > 0:
                            pv_symb_1 = general.pvalue_symbol(pv[i][0], sign_symbol_opts['symbol'])
                            pv_symb_2 = general.pvalue_symbol(pv[i][1], sign_symbol_opts['symbol'])
                            if pv_symb_1:
                                plt.annotate(pv_symb_1, xy=(x_pos, y_pos), fontsize=sign_symbol_opts['fontsize'],
                                                ha="center")
                            if pv_symb_2:
                                plt.annotate(pv_symb_2, xy=(x_pos_2, y_pos_2), fontsize=sign_symbol_opts['fontsize'],
                                             ha="center")
            elif len(colbar) == 3:
                for i in xbar:
                    x_pos = xbar[i]
                    x_pos_2 = xbar[i] + bw
                    x_pos_3 = xbar[i] + (2 * bw)
                    # max value size factor is essential for rel pos of symbol
                    y_pos = df[colbar[0]].to_numpy()[i] + df[colerrorbar[0]].to_numpy()[i] + \
                            (max(df[colbar[0]].to_numpy()) / 20)
                    y_pos_2 = df[colbar[1]].to_numpy()[i] + df[colerrorbar[1]].to_numpy()[i] + \
                              (max(df[colbar[1]].to_numpy()) / 20)
                    y_pos_3 = df[colbar[2]].to_numpy()[i] + df[colerrorbar[2]].to_numpy()[i] + \
                              (max(df[colbar[2]].to_numpy()) / 20)
                    # only if y axis is positive
                    if y_pos > 0:
                            pv_symb_1 = general.pvalue_symbol(pv[i][0], sign_symbol_opts['symbol'])
                            pv_symb_2 = general.pvalue_symbol(pv[i][1], sign_symbol_opts['symbol'])
                            pv_symb_3 = general.pvalue_symbol(pv[i][2], sign_symbol_opts['symbol'])
                            if pv_symb_1:
                                plt.annotate(pv_symb_1, xy=(x_pos, y_pos), fontsize=sign_symbol_opts['fontsize'],
                                                ha="center")
                            if pv_symb_2:
                                plt.annotate(pv_symb_2, xy=(x_pos_2, y_pos_2), fontsize=sign_symbol_opts['fontsize'],
                                             ha="center")
                            if pv_symb_3:
                                plt.annotate(pv_symb_3, xy=(x_pos_3, y_pos_3), fontsize=sign_symbol_opts['fontsize'],
                                             ha="center")
        # update this later for min_value
        min_value = 0
        sub_cat_i = 0
        if sub_cat:
            if isinstance(sub_cat, dict):
                for k in sub_cat:
                    if isinstance(k, tuple) and len(k) == 2:
                        cat_x_pos, cat_y_pos, cat_x_pos_2 = k[0], min_value - \
                                                            (sub_cat_opts[
                                                                 'y_neg_dist'] * size_factor_to_start_line), k[1]
                        plt.annotate('', xy=(cat_x_pos - (bw / 2), cat_y_pos),
                                     xytext=(cat_x_pos_2 + (bw / 2), cat_y_pos),
                                     arrowprops={'arrowstyle': '-', 'linewidth': 0.5}, annotation_clip=False)
                        if sub_cat_label_dist and isinstance(sub_cat_label_dist, list):
                            plt.annotate(sub_cat[k], xy=(np.mean([cat_x_pos, cat_x_pos_2]),
                                                         cat_y_pos - size_factor_to_start_line - sub_cat_label_dist[
                                                             sub_cat_i]),
                                         ha="center", fontsize=sub_cat_opts['fontsize'], annotation_clip=False)
                            sub_cat_i += 1
                        else:
                            plt.annotate(sub_cat[k], xy=(np.mean([cat_x_pos, cat_x_pos_2]),
                                                         cat_y_pos - size_factor_to_start_line),
                                         ha="center", fontsize=sub_cat_opts['fontsize'], annotation_clip=False)
                    else:
                        raise KeyError("Sub category keys must be tuple of size 2")

        general.get_figure(show, r, figtype, figname)

    # with replicates values stacked replicates
    # need to work on this later
    def multi_bar_raw(df="dataframe", dim=(5, 4), samp_col_name=None, bw=0.4, colorbar=None, r=300,
                  show=False, axtickfontname="Arial", axtickfontsize=(9, 9), ax_x_ticklabel=None, ar=(0, 90), figtype='png',
                  figname='multi_bar', valphabar=1, legendpos='best', errorbar=False, yerrlw=None, yerrcw=None,
                  plotlegend=False, hbsize=4, ylm=None, add_sign_line=False, pv=None,
                  sign_line_opts={'symbol': '*', 'fontsize': 9, 'linewidth': 0.8, 'arrowstyle': '-', 'dist_y_pos': 2.5,
                                  'dist_y_neg': 4.2}, add_sign_symbol=False,
                      sign_symbol_opts={'symbol': '*', 'fontsize': 9, 'fontname':'Arial', 'rotation':0},
                  dotplot=False, dotplot_opts={'dotsize': 5, 'color':'#7d0013', 'valpha': 1, 'marker': 'o'},
                  sign_line_pairs=None, group_let_df=None, legendanchor=None, legendcols=1, legendfontsize=8,
                  axylabel=None, axxlabel=None, symb_dist=None, axlabelfontsize=(9, 9), axlabelar=(0, 90), sub_cat=None,
                  sub_cat_opts={'y_neg_dist': 3.5, 'fontsize': 9, 'fontname':'Arial'}, sub_cat_label_dist=None,
                      legendlabelframe=False, div_fact=20, legend_columnspacing=None, add_text=None):
        if samp_col_name is None or colorbar is None:
            raise ValueError('Invalid value for samp_col_name or colorbar options')
        fig, ax = plt.subplots(figsize=dim)
        sample_list = df[samp_col_name].unique()
        # assert len(sample_list) >= 2, "number of bar should be atleast 2"
        df_mean = df.groupby(samp_col_name).mean().reset_index().set_index(samp_col_name).T
        df_sem = df.groupby(samp_col_name).sem().reset_index().set_index(samp_col_name).T
        colbar = sample_list
        colerrorbar = sample_list
        xbar = np.arange(df_mean.shape[0])
        xbar_temp = xbar
        xbarcol = df_mean.index
        assert len(colbar) == len(colorbar), "number of color should be equivalent to number of column bars"
        df_melt = pd.melt(df.reset_index(), id_vars=[samp_col_name], value_vars=df_mean.index)
        variable_list = df_melt['variable'].unique()
        min_value = (0, min(df_mean.min()))[min(df_mean.min()) < 0]

        if colbar is not None:
            for i in range(len(colbar)):
                if errorbar:
                    ax.bar(x=xbar_temp, height=df_mean[colbar[i]], yerr=df_sem[colerrorbar[i]], width=bw,
                           color=colorbar[i], alpha=valphabar, capsize=hbsize, label=colbar[i],
                           error_kw={'elinewidth': yerrlw, 'capthick': yerrcw})
                    xbar_temp = xbar_temp + bw
                else:
                    ax.bar(x=xbar_temp, height=df_mean[colbar[i]], width=bw, color=colorbar[i], alpha=valphabar,
                           label=colbar[i])
                    xbar_temp = xbar_temp + bw

        bw_fact = bw / 2
        ax.set_xticks(xbar+((len(df_mean.columns)-1) * bw_fact) )
        # ax.set_xticks(xbar + ((bw * (len(colbar) - 1)) / (1 + (len(colbar) - 1))))
        if ax_x_ticklabel:
            x_ticklabel = ax_x_ticklabel
        else:
            x_ticklabel = df[xbarcol]
        ax.set_xticklabels(x_ticklabel, fontsize=axtickfontsize[0], rotation=ar[0], fontname=axtickfontname)
        if axylabel:
            ax.set_ylabel(axylabel, fontsize=axlabelfontsize[1], rotation=axlabelar[1], fontname=axtickfontname)
        if axxlabel:
            ax.set_xlabel(axxlabel, fontsize=axlabelfontsize[0], rotation=axlabelar[0], fontname=axtickfontname)
        # ylm must be tuple of start, end, interval
        if ylm:
            plt.ylim(bottom=ylm[0], top=ylm[1])
            plt.yticks(np.arange(ylm[0], ylm[1], ylm[2]), fontsize=axtickfontsize[1],
                       fontname=axtickfontname)
        if plotlegend:
            plt.legend(loc=legendpos, bbox_to_anchor=legendanchor, ncol=legendcols, fontsize=legendfontsize,
                       frameon=legendlabelframe, columnspacing=legend_columnspacing)

        if isinstance(add_text, list):
            plt.text(add_text[0], add_text[1], add_text[2], fontsize=9, fontfamily='Arial')

        if dotplot:
            for cols in range(len(variable_list)):
                move_fact = 0
                for cols1 in range(len(sample_list)):
                        ax.scatter(x=np.linspace(xbar[cols] - bw_fact + move_fact, xbar[cols] + bw_fact + move_fact,
                                         int(df.groupby(samp_col_name).count().loc[sample_list[cols1], variable_list[cols]])),
                           y=df_melt[(df_melt['variable'] == df_melt['variable'].unique()[cols]) & (
                                       df_melt[samp_col_name] == sample_list[cols1])]['value'], s=dotplot_opts['dotsize'],
                               color=dotplot_opts['color'], zorder=10, alpha=dotplot_opts['valpha'],
                               marker=dotplot_opts['marker'])
                        move_fact += 2 * bw_fact

        size_factor_to_start_line = max(df_mean.max()) / div_fact
        y_pos_dict = dict()
        y_pos_dict_trt = dict()
        if add_sign_line:
            if len(colbar) == 2:
                for i in xbar:
                    x_pos = xbar[i]
                    x_pos_2 = xbar[i] + bw
                    y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i]
                    y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i]
                    # only if y axis is positive
                    if y_pos > 0:
                        y_pos += 0.5
                        y_pos_2 += 0.5
                        pv_symb = general.pvalue_symbol(pv[i], sign_line_opts['symbol'])
                        if pv_symb:
                            ax.annotate('', xy=(x_pos, max(y_pos, y_pos_2)), xytext=(x_pos_2, max(y_pos, y_pos_2)),
                                        arrowprops={'connectionstyle': 'bar, armA=50, armB=50, angle=180, fraction=0 ',
                                                    'arrowstyle': sign_line_opts['arrowstyle'],
                                                    'linewidth': sign_line_opts['linewidth']})
                            ax.annotate(pv_symb, xy=(np.mean([x_pos, x_pos_2]), max(y_pos, y_pos_2) +
                                                     sign_line_opts['dist_y_pos']),
                                        fontsize=sign_line_opts['fontsize'], ha="center")
                    else:
                        y_pos -= 0.5
                        y_pos_2 -= 0.5
                        pv_symb = general.pvalue_symbol(pv[i], sign_line_opts['symbol'])
                        if pv_symb:
                            ax.annotate('', xy=(x_pos, y_pos), xytext=(x_pos_2, y_pos),
                                        arrowprops={'connectionstyle': 'bar, armA=50, armB=50, angle=180, fraction=-1 ',
                                                    'arrowstyle': sign_line_opts['arrowstyle'],
                                                    'linewidth': sign_line_opts['linewidth']})
                            ax.annotate(pv_symb, xy=(np.mean([x_pos, x_pos_2]), min(y_pos_2, y_pos) -
                                                     sign_line_opts['dist_y_neg']),
                                        fontsize=sign_line_opts['fontsize'], ha="center")
            elif len(colbar) == 3:
                for i in xbar:
                    x_pos = xbar[i]
                    x_pos_2 = xbar[i] + bw
                    x_pos_3 = xbar[i] + (2 * bw)
                    y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i]
                    y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i]
                    y_pos_3 = df_mean[colbar[2]].to_numpy()[i] + df_sem[colerrorbar[2]].to_numpy()[i]

                    # only if y axis is positive
                    if y_pos > 0:
                        y_pos += size_factor_to_start_line / 2
                        y_pos_2 += size_factor_to_start_line / 2
                        y_pos_3 += size_factor_to_start_line / 2

                        pv_symb1 = general.pvalue_symbol(pv[i][0], sign_line_opts['symbol'])
                        pv_symb2 = general.pvalue_symbol(pv[i][1], sign_line_opts['symbol'])
                        if pv_symb1:
                            if max(y_pos, y_pos_2) >= y_pos_3:
                                pass
                            ax.annotate('', xy=(x_pos, max(y_pos, y_pos_2)), xytext=(x_pos_2, max(y_pos, y_pos_2)),
                                        arrowprops={'connectionstyle': 'bar, armA=50, armB=50, angle=180, fraction=0 ',
                                                    'arrowstyle': sign_line_opts['arrowstyle'],
                                                    'linewidth': sign_line_opts['linewidth']})
                            ax.annotate(pv_symb1, xy=(np.mean([x_pos, x_pos_2]), max(y_pos, y_pos_2) +
                                                     size_factor_to_start_line),
                                        fontsize=sign_line_opts['fontsize'], ha="center")
                        if pv_symb2:
                            if max(y_pos, y_pos_3) < y_pos_2:
                                y_pos_3 = y_pos_2 + (4 * size_factor_to_start_line)
                            ax.annotate('', xy=(x_pos, max(y_pos, y_pos_3)), xytext=(x_pos_3, max(y_pos, y_pos_3)),
                                        arrowprops={'connectionstyle': 'bar, armA=50, armB=50, angle=180, fraction=0 ',
                                                    'arrowstyle': sign_line_opts['arrowstyle'],
                                                    'linewidth': sign_line_opts['linewidth']})
                            ax.annotate(pv_symb2, xy=(np.mean([x_pos, x_pos_3]), max(y_pos, y_pos_3) +
                                                      size_factor_to_start_line),
                                        fontsize=sign_line_opts['fontsize'], ha="center")
                    else:
                        y_pos -= 0.5
                        y_pos_2 -= 0.5
                        pv_symb = general.pvalue_symbol(pv[i], sign_line_opts['symbol'])
                        if pv_symb:
                            ax.annotate('', xy=(x_pos, y_pos), xytext=(x_pos_2, y_pos),
                                        arrowprops={'connectionstyle': 'bar, armA=50, armB=50, angle=180, fraction=-1 ',
                                                    'arrowstyle': sign_line_opts['arrowstyle'],
                                                    'linewidth': sign_line_opts['linewidth']})
                            ax.annotate(pv_symb, xy=(np.mean([x_pos, x_pos_2]), min(y_pos_2, y_pos) -
                                                     sign_line_opts['dist_y_neg']),
                                        fontsize=sign_line_opts['fontsize'], ha="center")

        if add_sign_symbol:
            if len(colbar) == 2:
                for i in xbar:
                    x_pos = xbar[i]
                    x_pos_2 = xbar[i] + bw
                    if symb_dist:
                        # max value size factor is essential for rel pos of symbol
                        y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i] + \
                                (max(df_mean[colbar[0]].to_numpy()) / 20) + symb_dist[i][0]
                        y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i] + \
                                (max(df_mean[colbar[1]].to_numpy()) / 20) + symb_dist[i][1]
                    else:
                        y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i] + \
                                (max(df_mean[colbar[0]].to_numpy()) / 20)
                        y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i] + \
                                  (max(df_mean[colbar[1]].to_numpy()) / 20)

                    '''
                    y_pos = df[colbar[0]].to_numpy()[i] + df[colerrorbar[0]].to_numpy()[i] + \
                            (max(df[colbar[0]].to_numpy()) / 20)
                    y_pos_2 = df[colbar[1]].to_numpy()[i] + df[colerrorbar[1]].to_numpy()[i] + \
                              (max(df[colbar[1]].to_numpy()) / 20)
                    '''
                    # group_let_df need index column
                    if isinstance(group_let_df, pd.DataFrame):
                        # only if y axis is positive
                        if y_pos > 0:
                            if not pd.isnull(group_let_df.loc[colbar[0], xbarcol[i]]):
                                plt.annotate(group_let_df.loc[colbar[0], xbarcol[i]], xy=(x_pos, y_pos),
                                             fontsize=sign_symbol_opts['fontsize'], ha='center',
                                             fontfamily=sign_symbol_opts['fontname'],
                                             rotation=sign_symbol_opts['rotation'])
                        if y_pos_2 > 0:
                            if not pd.isnull(group_let_df.loc[colbar[1], xbarcol[i]]):
                                plt.annotate(group_let_df.loc[colbar[1], xbarcol[i]], xy=(x_pos_2, y_pos_2),
                                             fontsize=sign_symbol_opts['fontsize'], ha='center',
                                             fontfamily=sign_symbol_opts['fontname'],
                                             rotation=sign_symbol_opts['rotation'])
                    # only if y axis is positive
                    # need to verify this
                    elif pv:
                        if y_pos > 0:
                            pv_symb_1 = general.pvalue_symbol(pv[i][0], sign_symbol_opts['symbol'])
                            pv_symb_2 = general.pvalue_symbol(pv[i][1], sign_symbol_opts['symbol'])
                            if pv_symb_1:
                                plt.annotate(pv_symb_1, xy=(x_pos, y_pos), fontsize=sign_symbol_opts['fontsize'],
                                         ha="center", fontfamily=sign_symbol_opts['fontname'],
                                             rotation=sign_symbol_opts['rotation'])
                            if pv_symb_2:
                                plt.annotate(pv_symb_2, xy=(x_pos_2, y_pos_2), fontsize=sign_symbol_opts['fontsize'],
                                         ha="center", fontfamily=sign_symbol_opts['fontname'],
                                             rotation=sign_symbol_opts['rotation'])
                    else:
                        raise Exception('Either group dataframe of p value list is required')

            elif len(colbar) == 3:
                for i in xbar:
                    x_pos = xbar[i]
                    x_pos_2 = xbar[i] + bw
                    x_pos_3 = xbar[i] + (2 * bw)

                    if symb_dist:
                        # max value size factor is essential for rel pos of symbol
                        y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i] + \
                                (max(df_mean[colbar[0]].to_numpy()) / 20) + symb_dist[i][0]
                        y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i] + \
                                (max(df_mean[colbar[1]].to_numpy()) / 20) + symb_dist[i][1]
                        y_pos_3 = df_mean[colbar[2]].to_numpy()[i] + df_sem[colerrorbar[2]].to_numpy()[i] + \
                                  (max(df_mean[colbar[2]].to_numpy()) / 20) + symb_dist[i][2]
                    else:
                        y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i] + \
                                (max(df_mean[colbar[0]].to_numpy()) / 20)
                        y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i] + \
                                  (max(df_mean[colbar[1]].to_numpy()) / 20)
                        y_pos_3 = df_mean[colbar[2]].to_numpy()[i] + df_sem[colerrorbar[2]].to_numpy()[i] + \
                                  (max(df_mean[colbar[2]].to_numpy()) / 20)

                    # group_let_df need index column
                    if isinstance(group_let_df, pd.DataFrame):
                        if y_pos > 0:
                            plt.annotate(group_let_df.loc[colbar[0], xbarcol[i]], xy=(x_pos, y_pos),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")
                        if y_pos_2 > 0:
                            plt.annotate(group_let_df.loc[colbar[1], xbarcol[i]], xy=(x_pos_2, y_pos_2),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")
                        if y_pos_3 > 0:
                            plt.annotate(group_let_df.loc[colbar[2], xbarcol[i]], xy=(x_pos_3, y_pos_3),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")

                    if pv:
                        # only if y axis is positive
                        if y_pos > 0:
                            pv_symb_1 = general.pvalue_symbol(pv[i][0], sign_symbol_opts['symbol'])
                            pv_symb_2 = general.pvalue_symbol(pv[i][1], sign_symbol_opts['symbol'])
                            pv_symb_3 = general.pvalue_symbol(pv[i][2], sign_symbol_opts['symbol'])
                            if pv_symb_1:
                                plt.annotate(pv_symb_1, xy=(x_pos, y_pos), fontsize=sign_symbol_opts['fontsize'],
                                             ha="center")
                            if pv_symb_2:
                                plt.annotate(pv_symb_2, xy=(x_pos_2, y_pos_2), fontsize=sign_symbol_opts['fontsize'],
                                             ha="center")
                            if pv_symb_3:
                                plt.annotate(pv_symb_3, xy=(x_pos_3, y_pos_3), fontsize=sign_symbol_opts['fontsize'],
                                             ha="center")
            elif len(colbar) == 4:
                for i in xbar:
                    x_pos = xbar[i]
                    x_pos_2 = xbar[i] + bw
                    x_pos_3 = xbar[i] + (2 * bw)
                    x_pos_4 = xbar[i] + (3 * bw)
                    if symb_dist:
                        # max value size factor is essential for rel pos of symbol
                        y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i] + \
                                (max(df_mean[colbar[0]].to_numpy()) / 20) + symb_dist[i][0]
                        y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i] + \
                                (max(df_mean[colbar[1]].to_numpy()) / 20) + symb_dist[i][1]
                        y_pos_3 = df_mean[colbar[2]].to_numpy()[i] + df_sem[colerrorbar[2]].to_numpy()[i] + \
                                (max(df_mean[colbar[2]].to_numpy()) / 20) + symb_dist[i][2]
                        y_pos_4 = df_mean[colbar[3]].to_numpy()[i] + df_sem[colerrorbar[3]].to_numpy()[i] + \
                                (max(df_mean[colbar[3]].to_numpy()) / 20) + symb_dist[i][3]
                    else:
                        y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i] + \
                                (max(df_mean[colbar[0]].to_numpy()) / 20)
                        y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i] + \
                                  (max(df_mean[colbar[1]].to_numpy()) / 20)
                        y_pos_3 = df_mean[colbar[2]].to_numpy()[i] + df_sem[colerrorbar[2]].to_numpy()[i] + \
                                  (max(df_mean[colbar[2]].to_numpy()) / 20)
                        y_pos_4 = df_mean[colbar[3]].to_numpy()[i] + df_sem[colerrorbar[3]].to_numpy()[i] + \
                                  (max(df_mean[colbar[3]].to_numpy()) / 20)

                    # group_let_df need index column
                    if isinstance(group_let_df, pd.DataFrame):
                        # only if y axis is positive
                        if y_pos > 0:
                            plt.annotate(group_let_df.loc[colbar[0], xbarcol[i]], xy=(x_pos, y_pos),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")
                        if y_pos_2 > 0:
                            plt.annotate(group_let_df.loc[colbar[1], xbarcol[i]], xy=(x_pos_2, y_pos_2),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")
                        if y_pos_3 > 0:
                            plt.annotate(group_let_df.loc[colbar[2], xbarcol[i]], xy=(x_pos_3, y_pos_3),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")
                        if y_pos_4 > 0:
                            plt.annotate(group_let_df.loc[colbar[3], xbarcol[i]], xy=(x_pos_4, y_pos_4),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")

                    # need to work on this for 4 bars
                    if pv:
                        pv_symb_1 = general.pvalue_symbol(pv[i][0], sign_symbol_opts['symbol'])
                        pv_symb_2 = general.pvalue_symbol(pv[i][1], sign_symbol_opts['symbol'])
                        pv_symb_3 = general.pvalue_symbol(pv[i][2], sign_symbol_opts['symbol'])
                        if pv_symb_1:
                            plt.annotate(pv_symb_1, xy=(x_pos, y_pos), fontsize=sign_symbol_opts['fontsize'],
                                         ha="center")
                        if pv_symb_2:
                            plt.annotate(pv_symb_2, xy=(x_pos_2, y_pos_2), fontsize=sign_symbol_opts['fontsize'],
                                         ha="center")
                        if pv_symb_3:
                            plt.annotate(pv_symb_3, xy=(x_pos_3, y_pos_3), fontsize=sign_symbol_opts['fontsize'],
                                         ha="center")
            elif len(colbar) == 5:
                for i in xbar:
                    x_pos = xbar[i]
                    x_pos_2 = xbar[i] + bw
                    x_pos_3 = xbar[i] + (2 * bw)
                    x_pos_4 = xbar[i] + (3 * bw)
                    x_pos_5 = xbar[i] + (4 * bw)
                    # max value size factor is essential for rel pos of symbol
                    if symb_dist:
                        y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i] + \
                                (max(df_mean[colbar[0]].to_numpy()) / 20) + symb_dist[i][0]
                        y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i] + \
                                  (max(df_mean[colbar[1]].to_numpy()) / 20) + symb_dist[i][1]
                        y_pos_3 = df_mean[colbar[2]].to_numpy()[i] + df_sem[colerrorbar[2]].to_numpy()[i] + \
                                  (max(df_mean[colbar[2]].to_numpy()) / 20) + symb_dist[i][2]
                        y_pos_4 = df_mean[colbar[3]].to_numpy()[i] + df_sem[colerrorbar[3]].to_numpy()[i] + \
                                  (max(df_mean[colbar[3]].to_numpy()) / 20) + symb_dist[i][3]
                        y_pos_5 = df_mean[colbar[4]].to_numpy()[i] + df_sem[colerrorbar[4]].to_numpy()[i] + \
                                  (max(df_mean[colbar[4]].to_numpy()) / 20) + symb_dist[i][4]
                    else:
                        y_pos = df_mean[colbar[0]].to_numpy()[i] + df_sem[colerrorbar[0]].to_numpy()[i] + \
                                (max(df_mean[colbar[0]].to_numpy()) / 20)
                        y_pos_2 = df_mean[colbar[1]].to_numpy()[i] + df_sem[colerrorbar[1]].to_numpy()[i] + \
                                  (max(df_mean[colbar[1]].to_numpy()) / 20)
                        y_pos_3 = df_mean[colbar[2]].to_numpy()[i] + df_sem[colerrorbar[2]].to_numpy()[i] + \
                                  (max(df_mean[colbar[2]].to_numpy()) / 20)
                        y_pos_4 = df_mean[colbar[3]].to_numpy()[i] + df_sem[colerrorbar[3]].to_numpy()[i] + \
                                  (max(df_mean[colbar[3]].to_numpy()) / 20)
                        y_pos_5 = df_mean[colbar[4]].to_numpy()[i] + df_sem[colerrorbar[4]].to_numpy()[i] + \
                                  (max(df_mean[colbar[4]].to_numpy()) / 20)

                    # group_let_df need index column
                    if isinstance(group_let_df, pd.DataFrame):
                        # only if y axis is positive
                        if y_pos > 0:
                            plt.annotate(group_let_df.loc[colbar[0], xbarcol[i]], xy=(x_pos, y_pos),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")
                        if y_pos_2 > 0:
                            plt.annotate(group_let_df.loc[colbar[1], xbarcol[i]], xy=(x_pos_2, y_pos_2),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")
                        if y_pos_3 > 0:
                            plt.annotate(group_let_df.loc[colbar[2], xbarcol[i]], xy=(x_pos_3, y_pos_3),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")
                        if y_pos_4 > 0:
                            plt.annotate(group_let_df.loc[colbar[3], xbarcol[i]], xy=(x_pos_4, y_pos_4),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")
                        if y_pos_5 > 0:
                            plt.annotate(group_let_df.loc[colbar[4], xbarcol[i]], xy=(x_pos_5, y_pos_5),
                                         fontsize=sign_symbol_opts['fontsize'], ha="center")

                    # need to work on this for 4 bars
                    if pv:
                        pv_symb_1 = general.pvalue_symbol(pv[i][0], sign_symbol_opts['symbol'])
                        pv_symb_2 = general.pvalue_symbol(pv[i][1], sign_symbol_opts['symbol'])
                        pv_symb_3 = general.pvalue_symbol(pv[i][2], sign_symbol_opts['symbol'])
                        if pv_symb_1:
                            plt.annotate(pv_symb_1, xy=(x_pos, y_pos), fontsize=sign_symbol_opts['fontsize'],
                                         ha="center")
                        if pv_symb_2:
                            plt.annotate(pv_symb_2, xy=(x_pos_2, y_pos_2), fontsize=sign_symbol_opts['fontsize'],
                                         ha="center")
                        if pv_symb_3:
                            plt.annotate(pv_symb_3, xy=(x_pos_3, y_pos_3), fontsize=sign_symbol_opts['fontsize'],
                                         ha="center")
        sub_cat_i = 0
        if sub_cat:
            if isinstance(sub_cat, dict):
                for k in sub_cat:
                    if isinstance(k, tuple) and len(k) == 2:
                        cat_x_pos, cat_y_pos, cat_x_pos_2 = k[0], min_value - \
                                                            (sub_cat_opts[
                                                                 'y_neg_dist'] * size_factor_to_start_line), k[1]
                        plt.annotate('', xy=(cat_x_pos - (bw / 2), cat_y_pos),
                                     xytext=(cat_x_pos_2 + (bw / 2), cat_y_pos),
                                     arrowprops={'arrowstyle': '-', 'linewidth': 0.5}, annotation_clip=False)
                        if sub_cat_label_dist and isinstance(sub_cat_label_dist, list):
                            plt.annotate(sub_cat[k], xy=(np.mean([cat_x_pos, cat_x_pos_2]),
                                                         cat_y_pos - size_factor_to_start_line - sub_cat_label_dist[
                                                             sub_cat_i]),
                                         ha="center", fontsize=sub_cat_opts['fontsize'], annotation_clip=False,
                                         fontfamily=sub_cat_opts['fontname'])
                            sub_cat_i += 1
                        else:
                            plt.annotate(sub_cat[k], xy=(np.mean([cat_x_pos, cat_x_pos_2]),
                                                         cat_y_pos - size_factor_to_start_line),
                                         ha="center", fontsize=sub_cat_opts['fontsize'], annotation_clip=False,
                                         fontfamily=sub_cat_opts['fontname'])
                    else:
                        raise KeyError("Sub category keys must be tuple of size 2")

        general.get_figure(show, r, figtype, figname)

    # for data with replicates
    # deprecate dist_y_pos and dist_y_neg (repalce with  size_factor_to_start_line)
    @staticmethod
    def singlebar(df='dataframe', dim=(6, 4), bw=0.4, colorbar='#f2aa4cff', hbsize=4, r=300, ar=(0, 0), valphabar=1,
                  errorbar=True, show=False, ylm=None, axtickfontsize=9, axtickfontname='Arial', ax_x_ticklabel=None,
                  axlabelfontsize=9, axlabelfontname='Arial', yerrlw=None, yerrcw=None, axxlabel=None, axylabel=None,
                  figtype='png', add_sign_line=False, pv=None,
                  sign_line_opts={'symbol': '*', 'fontsize': 9, 'linewidth': 0.5, 'arrowstyle': '-', 'fontname':'Arial'},
                  sign_line_pvals=False,
                  add_sign_symbol=False, sign_symbol_opts={'symbol': '*', 'fontsize': 9, 'rotation':0, 'fontname':'Arial'},
                  sign_line_pairs=None, sub_cat=None, sub_cat_opts={'y_neg_dist': 3.5, 'fontsize': 9, 'fontname':'Arial'},
                  sub_cat_label_dist=None, symb_dist=None, group_let=None, df_format=None, samp_col_name=None,
                  col_order=False, dotplot=False, dotsize=6, colordot=['#101820ff'], valphadot=1, markerdot='o',
                  sign_line_pairs_dist=None, sign_line_pv_symb_dist=None, div_fact=20, add_text=None,
                  figname='singlebar', connectionstyle='bar, armA=50, armB=50, angle=180, fraction=0',
                  std_errs_vis='both', yerrzorder=8):
        plt.rcParams['mathtext.fontset'] = 'custom'
        plt.rcParams['mathtext.default'] = 'regular'
        plt.rcParams['mathtext.it'] = 'Arial:italic'
        plt.rcParams['mathtext.bf'] = 'Arial:italic:bold'

        # set axis labels to None
        _x = None
        _y = None
        if df_format == 'stack':
            # sample_list = df[samp_col_name].unique()
            if samp_col_name is None:
                raise ValueError('sample column name required')
            df_mean = df.groupby(samp_col_name).mean().reset_index().set_index(samp_col_name).T
            df_sem = df.groupby(samp_col_name).sem().reset_index().set_index(samp_col_name).T
            if col_order:
                df_mean = df_mean[df[samp_col_name].unique()]
                df_sem = df_sem[df[samp_col_name].unique()]
            bar_h = df_mean.iloc[0]
            bar_se = df_sem.iloc[0]
            sample_list = df_mean.columns.to_numpy()
            # get minimum from df
            min_value = (0, df_mean.iloc[0].min())[df_mean.iloc[0].min() < 0]
        else:
            bar_h = df.describe().loc['mean']
            bar_se = df.sem()
            bar_counts = df.describe().loc['count']
            sample_list = df.columns.to_numpy()
            min_value = (0, min(df.min()))[min(df.min()) < 0]

        if std_errs_vis == 'upper':
            std_errs_vis = [len(bar_se)*[0], bar_se]
        elif std_errs_vis == 'lower':
            std_errs_vis = [bar_se, len(bar_se)*[0]]
        elif std_errs_vis == 'both':
            std_errs_vis = bar_se
        else:
            raise ValueError('In valid value for the std_errs_vis')

        xbar = np.arange(len(sample_list))
        color_list_bar = colorbar
        plt.subplots(figsize=dim)
        if errorbar:
            plt.bar(x=xbar, height=bar_h, yerr=std_errs_vis, width=bw, color=color_list_bar,
                    capsize=hbsize, alpha=valphabar, zorder=5, error_kw={'elinewidth': yerrlw, 'capthick': yerrcw,
                                                                          'zorder': yerrzorder})
        else:
            plt.bar(x=xbar, height=bar_h, width=bw, color=color_list_bar, capsize=hbsize, alpha=valphabar)

        if ax_x_ticklabel:
            x_ticklabel = ax_x_ticklabel
        else:
            x_ticklabel = sample_list

        plt.xticks(ticks=xbar, labels=x_ticklabel, fontsize=axtickfontsize, rotation=ar[0], fontname=axtickfontname)
        if axxlabel:
            _x = axxlabel
        if axylabel:
            _y = axylabel
        general.axis_labels(_x, _y, axlabelfontsize, axlabelfontname)
        # ylm must be tuple of start, end, interval
        if ylm:
            plt.ylim(bottom=ylm[0], top=ylm[1])
            plt.yticks(np.arange(ylm[0], ylm[1], ylm[2]), fontsize=axtickfontsize, fontname=axtickfontname)
        plt.yticks(fontsize=axtickfontsize, rotation=ar[1], fontname=axtickfontname)

        color_list_dot = colordot
        if len(color_list_dot) == 1:
            color_list_dot = colordot * len(sample_list)
        # checked for unstacked data
        if dotplot:
            for cols in range(len(sample_list)):
                plt.scatter(
                    x=np.linspace(xbar[cols] - bw / 2, xbar[cols] + bw / 2, int(bar_counts[cols])),
                    y=df[df.columns[cols]].dropna(), s=dotsize, color=color_list_dot[cols], zorder=10, alpha=valphadot,
                    marker=markerdot)

        size_factor_to_start_line = max(bar_h) / div_fact
        # for only adjacent bars (not for multiple bars with single control)
        if add_sign_line:
            for i in xbar:
                if i % 2 != 0:
                    continue
                x_pos = xbar[i]
                x_pos_2 = xbar[i+1]
                y_pos = df.describe().loc['mean'].to_numpy()[i] + df.sem().to_numpy()[i]
                y_pos_2 = df.describe().loc['mean'].to_numpy()[i+1] + df.sem().to_numpy()[i+1]
                # only if y axis is positive; in future make a function to call it (2 times used)
                if y_pos > 0:
                    y_pos += size_factor_to_start_line
                    y_pos_2 += size_factor_to_start_line
                    pv_symb = general.pvalue_symbol(pv[int(i/2)], sign_line_opts['symbol'])
                    if pv_symb:
                        plt.annotate('', xy=(x_pos, max(y_pos, y_pos_2)), xytext=(x_pos_2, max(y_pos, y_pos_2)),
                                    arrowprops={'connectionstyle': connectionstyle,
                                                'arrowstyle': sign_line_opts['arrowstyle'],
                                                'linewidth': sign_line_opts['linewidth']})
                        plt.annotate(pv_symb, xy=(np.mean([x_pos, x_pos_2]), max(y_pos, y_pos_2) +
                                                 sign_line_opts['dist_y_pos']),
                                    fontsize=sign_line_opts['fontsize'], ha="center")

        # for only adjacent bars with one control but multiple treatments
        # need to work for sign_line_pairs (update df on line 1276)
        p_index = 0
        y_pos_dict = dict()
        y_pos_dict_trt = dict()
        if sign_line_pairs:
            for i in sign_line_pairs:
                y_pos_adj = 0
                x_pos = xbar[i[0]]
                x_pos_2 = xbar[i[1]]
                y_pos = df.describe().loc['mean'].to_numpy()[i[0]] + df.sem().to_numpy()[i[0]]
                y_pos_2 = df.describe().loc['mean'].to_numpy()[i[1]] + df.sem().to_numpy()[i[1]]
                # only if y axis is positive; in future make a function to call it (2 times used)
                if y_pos > 0:
                    y_pos += size_factor_to_start_line/2
                    y_pos_2 += size_factor_to_start_line/2
                    # check if the mean of y_pos is not lesser than not other treatments which lies between
                    # eg if 0-1 has higher sign bar than the 0-2
                    if i[0] in y_pos_dict_trt:
                        y_pos_adj = 1
                        if y_pos_2 <= y_pos_dict_trt[i[0]][1]:
                            if sign_line_pairs_dist:
                                y_pos_2 += (y_pos_dict_trt[i[0]][1] - y_pos_2) + (3 * size_factor_to_start_line) + \
                                       sign_line_pairs_dist[p_index]
                            else:
                                y_pos_2 += (y_pos_dict_trt[i[0]][1] - y_pos_2) + (3 * size_factor_to_start_line)
                        elif y_pos <= y_pos_dict_trt[i[0]][0]:
                            if sign_line_pairs_dist:
                                y_pos += 3 * size_factor_to_start_line + sign_line_pairs_dist[p_index]
                            else:
                                y_pos += 3 * size_factor_to_start_line
                    # check if difference is not equivalent between two y_pos
                    # if yes add some distance, so that sign bar will not overlap
                    if i[0] in y_pos_dict:
                        y_pos_adj = 1
                        if 0.75 < df.describe().loc['mean'].to_numpy()[i[0]]/df.describe().loc['mean'].to_numpy()[i[1]] < 1.25:
                            if sign_line_pairs_dist:
                                y_pos += 2 * size_factor_to_start_line + sign_line_pairs_dist[p_index]
                            else:
                                y_pos += 2 * size_factor_to_start_line

                    if y_pos_adj == 0 and sign_line_pairs_dist:
                        if y_pos >= y_pos_2:
                            y_pos += sign_line_pairs_dist[p_index]
                        else:
                            y_pos_2 += sign_line_pairs_dist[p_index]

                    # sign_line_pvals passed, used p values instead of symbols
                    if sign_line_pvals:
                        pv_symb = '$\it{p}$'+ str(pv[p_index])
                    else:
                        pv_symb = general.pvalue_symbol(pv[p_index], sign_line_opts['symbol'])
                    y_pos_dict[i[0]] = y_pos
                    y_pos_dict_trt[i[0]] = [y_pos, y_pos_2]
                    if pv_symb:
                        plt.annotate('', xy=(x_pos, max(y_pos, y_pos_2)), xytext=(x_pos_2, max(y_pos, y_pos_2)),
                                     arrowprops={'connectionstyle': connectionstyle,
                                                     'arrowstyle': sign_line_opts['arrowstyle'],
                                                     'linewidth': sign_line_opts['linewidth']})
                        # here size factor size_factor_to_start_line added instead of sign_line_opts['dist_y_pos']
                        # make this change everywhere in future release
                        plt.annotate(pv_symb, xy=(np.mean([x_pos, x_pos_2]), max(y_pos, y_pos_2) +
                                                  size_factor_to_start_line + sign_line_pv_symb_dist[p_index]),
                                     fontsize=sign_line_opts['fontsize'], ha="center")
                    p_index += 1

        if add_sign_symbol:
            for i in xbar:
                x_pos = xbar[i]
                # y_pos = df.describe().loc['mean'].to_numpy()[i] + df.sem().to_numpy()[i] + size_factor_to_start_line

                if symb_dist:
                    y_pos = bar_h.to_numpy()[i] + bar_se.to_numpy()[i] + \
                            size_factor_to_start_line + symb_dist[i]
                else:
                    y_pos = bar_h.to_numpy()[i] + bar_se.to_numpy()[i] + \
                            size_factor_to_start_line

                # group_let list
                if isinstance(group_let, list):
                    if y_pos > 0:
                        plt.annotate(group_let[i], xy=(x_pos, y_pos),
                                     fontsize=sign_symbol_opts['fontsize'], ha="center",
                                     rotation=sign_symbol_opts['rotation'], fontfamily=sign_symbol_opts['fontname'])

                # only if y axis is positive
                if pv:
                    if y_pos > 0:
                        pv_symb = general.pvalue_symbol(pv[i], sign_symbol_opts['symbol'])
                        if pv_symb:
                            plt.annotate(pv_symb, xy=(x_pos, y_pos), fontsize=sign_symbol_opts['fontsize'], ha="center",
                                         rotation=sign_symbol_opts['rotation'], fontfamily=sign_symbol_opts['fontname'])

        sub_cat_i = 0
        if sub_cat:
            if isinstance(sub_cat, dict):
                for k in sub_cat:
                    if isinstance(k, tuple) and len(k) == 2:
                        cat_x_pos, cat_y_pos, cat_x_pos_2 = k[0], min_value - \
                                                            (sub_cat_opts['y_neg_dist']*size_factor_to_start_line), k[1]
                        plt.annotate('', xy=(cat_x_pos-(bw/2), cat_y_pos), xytext=(cat_x_pos_2+(bw/2), cat_y_pos),
                                     arrowprops={'arrowstyle': '-', 'linewidth': 0.5}, annotation_clip=False)
                        if sub_cat_label_dist and isinstance(sub_cat_label_dist, list):
                            plt.annotate(sub_cat[k], xy=(np.mean([cat_x_pos, cat_x_pos_2]),
                                                         cat_y_pos - size_factor_to_start_line - sub_cat_label_dist[sub_cat_i]),
                                         ha="center", fontsize=sub_cat_opts['fontsize'], annotation_clip=False,
                                         fontfamily=sub_cat_opts['fontname'])
                            sub_cat_i += 1
                        else:
                            plt.annotate(sub_cat[k], xy=(np.mean([cat_x_pos, cat_x_pos_2]),
                                                     cat_y_pos-size_factor_to_start_line),
                                     ha="center", fontsize=sub_cat_opts['fontsize'], annotation_clip=False,
                                         fontfamily=sub_cat_opts['fontname'])
                    else:
                        raise KeyError("Sub category keys must be tuple of size 2")

        if isinstance(add_text, list):
            plt.text(add_text[0], add_text[1], add_text[2], fontsize=9, fontfamily='Arial')

        general.get_figure(show, r, figtype, figname)

    @staticmethod
    def normal_bar(df='dataframe', x_col_name=None, y_col_name=None, dim=(6, 4), bw=0.4, colorbar="#f2aa4cff", r=300,
                   ar=(0, 0), valphabar=1, show=False, ylm=None, axtickfontsize=9, axtickfontname='Arial',
                   ax_x_ticklabel=None, axlabelfontsize=9, axlabelfontname='Arial', axxlabel=None, axylabel=None,
                   figtype='png', figname='normal_bar'):
        # set axis labels to None
        _x = None
        _y = None
        xbar = np.arange(len(df[x_col_name]))
        plt.subplots(figsize=dim)
        plt.bar(x=xbar, height=df[y_col_name], width=bw, color=colorbar, alpha=valphabar)
        if ax_x_ticklabel:
            x_ticklabel = ax_x_ticklabel
        else:
            x_ticklabel = df[x_col_name].to_numpy()
        plt.xticks(ticks=xbar, labels=x_ticklabel, fontsize=axtickfontsize, rotation=ar[0], fontname=axtickfontname)
        if axxlabel:
            _x = axxlabel
        if axylabel:
            _y = axylabel
        general.axis_labels(_x, _y, axlabelfontsize, axlabelfontname)
        general.get_figure(show, r, figtype, figname)

    def boxplot_single_factor(df='dataframe', column_names=None, grid=False, ar=(0, 0), axtickfontsize=9,
                              axtickfontname='Arial', dim=(6, 4), show=False, figtype='png', figname='boxplot', r=300,
                              ylm=None, box_line_style='-', box_line_width=1, box_line_color='b', med_line_style='-',
                              med_line_width=1, med_line_color='g', whisk_line_color='b', cap_color='b',
                              add_sign_symbol=False, symb_dist=None, sign_symbol_opts={'symbol': '*', 'fontsize': 8 },
                              pv=None, notch=False, outliers=True, fill_box_color=True, dotplot=False, dotsize=6,
                              colordot=['#101820ff'], valphadot=1, markerdot='o'):
        plt.subplots()
        if column_names:
            xbar = column_names
        else:
            xbar = list(df.columns)
            # rot is x axis rotation
        other_args = {'grid': grid, 'rot': ar[0], 'fontsize': axtickfontsize, 'notch':notch, 'showfliers':outliers,
                      'figsize': dim, 'patch_artist': fill_box_color}
        color_args = {'medians': med_line_color, 'boxes': box_line_color, 'whiskers': whisk_line_color,
                      'caps': cap_color}
        medianprops_args = {'linestyle': med_line_style, 'linewidth': med_line_width}
        boxprops_args = {'linestyle': box_line_style, 'linewidth': box_line_width}

        if isinstance(column_names, list):
            df.boxplot(column=column_names, **other_args, boxprops=boxprops_args, medianprops=medianprops_args,
                       color=color_args)
        else:
            df.boxplot(**other_args, boxprops=boxprops_args, color=color_args, medianprops=medianprops_args)

        # ylm must be tuple of start, end, interval
        if ylm:
            plt.ylim(bottom=ylm[0], top=ylm[1])
            plt.yticks(np.arange(ylm[0], ylm[1], ylm[2]), fontsize=axtickfontsize, fontname=axtickfontname)
        plt.yticks(fontsize=axtickfontsize, rotation=ar[1], fontname=axtickfontname)

        color_list_dot = colordot
        if len(color_list_dot) == 1:
            color_list_dot = colordot * len(xbar)
        # checked for unstacked data
        if dotplot:
            for cols in range(len(xbar)):
                plt.scatter(
                    x=np.linspace(xbar[cols] - bw / 2, xbar[cols] + bw / 2, int(bar_counts[cols])),
                    y=df[df.columns[cols]].dropna(), s=dotsize, color=color_list_dot[cols], zorder=10, alpha=valphadot,
                    marker=markerdot)

        size_factor_to_start_line = max(df.max()) / 20
        if add_sign_symbol:
            # p and symb_dist should be dict
            if isinstance(pv, dict):
                for k, v in pv.items():
                    if isinstance(symb_dist, dict):
                        if k not in symb_dist:
                            symb_dist[k] = 0
                        y_pos = df[k].max() + size_factor_to_start_line + symb_dist[k]
                    else:
                        y_pos = df[k].max() + size_factor_to_start_line

                    if y_pos > 0 and v <= 0.05:
                        pv_symb = general.pvalue_symbol(v, sign_symbol_opts['symbol'])
                        if pv_symb:
                            plt.annotate(pv_symb, xy=((xbar.index(k))+1, y_pos),
                                         fontsize=sign_symbol_opts['fontsize'],
                                         ha="center")

        general.get_figure(show, r, figtype, figname)

    @staticmethod
    def roc(fpr=None, tpr=None, c_line_style='-', c_line_color='#f05f21', c_line_width=1, diag_line=True,
            diag_line_style='--', diag_line_width=1, diag_line_color='b', auc=None, shade_auc=False,
            shade_auc_color='#f48d60',
            axxlabel='False Positive Rate (1 - Specificity)', axylabel='True Positive Rate (Sensitivity)', ar=(0, 0),
            axtickfontsize=9, axtickfontname='Arial', axlabelfontsize=9, axlabelfontname='Arial',
            plotlegend=True, legendpos='lower right', legendanchor=None, legendcols=1, legendfontsize=8,
            legendlabelframe=False, legend_columnspacing=None, per_class=False, dim=(6, 5), show=False, figtype='png',
            figname='roc', r=300, ylm=None):
        plt.subplots(figsize=dim)
        # plt.margins(x=0)
        if auc:
            plt.plot(fpr, tpr, color=c_line_color, linestyle=c_line_style, linewidth=c_line_width,
                 label='AUC = %0.4f' % auc)
        else:
            plt.plot(fpr, tpr, color=c_line_color, linestyle=c_line_style, linewidth=c_line_width)
        if diag_line:
            plt.plot([0, 1], [0, 1], color=diag_line_color, linestyle=diag_line_style, linewidth=diag_line_width,
                     label='Chance level')
        if per_class:
            plt.plot([0, 0], [0, 1], color='grey', linestyle='-', linewidth=1)
            plt.plot([0, 1], [1, 1], color='grey', linestyle='-', linewidth=1, label='Perfect performance')
        # ylm must be tuple of start, end, interval
        if ylm:
            plt.ylim(bottom=ylm[0], top=ylm[1])
            plt.yticks(np.arange(ylm[0], ylm[1], ylm[2]), fontsize=axtickfontsize, fontname=axtickfontname)
        plt.yticks(fontsize=axtickfontsize, rotation=ar[1], fontname=axtickfontname)
        if axxlabel:
            _x = axxlabel
        if axylabel:
            _y = axylabel
        if shade_auc:
            plt.fill_between(x=fpr, y1=tpr, color=shade_auc_color)
        if plotlegend:
            plt.legend(loc=legendpos, bbox_to_anchor=legendanchor, ncol=legendcols, fontsize=legendfontsize,
                       frameon=legendlabelframe, columnspacing=legend_columnspacing)
        general.axis_labels(_x, _y, axlabelfontsize, axlabelfontname)
        general.get_figure(show, r, figtype, figname)


class cluster:
    def __init__(self):
        pass

    @staticmethod
    def screeplot(obj="pcascree", axlabelfontsize=9, axlabelfontname="Arial", axxlabel=None,
                axylabel=None, figtype='png', r=300, show=False, dim=(6, 4)):
        y = [x * 100 for x in obj[1]]
        plt.subplots(figsize=dim)
        plt.bar(obj[0], y)
        xlab='PCs'
        ylab='Proportion of variance (%)'
        if axxlabel:
            xlab = axxlabel
        if axylabel:
            ylab = axylabel
        plt.xticks(fontsize=7, rotation=70)
        general.axis_labels(xlab, ylab, axlabelfontsize, axlabelfontname)
        general.get_figure(show, r, figtype, 'screeplot')

    @staticmethod
    def pcaplot(x=None, y=None, z=None, labels=None, var1=None, var2=None, var3=None, axlabelfontsize=9,
                axlabelfontname="Arial", figtype='png', r=300, show=False, plotlabels=True, dim=(6, 4)):
        if x is not None and y is not None and z is None:
            assert var1 is not None and var2 is not None and labels is not None, "var1 or var2 variable or labels are missing"
            plt.subplots(figsize=dim)
            for i, varnames in enumerate(labels):
                plt.scatter(x[i], y[i])
                if plotlabels:
                    plt.text(x[i], y[i], varnames, fontsize=10)
            general.axis_labels("PC1 ({}%)".format(var1), "PC2 ({}%)".format(var2), axlabelfontsize, axlabelfontname)
            general.get_figure(show, r, figtype, 'pcaplot_2d')
        elif x is not None and y is not None and z is not None:
            assert var1 and var2 and var3 and labels is not None, "var1 or var2 or var3 or labels are missing"
            # for 3d plot
            fig = plt.figure(figsize=dim)
            ax = fig.add_subplot(111, projection='3d')
            for i, varnames in enumerate(labels):
                ax.scatter(x[i], y[i], z[i])
                if plotlabels:
                    ax.text(x[i], y[i], z[i], varnames, fontsize=10)
            ax.set_xlabel("PC1 ({}%)".format(var1), fontsize=axlabelfontsize, fontname=axlabelfontname)
            ax.set_ylabel("PC2 ({}%)".format(var2), fontsize=axlabelfontsize, fontname=axlabelfontname)
            ax.set_zlabel("PC3 ({}%)".format(var3), fontsize=axlabelfontsize, fontname=axlabelfontname)
            general.get_figure(show, r, figtype, 'pcaplot_3d')

    @staticmethod
    # adapted from https://stackoverflow.com/questions/39216897/plot-pca-loadings-and-loading-in-biplot-in-sklearn-like-rs-autoplot
    def biplot(cscore=None, loadings=None, labels=None, var1=None, var2=None, var3=None, axlabelfontsize=9, axlabelfontname="Arial",
               figtype='png', r=300, show=False, markerdot="o", dotsize=6, valphadot=1, colordot='#4a4e4d', arrowcolor='#fe8a71',
               valphaarrow=1, arrowlinestyle='-', arrowlinewidth=1.0, centerlines=True, colorlist=None, legendpos='best',
               datapoints=True, dim=(6, 4)):
        assert cscore is not None and loadings is not None and labels is not None and var1 is not None and var2 is not None, \
            "cscore or loadings or labels or var1 or var2 are missing"
        if var1 is not None and var2 is not None and var3 is None:
            xscale = 1.0 / (cscore[:, 0].max() - cscore[:, 0].min())
            yscale = 1.0 / (cscore[:, 1].max() - cscore[:, 1].min())
            # zscale = 1.0 / (cscore[:, 2].max() - cscore[:, 2].min())
            # colorlist is an array of classes from dataframe column
            plt.subplots(figsize=dim)
            if datapoints:
                if colorlist is not None:
                    unique_class = set(colorlist)
                    # color_dict = dict()
                    assign_values = {col: i for i, col in enumerate(unique_class)}
                    color_result_num = [assign_values[i] for i in colorlist]
                    if colordot and isinstance(colordot, (tuple, list)):
                        colour_map = ListedColormap(colordot)
                        # for i in range(len(list(unique_class))):
                        #    color_dict[list(unique_class)[i]] = colordot[i]
                        # color_result = [color_dict[i] for i in colorlist]
                        s = plt.scatter(cscore[:, 0] * xscale, cscore[:, 1] * yscale, c=color_result_num, cmap=colour_map,
                                        s=dotsize, alpha=valphadot, marker=markerdot)
                        plt.legend(handles=s.legend_elements()[0], labels=list(unique_class), loc=legendpos)
                    elif colordot and not isinstance(colordot, (tuple, list)):
                        # s = plt.scatter(cscore[:, 0] * xscale, cscore[:, 1] * yscale, color=color_result, s=dotsize,
                        #                alpha=valphadot, marker=markerdot)
                        # plt.legend(handles=s.legend_elements()[0], labels=list(unique_class))
                        s = plt.scatter(cscore[:, 0] * xscale, cscore[:, 1] * yscale, c=color_result, s=dotsize,
                                    alpha=valphadot, marker=markerdot)
                        plt.legend(handles=s.legend_elements()[0], labels=list(unique_class), loc=legendpos)
                else:
                    plt.scatter(cscore[:, 0] * xscale, cscore[:, 1] * yscale, color=colordot, s=dotsize,
                                    alpha=valphadot, marker=markerdot)
            if centerlines:
                plt.axhline(y=0, linestyle='--', color='#7d7d7d', linewidth=1)
                plt.axvline(x=0, linestyle='--', color='#7d7d7d', linewidth=1)
            for i in range(len(loadings)):
                plt.arrow(0, 0, loadings[0][i], loadings[1][i], color=arrowcolor, alpha=valphaarrow, ls=arrowlinestyle,
                          lw=arrowlinewidth)
                plt.text(loadings[0][i], loadings[1][i], labels[i])
                # adjust_text(t)
            # plt.xlim(min(loadings[0]) - 0.1, max(loadings[0]) + 0.1)
            # plt.ylim(min(loadings[1]) - 0.1, max(loadings[1]) + 0.1)
            xlimit_max = np.max([np.max(cscore[:, 0]*xscale), np.max(loadings[0])])
            xlimit_min = np.min([np.min(cscore[:, 0]*xscale), np.min(loadings[0])])
            ylimit_max = np.max([np.max(cscore[:, 1]*yscale), np.max(loadings[1])])
            ylimit_min = np.min([np.min(cscore[:, 1]*xscale), np.min(loadings[1])])
            plt.xlim(xlimit_min-0.2, xlimit_max+0.2)
            plt.ylim(ylimit_min-0.2, ylimit_max + 0.2)
            general.axis_labels("PC1 ({}%)".format(var1), "PC2 ({}%)".format(var2), axlabelfontsize, axlabelfontname)
            general.get_figure(show, r, figtype, 'biplot_2d')
        # 3D
        if var1 is not None and var2 is not None and var3 is not None:
            xscale = 1.0 / (cscore[:, 0].max() - cscore[:, 0].min())
            yscale = 1.0 / (cscore[:, 1].max() - cscore[:, 1].min())
            zscale = 1.0 / (cscore[:, 2].max() - cscore[:, 2].min())
            fig = plt.figure(figsize=dim)
            ax = fig.add_subplot(111, projection='3d')
            if datapoints:
                if colorlist is not None:
                    unique_class = set(colorlist)
                    assign_values = {col: i for i, col in enumerate(unique_class)}
                    color_result_num = [assign_values[i] for i in colorlist]
                    if colordot and isinstance(colordot, (tuple, list)):
                        colour_map = ListedColormap(colordot)
                        s = ax.scatter(cscore[:, 0]*xscale, cscore[:, 1]*yscale, cscore[:, 2]*zscale, c=color_result_num,
                                       cmap=colour_map, s=dotsize, alpha=valphadot, marker=markerdot)
                        plt.legend(handles=s.legend_elements()[0], labels=list(unique_class), loc=legendpos)
                    elif colordot and not isinstance(colordot, (tuple, list)):
                        s = plt.scatter(cscore[:, 0]*xscale, cscore[:, 1]*yscale, cscore[:, 2]*zscale, c=color_result_num,
                                        s=dotsize, alpha=valphadot, marker=markerdot)
                        plt.legend(handles=s.legend_elements()[0], labels=list(unique_class), loc=legendpos)
                else:
                    ax.scatter(cscore[:, 0] * xscale, cscore[:, 1] * yscale, cscore[:, 2] * zscale, color=colordot,
                               s=dotsize, alpha=valphadot, marker=markerdot)
            for i in range(len(loadings)):
                ax.quiver(0, 0, 0, loadings[0][i], loadings[1][i], loadings[2][i], color=arrowcolor, alpha=valphaarrow,
                          ls=arrowlinestyle, lw=arrowlinewidth)
                ax.text(loadings[0][i], loadings[1][i], loadings[2][i],  labels[i])

            xlimit_max = np.max([np.max(cscore[:, 0] * xscale), np.max(loadings[0])])
            xlimit_min = np.min([np.min(cscore[:, 0] * xscale), np.min(loadings[0])])
            ylimit_max = np.max([np.max(cscore[:, 1] * yscale), np.max(loadings[1])])
            ylimit_min = np.min([np.min(cscore[:, 1] * xscale), np.min(loadings[1])])
            zlimit_max = np.max([np.max(cscore[:, 2] * zscale), np.max(loadings[2])])
            zlimit_min = np.min([np.min(cscore[:, 2] * zscale), np.min(loadings[2])])
            # ax.set_xlim(min(loadings[0])-0.1, max(loadings[0])+0.1)
            # ax.set_ylim(min(loadings[1])-0.1, max(loadings[1])+0.1)
            # ax.set_zlim(min(loadings[2])-0.1, max(loadings[2])+0.1)
            ax.set_xlim(xlimit_min-0.2, xlimit_max+0.2)
            ax.set_ylim(ylimit_min-0.2, ylimit_max+0.2)
            ax.set_zlim(zlimit_min-0.2, zlimit_max+0.2)
            ax.set_xlabel("PC1 ({}%)".format(var1), fontsize=axlabelfontsize, fontname=axlabelfontname)
            ax.set_ylabel("PC2 ({}%)".format(var2), fontsize=axlabelfontsize, fontname=axlabelfontname)
            ax.set_zlabel("PC3 ({}%)".format(var3), fontsize=axlabelfontsize, fontname=axlabelfontname)
            general.get_figure(show, r, figtype, 'biplot_3d')

    def tsneplot(score=None, axlabelfontsize=9, axlabelfontname="Arial", figtype='png', r=300, show=False,
             markerdot="o", dotsize=6, valphadot=1, colordot='#4a4e4d', colorlist=None, legendpos='best',
             figname='tsne_2d', dim=(6, 4), legendanchor=None):
        assert score is not None, "score are missing"
        plt.subplots(figsize=dim)
        if colorlist is not None:
            unique_class = set(colorlist)
            # color_dict = dict()
            assign_values = {col: i for i, col in enumerate(unique_class)}
            color_result_num = [assign_values[i] for i in colorlist]
            if colordot and isinstance(colordot, (tuple, list)):
                colour_map = ListedColormap(colordot)
                s = plt.scatter(score[:, 0], score[:, 1], c=color_result_num, cmap=colour_map,
                                s=dotsize, alpha=valphadot, marker=markerdot)
                plt.legend(handles=s.legend_elements()[0], labels=list(unique_class), loc=legendpos,
                               bbox_to_anchor=legendanchor)
            elif colordot and not isinstance(colordot, (tuple, list)):
                s = plt.scatter(score[:, 0], score[:, 1], c=color_result_num,
                                s=dotsize, alpha=valphadot, marker=markerdot)
                plt.legend(handles=s.legend_elements()[0], labels=list(unique_class), loc=legendpos,
                           bbox_to_anchor=legendanchor)
        else:
            plt.scatter(score[:, 0], score[:, 1], color=colordot,
                       s=dotsize, alpha=valphadot, marker=markerdot)
        plt.xlabel("t-SNE-1", fontsize=axlabelfontsize, fontname=axlabelfontname)
        plt.ylabel("t-SNE-2", fontsize=axlabelfontsize, fontname=axlabelfontname)
        general.get_figure(show, r, figtype, figname)


