import os
import copy

import numpy as np
import pandas as pd

import scipy.stats
import scipy.signal
import scipy.cluster.hierarchy
from scipy.interpolate import UnivariateSpline

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patheffects as path_effects
from matplotlib import cm

import plotly.graph_objects

class VisualizationFunctions:

    '''Class of visualization functions for DigitalCellSorter'''

    def __init__(self, dataName='dataName', saveDir=os.path.join('')):

        self.saveDir = saveDir
        self.dataName = dataName

        return

    def makeHeatmapGeneExpressionPlot(self, df, nameToAppend=''):

        '''Make heatmap gene expression plot from a provided gene expression matrix.

        Parameters:
            df: pandas.DataFrame
                Gene expression matrix
    
            nameToAppend: str, Default ''
                String to append to fifure file name.

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.makeHeatmapGeneExpressionPlot()
        '''

        for i in range(df.shape[0]):
            df.iloc[i,:] -= np.min(df.iloc[i,:])
            df.iloc[i,:] /= np.max(df.iloc[i,:])

        df = df.T.iloc[scipy.cluster.hierarchy.dendrogram(scipy.cluster.hierarchy.linkage(df.T, 'ward'), no_plot=True, get_leaves=True)['leaves']].T

        fig, ax = plt.subplots(figsize=(8,8))

        ax.imshow(df.T.values, cmap='Blues', interpolation='None', aspect='auto')

        ax.set_xticks(range(df.shape[0]))
        ax.set_yticks(range(df.shape[1]))

        ylabels = ['(' + str(col[1]) + ')%s#'%('    ' if len(col[0])<=3 else '  ') + str(col[0]) for col in df.columns]

        ax.set_xticklabels(df.index, rotation=90, fontsize=10)
        ax.set_yticklabels(ylabels, rotation=0, fontsize=10)

        ax.set_xlim([-0.5, df.shape[0] - 0.5])
        ax.set_ylim([-0.5, df.shape[1] - 0.5])

        fig.tight_layout()

        if self.saveDir is not None: 
            fig.savefig(os.path.join(self.saveDir, self.dataName + '_' + nameToAppend + '_genes.png'), dpi=300)

        return
    
    def makeMarkerExpressionPlot(self):

        '''Produce image on marker genes and their expression on all clusters.
        Uses files generated by function DCS.Vote

        Parameters:
            None

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.MakeMarkerExpressionPlot()
        '''

        df_votingResults = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'), sheet_name='z-scores')
        votingResults = dict(zip(df_votingResults['cluster'].values, df_votingResults['Predicted cell type'].values))
        predictedCelltypes = dict(zip(df_votingResults['cluster'].values, df_votingResults['Predicted cell type'].str.split(' #', expand=True)[0]))
        supportingMarkersList = dict(zip(df_votingResults['cluster'].values, df_votingResults['Supporting markers'].str.split(' // ')))
        allMarkersList = dict(zip(df_votingResults['cluster'].values, df_votingResults['All markers'].str.split(' // ')))
        df_markers_cluster_centroids = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'), sheet_name='Cluster centroids', index_col=0, header=0).T

        df_markers = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'), sheet_name='Marker cell type weight matrix', index_col=0)
        df_markers[df_markers>=0.] = np.nan
        df_markers[df_markers<0.] = -1.

        # Y_mc.T
        X_markers_cluster_means_transpose = df_markers_cluster_centroids.values.T

        # Normalization
        for i in range(X_markers_cluster_means_transpose.shape[1]):
            X_markers_cluster_means_transpose[:,i] -= np.min(X_markers_cluster_means_transpose[:,i])
            X_markers_cluster_means_transpose[:,i] /= np.max(X_markers_cluster_means_transpose[:,i])

        ORDER = scipy.cluster.hierarchy.dendrogram(scipy.cluster.hierarchy.linkage(X_markers_cluster_means_transpose, 'ward'), no_plot=True, get_leaves=True)['leaves']
        ORDER2 = scipy.cluster.hierarchy.dendrogram(scipy.cluster.hierarchy.linkage(X_markers_cluster_means_transpose.T, 'ward'), no_plot=True, get_leaves=True)['leaves']

        X_markers_cluster_means_sorted = X_markers_cluster_means_transpose[ORDER,:][:,ORDER2]

        df_all_marker_hits = pd.DataFrame(data=np.zeros((df_markers_cluster_centroids.shape)), index=df_markers_cluster_centroids.index, columns=df_markers_cluster_centroids.columns)
        for cluster in allMarkersList:
            if not allMarkersList[cluster] is np.nan:
                for gene in allMarkersList[cluster]:
                    df_all_marker_hits.loc[gene, cluster] = 1

        df_supp_marker_hits = pd.DataFrame(data=np.zeros((df_markers_cluster_centroids.shape)), index=df_markers_cluster_centroids.index, columns=df_markers_cluster_centroids.columns)
        for cluster in supportingMarkersList:
            if not supportingMarkersList[cluster] is np.nan:
                for gene in supportingMarkersList[cluster]:
                    df_supp_marker_hits.loc[gene, cluster] = 1

        df_neg_supp_marker_hits = pd.DataFrame(data=np.zeros((df_markers_cluster_centroids.shape)), index=df_markers_cluster_centroids.index, columns=df_markers_cluster_centroids.columns)
        for cluster in supportingMarkersList:
            if not supportingMarkersList[cluster] is np.nan:
                for gene in allMarkersList[cluster]:
                #for gene in df_markers.columns:
                    if df_markers.loc[predictedCelltypes[cluster], gene]==-1.:
                        df_neg_supp_marker_hits.loc[gene, cluster] = 1

        X_marker_hits = df_all_marker_hits.values.T[ORDER,:][:,ORDER2]
        X_supp_marker_hits = df_supp_marker_hits.values.T[ORDER,:][:,ORDER2]
        X_neg_supp_marker_hits = df_neg_supp_marker_hits.values.T[ORDER,:][:,ORDER2]

        _figsize = np.float_(X_markers_cluster_means_transpose.shape[::-1]) / \
                    np.max(X_markers_cluster_means_transpose.shape) * 15.0 + 2.0

        _figsize[1] *= 1.5

        gs = matplotlib.gridspec.GridSpec(1, 2, width_ratios=[20,1], left=0.13, right=0.99, top=0.99, bottom=0.25, wspace=0.01, hspace=0.01)

        fig = plt.figure(figsize=_figsize)
        ax = plt.subplot(gs[0])

        ax.imshow(X_markers_cluster_means_sorted,cmap='Blues',interpolation='None', aspect='auto')

        i_list,j_list = np.where(X_marker_hits.T > 0)
        color = 'w' #(1., 1., 0.7)
        ax.plot(i_list, j_list, 'k*', mec=color, mew=0.5, markersize=4)

        i_list_supp,j_list_supp = np.where(X_supp_marker_hits.T > 0)
        i_list_neg_supp,j_list_neg_supp = np.where(X_neg_supp_marker_hits.T > 0)
        ax.plot(i_list_supp, j_list_supp, 'k*', mec='lime', mew=0.7, markersize=4) #mec='k', alpha=0.5, markersize=6
        ax.plot(i_list_neg_supp, j_list_neg_supp, 'k*', mec='red', mew=0.7, markersize=4) #mec='k', alpha=0.5, markersize=6

        ax.set_xticks(range(X_markers_cluster_means_transpose.shape[1]))
        ax.set_yticks(range(X_markers_cluster_means_transpose.shape[0]))

        xtickslabels = np.array(df_markers_cluster_centroids.index[ORDER2])
        for i in range(0,len(xtickslabels),2):
            xtickslabels[i] += " ─────────"

        ax.set_xticklabels(xtickslabels,rotation=90, fontsize=5)

        clusterNames = list(votingResults.values())
        clusterIndices = list(votingResults.keys())

        ax.set_yticklabels([str(clusterNames[i]) + ' (' + str(clusterIndices[i]) + ')' for i in ORDER], rotation=0, fontsize=8)
        #ax.set_yticklabels(['Cluster '+str(i) for i in ORDER], rotation=45,
        #fontsize=10)

        ax.set_xlim([-0.5,X_markers_cluster_means_transpose.shape[1] - 0.5])
        ax.set_ylim([-0.5,X_markers_cluster_means_transpose.shape[0] - 0.5])



        axx = plt.subplot(gs[1])
        fontsize = 5
        cells_in_clusters = df_votingResults['# cells in cluster'].values.copy()[ORDER]
        numberOfCells = cells_in_clusters.sum()

        with open(os.path.join(self.saveDir, 'ColormapForCellTypes.txt'), 'r') as temp_file:
            colormap = {item.strip().split('\t')[0]:eval(item.strip().split('\t')[1]) for item in temp_file.readlines()}
        celltypes = df_votingResults['Predicted cell type'].str.split(' #', expand=True)[0].values.copy()[ORDER]

        axx.barh(y=range(len(cells_in_clusters)), width=cells_in_clusters, height=0.8, align='center', color=[colormap[i] for i in celltypes])
        for i in range(len(cells_in_clusters)):
            axx.text(np.max(cells_in_clusters), i, cells_in_clusters[i], ha='right',va='top', color='k', weight='bold', fontsize=fontsize)
            axx.text(0.02 * numberOfCells, i, str(round(100 * cells_in_clusters[i] / numberOfCells, 1)) + '%', ha='left',va='bottom', color='b', fontsize=fontsize)
        axx.set_xticklabels(cells_in_clusters, fontsize=fontsize)
        axx.set_yticklabels(cells_in_clusters, alpha=0)
        axx.set_xticklabels(cells_in_clusters, alpha=0)
        axx.set_xticks([])
        axx.set_yticks([])
        axx.set_xlabel('Number of\ncells in clusters', fontsize=fontsize)
        axx.set_ylim(-0.5, len(cells_in_clusters) - 0.5)

        if self.saveDir is not None: 
            fig.savefig(os.path.join(self.saveDir, self.dataName + '_voting.png'), dpi=600)

        return

    def internalMarkerSubplots(self, df, X_projection, hugo_cd_dict, NoFrameOnFigures=False, HideClusterLabels=False, outlineClusters=True, analyzeBy='cluster'):

        '''Produce subplots on each marker and its expression on all clusters

        Parameters:
            df: pandas.DataFrame 
                Data with marker genes expression

            X_projection: 2d numpy.array
                2D coordinates for each cell

            hugo_cd_dict: dictionary 
                With aliases for hugo names of genes

            NoFrameOnFigures: boolean, Default False
                Whether to include frame on the figure

            HideClusterLabels: boolean, Default False
                Whether to print cluster labels on the figure

            outlineClusters: boolean, Default True
                Whether to outline the clusters with circles

            analyzeBy: str, Default 'cluster'
                What level of lablels to include.
                Other possible option is 'label'

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.MakeMarkerSubplot(df_markers_expr, projection, hugo_cd_dict)
        '''
        
        def MarkerSubplot(counter, marker, df, votingResults, X_projection, cellClusterIndexLabel, hugo_cd_dict, dataName, saveDir, NoFrameOnFigures, HideClusterLabels, XLIM, YLIM, directory, circles):

            fig,ax = plt.subplots(figsize=(8,8))

            ax.cla()
            suffix = '(' + str(hugo_cd_dict[marker]).replace('\"', '').replace('\'', '').replace('(', '').replace(')', '').replace(' ','') + ')'
            ax.plot(np.nan,np.nan,'*',markersize=15,c=cm.seismic(1.0),label=marker + '\n' + suffix.replace(',','\n'))
            circleIndices = np.where(df.loc[marker].values == 0)[0] # cells that don't have this marker
            starIndices = np.where(df.loc[marker].values > 0)[0] # cells that have this marker
            starIndices = starIndices[np.argsort(df.loc[marker].values[starIndices])]
            args1 = [X_projection[0,circleIndices],
                        X_projection[1,circleIndices]]
            kwargs1 = {'marker':'o',
                        'c':'b',
                        'alpha':0.1,
                        's':6 * 3,
                        'linewidth':0,}
            args2 = [X_projection[0,starIndices],
                        X_projection[1,starIndices]]
            kwargs2 = {'marker':'*',
                        'c':cm.seismic(df.loc[marker].values[starIndices] / np.max(df.loc[marker].values[starIndices])),
                        's':30 * 4,
                        'linewidth':0.0,}
            ax.scatter(*args1,**kwargs1)
            ax.scatter(*args2,**kwargs2)
            for label in set(cellClusterIndexLabel):
                # cells with this label
                X_projection2_cluster = X_projection[:,cellClusterIndexLabel == label]
                x_mean = np.mean(X_projection2_cluster[0,:])
                y_mean = np.mean(X_projection2_cluster[1,:])

                _text_label = votingResults[label] if not HideClusterLabels else ''

                ax.text(x_mean,y_mean,
                        _text_label.
                            replace('-','-\n').replace(' ','\n').
                            replace('T\n','T ').replace('B\n','B ').
                            replace('\n#',' #').replace('/','/\n').
                            replace('NK\n','NK ').replace('Stem\n','Stem '),
                        fontsize=10,
                        ha='center',va='center',#alpha=0.75,
                        ).set_path_effects([path_effects.Stroke(linewidth=3, foreground='white'),path_effects.Normal()])

                if circles:
                    radius = np.sqrt(X_projection2_cluster.shape[1]) * 300.0
                    ax.scatter(x_mean,y_mean,s=radius * 1,facecolors='none',edgecolors='k')
            ax.set_xlim(XLIM)
            ax.set_ylim(YLIM)
            ax.legend(loc='upper right', frameon=False, fontsize=14) #loc='best',numpoints=1,fontsize=12
            ax.set_xticks([])
            ax.set_yticks([]) 
            if NoFrameOnFigures:
                #fig.patch.set_visible(False)
                ax.axis('off')
            fig.tight_layout()
            if self.saveDir is not None: 
                #fig.savefig('%s/marker_subplots/%s_%s_%s.png' %
                #(self.saveDir,self.dataName,marker,suffix.replace(',','_').replace('/','_')),dpi=150)
                #fig.savefig(os.path.join(self.saveDir, 'marker_subplots', '%s_%s_%s.pdf' % (self.dataName,marker,suffix.replace(',','_').replace('/','_'))))
                fig.savefig(os.path.join(self.saveDir, 'marker_subplots', '%s_%s_%s.png' % (self.dataName,marker,suffix.replace(',','_').replace('/','_'))), dpi=300)
                print(marker, end=" ", flush=True)

            plt.close(fig)

            return
        
        maxs = np.max(X_projection,axis=1)
        mins = np.min(X_projection,axis=1)
        maxDiffs = maxs - mins
        deltas = maxDiffs * 0.05
        XLIM = [mins[0] - deltas[0],maxs[0] + deltas[0]]
        YLIM = [mins[1] - deltas[1],maxs[1] + deltas[1]]

        directory = os.path.join(self.saveDir, 'marker_subplots', '')
        if not os.path.exists(directory):
            os.makedirs(directory)

        if len(df.index)>1:
            print('\nSaving marker expression plots:\n')
        else:
            print('Saving expression plot of:', end=' ', flush=True)

        votingAnalyzeBy = 'Predicted cell type' if analyzeBy=='label' else analyzeBy

        df_votingResults = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'), sheet_name='z-scores')
        votingResults = dict(zip(df_votingResults[votingAnalyzeBy].values, df_votingResults['Predicted cell type'].values))

        cellClusterIndexLabel = df.columns.get_level_values(analyzeBy).values

        for counter,marker in enumerate(df.index.values):
            MarkerSubplot(counter, marker, pd.DataFrame(data=np.reshape(np.array(df.loc[marker]), (1,len(df.loc[marker]))), columns=df.columns, index=[marker]), 
                            votingResults, X_projection, cellClusterIndexLabel, hugo_cd_dict, self.dataName, self.saveDir, NoFrameOnFigures, HideClusterLabels, XLIM, YLIM, directory, outlineClusters)
        print()

        return

    def makeVotingResultsMatrixPlot(self):

        '''Produce voting results voting matrix plot

        Parameters:
            None

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.MakeVotingResultsMatrixPlot()
        '''

        df_votingResults = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'), sheet_name='z-scores')

        cellTypes = sorted([x for x in df_votingResults.columns.values.tolist() if x not in ['cluster', 'Predicted cell type', '# cells in cluster', 'Winning score', 'Supporting markers', 'All markers']])

        #df_votingResults['order'] =
        #scipy.cluster.hierarchy.dendrogram(scipy.cluster.hierarchy.linkage(df_votingResults[cellTypes].values,
        #method='ward', metric='euclidean', optimal_ordering=True),
        #                                                               no_plot=True,get_leaves=True)['leaves']

        df_votingResults['order'] = np.argsort(np.argsort(df_votingResults['Predicted cell type']))
        df_votingResults = df_votingResults.sort_values(by='order', axis=0, ascending=False)

        numberOfCells = np.sum(df_votingResults['# cells in cluster'])
        num_of_cell_types = len(cellTypes)
        num_of_clusters = np.unique(df_votingResults['cluster']).shape[0]

        indicis_of_clusters = df_votingResults['cluster']
        assigned_names_of_clusters = df_votingResults['Predicted cell type']
        
        label_max = 0.5 * max([len(assigned_names_of_clusters[i]) for i in range(len(assigned_names_of_clusters))])

        _figsize = np.float_((num_of_cell_types,num_of_clusters)) / np.max(num_of_clusters) * 15.0
        _figsize[0] += 1.0 + label_max
        _figsize[1] += 2.0

        gs = matplotlib.gridspec.GridSpec(1, 2, width_ratios=[3,1])

        fig = plt.figure(figsize=_figsize)
        
        ax = fig.add_axes([0.15, 0.1, 0.65, 0.85])
        axx = fig.add_axes([0.76, 0.1, 0.19, 0.85])

        #ax = plt.subplot(gs[0])
        #axx = plt.subplot(gs[1])

        zscores = df_votingResults[cellTypes].values
        ax.imshow(zscores, aspect=1, cmap='Greens', vmin=0, vmax=0.5, interpolation='None')

        for i in range(num_of_clusters):
            for j in range(num_of_cell_types):
                if np.round(zscores[i,j],1) > 0:
                    if zscores[i,j] == np.max(zscores[i,:]):
                        ax.text(j,i,np.round(zscores[i,j],1), color='w', fontsize=125*4 / max([num_of_cell_types, num_of_clusters]),ha='center',va='center').set_path_effects([path_effects.Stroke(linewidth=4, foreground='red'),path_effects.Normal()])
                    else:
                        ax.text(j,i,np.round(zscores[i,j],1), color='w', fontsize=125*3 / max([num_of_cell_types, num_of_clusters]),ha='center',va='center').set_path_effects([path_effects.Stroke(linewidth=2, foreground='black'),path_effects.Normal()])

        ax.set_xticks(range(num_of_cell_types))
        ax.set_yticks(range(num_of_clusters))

        ytickslabels = copy.deepcopy(assigned_names_of_clusters)
        for i in range(len(ytickslabels)):
            ytickslabels[i] = str(assigned_names_of_clusters[i]) + ' (' + str(indicis_of_clusters[i]) + ')'

        xtickslabels = np.array(copy.deepcopy(cellTypes))
        for i in range(len(xtickslabels)):
            if i % 3 == 1: xtickslabels[i] = '\n' + xtickslabels[i]
            if i % 3 == 2: xtickslabels[i] = '\n\n' + xtickslabels[i]

        ax.set_xticklabels(xtickslabels, rotation=0, fontsize=20, ha='center')
        ax.set_yticklabels(ytickslabels, fontsize=20, rotation=0) 
        ax.set_xlim([-0.5,num_of_cell_types - 0.5])
        ax.set_ylim([-0.5,num_of_clusters - 0.5])

        cells_in_clusters = df_votingResults['# cells in cluster'].values

        with open(os.path.join(self.saveDir, 'ColormapForCellTypes.txt'), 'r') as temp_file:
            colormap = {item.strip().split('\t')[0]:eval(item.strip().split('\t')[1]) for item in temp_file.readlines()}
        celltypes = df_votingResults['Predicted cell type'].str.split(' #', expand=True)[0].values.copy()

        axx.barh(y=range(len(cells_in_clusters)), width=cells_in_clusters, height=0.8, align='center', color=[colormap[i] for i in celltypes])
        for i in range(len(cells_in_clusters)):
            axx.text(np.max(cells_in_clusters), i, cells_in_clusters[i], ha='right',va='top', color='k', weight='bold', fontsize = 20)
            axx.text(0.02 * numberOfCells, i, str(round(100 * cells_in_clusters[i] / numberOfCells, 1)) + '%', ha='left',va='bottom', color='b', fontsize = 20)
        axx.set_xticklabels(cells_in_clusters, fontsize=20)
        axx.set_yticklabels(cells_in_clusters, alpha=0)
        axx.set_xticklabels(cells_in_clusters, alpha=0)
        axx.set_xlabel('Number of\ncells in clusters', fontsize=20)
        axx.set_ylim(-0.5, num_of_clusters - 0.5)
        
        #fig.tight_layout()
        fig.savefig(os.path.join(self.saveDir, self.dataName + '_matrix_voting.png'), dpi=150)
 
        return 

    def makeHistogramNullDistributionPlot(self):

        '''Produce histogram plot of the voting null distributions

        Parameters:
            None

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.MakeHistogramNullDistributionPlot()
        '''

        df_noise_dict = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'), sheet_name='Null distributions', index_col=0, header=[0,1], skiprows=[2])
        df_votingResultsV = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'), sheet_name='Voting scores', dtype={'cluster':str}).reset_index().set_index('cluster')
        df_votingResultsZ = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'), sheet_name='z-scores', dtype={'cluster':str}).reset_index().set_index('cluster')

        predicted_cell_type_cluster = df_votingResultsZ['Predicted cell type'].values
        predicted_cell_type = df_votingResultsZ['Predicted cell type'].str.split(' #', expand=True)[0].values

        cellTypes = sorted([x for x in df_votingResultsV.columns.values.tolist() if x not in ['cluster', 'Predicted cell type', '# cells in cluster', 'Winning score', 'Supporting markers', 'All markers']])

        df_votingResultsV = df_votingResultsV[cellTypes]
        df_votingResultsZ = df_votingResultsZ[cellTypes]

        cell_types = np.unique(df_noise_dict.columns.get_level_values(0).values)
        num_of_cell_types = cell_types.shape[0]
        clusters = np.unique(df_noise_dict.columns.get_level_values(1).values).astype(str)
        num_of_clusters = clusters.shape[0]

        maxy = np.round(np.nanmax(df_noise_dict.values), 2)

        with open(os.path.join(self.saveDir, 'ColormapForCellTypes.txt'), 'r') as temp_file:
            colormap = {item.strip().split('\t')[0]:eval(item.strip().split('\t')[1]) for item in temp_file.readlines()}

        origWidth = matplotlib.rcParams['axes.linewidth']
        matplotlib.rcParams['axes.linewidth'] = 0.1

        gs = matplotlib.gridspec.GridSpec(num_of_clusters, num_of_cell_types, hspace=0.45, wspace=0.1, bottom=0.04, top=0.96, left=0.05, right=0.99)
        
        fig = plt.figure(figsize=(num_of_cell_types, num_of_clusters * 0.4))

        for i in range(num_of_cell_types):

            try:
                minx, maxx = np.round(df_noise_dict.index.values[np.where(df_noise_dict.xs(key=cell_types[i], level=0, axis=1).values.sum(axis=1)!=0.)[0][[0,-1]]], 3)
                #maxx += 0.1
                maxx = np.round(maxx, 3)
            except:
                minx = np.round(df_noise_dict.index.values[0], 3)
                maxx = np.round(df_noise_dict.index.values[-1], 3)

            print(cell_types[i], end=':\t')

            for j in range(num_of_clusters):

                print(j, end=',', flush=True)

                fontsize = 3

                ax = plt.subplot(gs[i + num_of_cell_types * j])

                ax.bar(df_noise_dict.index.values, df_noise_dict[(cell_types[i], clusters[j])].values, 
                       width=df_noise_dict.index.values[2]-df_noise_dict.index.values[1], align='center', color=colormap[cell_types[i]])

                valueV = df_votingResultsV.loc[clusters[j], cell_types[i]]
                valueZ = df_votingResultsZ.loc[clusters[j], cell_types[i]]
                ax.axvline(x=valueV, ymin=0, ymax=1, color='k', lw=0.2)
                color = 'k' if predicted_cell_type[j] != cell_types[i] else 'r'
                xloc = 0.02*minx + minx #valueV
                ax.text(xloc, maxy - 0.02*maxy, r'$V_{%s,%s}=$' % (i,j) + str(np.round(valueV,2)), fontsize=fontsize, va='top', ha='left', color=color, zorder=np.inf)
                ax.text(xloc, maxy - 0.2*maxy, r'$\Lambda_{%s,%s}=$' % (i,j) + str(np.round(valueZ,2)), fontsize=fontsize, va='top', ha='left', color=color, zorder=np.inf)

                if j == 0:
                    ax.set_title(cell_types[i], fontdict={'color': 'b', 'size':'6'})

                if i == 0:
                    ax.text(-0.27 * maxx, maxy + 0.05*maxy, predicted_cell_type_cluster[j] + ' (Cluster %s)' % j, rotation=0, 
                            fontsize=fontsize, weight='bold', va='bottom', ha='left', color='k')
                    
                    ax.set_ylabel('Probability', fontsize=fontsize)
                    ax.set_yticklabels([0., maxy], fontsize=fontsize)
                else:
                    ax.set_yticklabels([], fontsize=fontsize)

                if j == num_of_clusters - 1:
                    #ax.set_xlabel('Voting score', fontsize=fontsize)
                    ax.set_xticklabels([minx, maxx], fontsize=fontsize)

                    if maxx>0.0 and minx<0.0:
                        ax.text(0.0, -0.2*maxy, '0.0', fontsize=fontsize, va='top', ha='center', color='k')
                else:
                    ax.set_xticklabels([], fontsize=fontsize)

                ax.set_xticks([minx, maxx])
                ax.set_yticks([0., maxy])

                ax.set_xlim(minx, maxx)
                ax.set_ylim(0., maxy)

                ax.tick_params(direction='in', length=1, width=0.1, colors='k')

            print()
        
        fig.savefig(os.path.join(self.saveDir, self.dataName + '_null_distributions.png'), dpi=1200)

        matplotlib.rcParams['axes.linewidth'] = origWidth

        return

    def makeProjectionPlot(self, Xprojection, cellClusterIndexLabel, suffix, colormap=cm.jet, legend=True, labels=True, colorbar=False, fontsize=10, plotNaNs=True, rightShift=0.3):

        '''Produce projection plot (2D layout) with a specified coloring scheme

        Parameters:
            Xprojection: 2D coordinates for each cell

            cellClusterIndexLabel: cluster index for each cell

            suffix: str
                Text label to append to the figure name

            colormap: cell coloring sequence, can be a dictionary or cm.colormap, 
                Default matplotlib.colors.LinearSegmentedColormap.jet

            legend: boolean, Default True
                Whether to print legend

            labels: boolean, Default True
                Whether to print labels

            colorbar: boolean, Default False
                Whether to show colorbar
                Use with non-numerical values will raise an error

            fontsize: int, Default 10
                Labels and legend font size

            plotNaNs: boolean, Default True
                Whether to plot NaN labels (in grey)
                
            rightShift: float, Default 0.3
                Fraction of space to leave on the right-hand side of the plot.
                This parameter is useful for adjusting legend overlap with data points.

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.makeProjectionPlot(projection, cellClusterIndexLabel, suffix)
        '''

        def add_colorbar(fig, labels, cmap=matplotlib.colors.LinearSegmentedColormap.from_list('GR', [(0, 1, 0), (1, 0, 0)], N=100), fontsize=10):
    
            mapp=cm.ScalarMappable(norm=matplotlib.colors.Normalize(vmin=np.min(labels), vmax=np.max(labels)), cmap=cmap)
            mapp.set_array(labels)
            sp = np.linspace(np.max(labels), np.min(labels), num=6, endpoint=True)

            axisColor = fig.add_axes([0.9,0.5,0.01,0.4])
            fig.colorbar(mapp, cax=axisColor, ticks=sp)

            axisColor.tick_params(labelsize=fontsize)
            axisColor.set_yticklabels(np.round(sp,2))
    
            return None

        fig = plt.figure(figsize=(8,8))
        ax = fig.add_axes([0.05,0.05,0.9,0.9])

        maxs, mins = np.max(Xprojection,axis=1), np.min(Xprojection,axis=1)

        missing = np.where(cellClusterIndexLabel!=cellClusterIndexLabel)[0]
        if len(missing)>0:
            ax.plot(Xprojection[0, missing], Xprojection[1, missing], 'o', color='grey', mew=0.5, alpha=0.2, markeredgecolor='k', label='NaN')

        nonMissing = np.where(cellClusterIndexLabel==cellClusterIndexLabel)[0]
        cellClusterIndexLabel = np.array(cellClusterIndexLabel)[nonMissing]
        Xprojection = Xprojection[:, nonMissing]

        possible_cluster_labels = np.sort(np.unique(cellClusterIndexLabel))

        if labels:
            print(possible_cluster_labels)

        for ilabel, label in enumerate(possible_cluster_labels):
            color = colormap(ilabel / len(possible_cluster_labels)) if type(colormap) is matplotlib.colors.LinearSegmentedColormap else colormap[label.split(' #')[0]]

            XprojectionC = Xprojection[:,cellClusterIndexLabel == label]

            ax.plot(XprojectionC[0,:], XprojectionC[1,:], 'o', color=color, mew=0.5, alpha=0.3, markeredgecolor='k', label=label)

            if labels:
                ax.text(np.mean(XprojectionC[0,:]), np.mean(XprojectionC[1,:]), 
                    label, fontsize=fontsize, ha='center',va='center').set_path_effects([path_effects.Stroke(linewidth=3, foreground='white'), path_effects.Normal()])

        ax.set_xticks([])
        ax.set_yticks([])
        
        ax.set_xlim([mins[0] - (maxs[0] - mins[0]) * 0.05, (1 + rightShift)*(maxs[0] + (maxs[0] - mins[0]) * 0.05)])
        ax.set_ylim([mins[1] - (maxs[1] - mins[1]) * 0.05, maxs[1] + (maxs[1] - mins[1]) * 0.05])

        if legend:
            plt.legend(loc='lower right', frameon=False, fontsize=fontsize)

        #fig.patch.set_visible(False)
        ax.axis('off')

        if colorbar:
            add_colorbar(fig, possible_cluster_labels, cmap=colormap, fontsize=fontsize)

        if self.saveDir is not None: 
            fig.savefig(os.path.join(self.saveDir, '%s_clusters_%s.png' % (self.dataName, suffix)), dpi=300)

        return

    def makeStackedBarplot(self, clusterName):
        
        '''Produce stacked barplot with cell fractions

        Parameters:
            clusterName: str
                Label to include at the bar bottom

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.make_stacked_bar_plot_subclustering(clusterName)
        '''

        def get_stacked_data_and_colors(saveDir):
            with open(os.path.join(saveDir, 'ColormapForCellTypes.txt'), 'r') as temp_file:
                colors = temp_file.readlines()
                colors = np.vstack([(color.strip('\n').split('\t')) for color in colors])
                colors = pd.DataFrame(colors.T[1], index=colors.T[0]).apply(lambda x: tuple(np.float_(x[0][1:][:-1].split(','))), axis=1)

            df = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'))
            index = df['Predicted cell type']

            if not clusterName is None:
                barName = self.dataName + ': ' + clusterName
            else:
                barName = self.dataName

            index = [index[i][:len(index[i]) if index[i].find('#') - 1 == -2 else index[i].find('#') - 1].strip('*').strip('#').strip(' ') for i in range(len(index))]
            df_BM_temp = pd.DataFrame(data=df['# cells in cluster'].values, index=index, columns=[barName])
        
            df_main = pd.DataFrame(data=np.zeros((len(colors),1)), index=colors.index, columns=[barName])

            for i, item in enumerate(df_BM_temp.index):
                df_main.loc[item,barName] += df_BM_temp.iloc[i][barName] if df_BM_temp.index[i] == item else 0

            s = 'sums'
            df_main[s] = np.array(np.sum(df_main, axis=1))
            df_main.loc['Unknown',s] = 0
            df_main = df_main.apply(lambda x: 100. * x / np.sum(df_main, axis=0), axis=1).loc[np.sum(df_main, axis=1) > 0].sort_values(by=[s]).drop(columns=[s])

            return df_main, colors, clusterName

        df_Main, colors, clusterName = get_stacked_data_and_colors(self.saveDir)

        saveName = os.path.join(self.saveDir, "%s_subclustering_stacked_barplot_%s.png" % (self.dataName, ('All cell clusters' if clusterName == None else clusterName).replace(' ', '_').replace('*', '')))

        fig,ax = plt.subplots(figsize=(4.5,8)) #4.15

        barWidth = 0.85
        cellTypes = df_Main.index
        bottom = np.zeros((len(df_Main.columns)))

        for i in range(len(cellTypes)):
            bottom += df_Main.loc[cellTypes[i - 1]] if i > 0 else 0
            ax.bar(range(len(df_Main.columns)), list(df_Main.loc[cellTypes[i]]), bottom=list(bottom), color=colors.loc[cellTypes[i]], edgecolor='white', width=barWidth, label=cellTypes[i])
 
        plt.xticks(range(len(df_Main.columns)), list(df_Main.columns), fontsize=12)
        plt.yticks([0,20,40,60,80,100], ['0','20%','40%','60%','80%','100%'], fontsize=12)
            
        handles, labels = ax.get_legend_handles_labels()
        ms = np.max([len(item) for item in labels]) - len('cell')
        labels = [item.replace(' ','\n').replace('B\n', 'B ').replace('T\n', 'T ') if len(item) >= ms else item for item in labels[::-1]]
        ax.legend(handles[::-1], labels, loc='upper left', bbox_to_anchor=(1,1), ncol=1, frameon=False, fontsize=14, labelspacing=1, title = ''.join([' ' for _ in range(60)]))

        plt.xlim((-0.5, len(df_Main.columns) - 0.5))
        plt.ylim((0, 100))

        for spine in plt.gca().spines.values():
            spine.set_visible(False)
          
        fig.tight_layout()
        fig.savefig(saveName, dpi=300)

        print('\n=================================\nDone saving stacked bar plot: %s!\n=================================' % ('All cell clusters' if clusterName == None else clusterName))

        return
    
    def makeQualityControlHistogramPlot(self, subset, cutoff, plotPathAndName=None, N_bins=100, mito=False, displayMeasures=True, precision=4):

        '''Function to calculate QC quality cutoff and visualize it on a histogram

        Parameters:
            subset: pandas.Series
                Data to analyze

            cutoff: float
                Cutoff to display

            plotPathAndName: str, Default None
                Text to include in the figure title and file name

            N_bins: int, Default 100
                Number of bins of the histogram

            mito: boolean, Default False
                Whether the analysis of mitochondrial genes fraction

            displayMeasures: boolean, Default True
                Print vertical dashed lines along with mean, median, and standard deviation

            precision: int, Default 4
                Number of digits after decimal

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            cutoff = DCS.makeQualityControlHistogramPlot(subset, cutoff)
        '''

        if plotPathAndName is None:
            plotPathAndName = 'QC_Plot'

        range_min = np.min(subset)
        range_max = max(1.1*cutoff, 0.2) if mito else 5000

        hist_of_subset = scipy.stats.rv_histogram(np.histogram(subset, bins=N_bins, range=(range_min, range_max)))
        hist_data = hist_of_subset._hpdf / N_bins
        hist_bins = hist_of_subset._hbins

        fig, ax = plt.subplots(figsize=(8,8))

        bar_bin_width = range_max / N_bins
        ax.bar(hist_bins, hist_data[:-1], width=0.9 * bar_bin_width, color='b', align='center')

        ax.set_title(plotPathAndName, fontdict={'color': 'b'})
        ax.set_xlabel('Fraction' if mito else 'Count', fontsize=8)
        ax.set_ylabel('Density', fontsize=8)
        ax.set_ylim(0.,ax.get_ylim()[1])
        ax.set_xlim(range_min - 0.5 * bar_bin_width, range_max + 0.5 * bar_bin_width)

        xs = np.linspace(hist_bins[0], hist_bins[-1], 1000)
        spline_data = np.vstack((xs, UnivariateSpline(hist_bins, hist_data[:-1], k=5, s=0)(xs))).T

        sg = scipy.signal.savgol_filter(spline_data.T[1], 101, 3)
        ax.plot(spline_data.T[0], sg, 'r', lw=3, alpha=0.95)

        try:
            x, y = cutoff, sg[np.where(spline_data.T[0] >= cutoff)[0][0]]
        except:
            x, y = cutoff, 0.

        ax.plot([x,x], [0,y], 'k', lw=2)
        ax.plot(x, y, 'ko', ms=10, alpha=0.8)
        ax.plot(x, y, 'ro', ms=7)
        ax.text(x, -0.04 * spline_data.T[1].max(), str(np.round(cutoff, precision)), va='top', ha='center', color='r')

        fig.tight_layout()

        if displayMeasures:
            dist_std, dist_median, dist_mean = np.round(np.std(subset),precision), np.round(np.median(subset),precision), np.round(np.mean(subset),precision)
            print(plotPathAndName, '\tstd:', dist_std,  '\tmedian:', dist_median,  '\tmean:', dist_mean)

            xspan = ax.get_xlim()[1] - ax.get_xlim()[0]
            yspan = ax.get_ylim()[1] - ax.get_ylim()[0]
        
            ax.axvline(x=dist_mean, color='k', lw=1.0, ls='--')
            ax.text(dist_mean + 0.02 * xspan, 0.98 * yspan, r'$\mu=%s$' % (dist_mean), fontsize=10, va='top', ha='left', color='k')
        
            ax.axvline(x=dist_median, color='k', lw=1.0, ls='--')
            ax.text(dist_median + 0.02 * xspan, 0.94 * yspan, r'$M=%s$' % (dist_median), fontsize=10, va='top', ha='left', color='k')
        
            ax.axvline(x=dist_median - dist_std, color='k', lw=1.0, ls='--')
            ax.text(dist_median - dist_std + 0.02 * xspan, 0.90 * yspan, r'$M-\sigma=%s$' % (np.round(dist_median - dist_std,precision)), fontsize=10, va='top', ha='left', color='k')
        
            ax.axvline(x=dist_median + dist_std, color='k', lw=1.0, ls='--')
            ax.text(dist_median + dist_std + 0.02 * xspan, 0.90 * yspan, r'$M+\sigma=%s$' % (np.round(dist_median + dist_std,precision)), fontsize=10, va='top', ha='left', color='k')

            ax.annotate(r'$\sigma=%s$' % (dist_std), (dist_median + dist_std, 0.86 * yspan), (dist_median, 0.86 * yspan), arrowprops={'width':0, 'headwidth':0, 'headlength':0})
            ax.annotate('', (dist_median + dist_std, 0.85 * yspan), (dist_median, 0.85 * yspan), arrowprops={'arrowstyle':'<|-|>'})

        fig.savefig(plotPathAndName + '_histogram.png', dpi=300)

        return None

    def makeSankeyDiagram(self, df, colormapForIndex=None, colormapForColumns=None, linksColor='rgba(100,100,100,0.6)', title='', interactive=False, quality=4):

        '''Make a Sankey diagram, also known as 'river plot' with two groups of nodes

        Parameters:
            df: pandas.DataFrame 
                With counts (overlaps)

            colormapForIndex: dictionary, Default None
                Colors to use for nodes specified in the DataFrame index

            colormapForColumns: dictionary, Default None
                Colors to use for nodes specified in the DataFrame columns

            linksColor: str, Default 'rgba(100,100,100,0.6)'
                Color of the non-overlapping links

            title: str, Default ''
                Title to print on the diagram

            interactive: boolean , Default False
                Whether to launch interactive JavaScript-based graph

            quality: int, Default 4
                Proportional to the resolution of the figure to save

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.sankeyDiagram(df, self.dataName)
        '''

        try:
            temp_index = pd.MultiIndex.from_arrays([df.index, [colormapForIndex[item] for item in df.index]], names=['label', 'color'])
            temp_columns = pd.MultiIndex.from_arrays([df.columns, [colormapForColumns[item] for item in df.columns]], names=['label', 'color'])
            df.index = temp_index
            df.columns = temp_columns
        except:
            print('Colormap error. Using default node colors')
            colormapForIndex = None
            colormapForColumns = None

        if (colormapForIndex is None) or (colormapForColumns is None):
            nodeColors = None
            nodeLabels = df.index.to_list() + df.columns.to_list()
        else:
            nodeLabels = df.index.get_level_values('label').to_list() + df.columns.get_level_values('label').to_list()
            nodeColors = df.index.get_level_values('color').to_list() + df.columns.get_level_values('color').to_list()

        sources, targets, values, labels = [], [], [], []
        for i, item in enumerate(df.index):
            sources.extend([i] * len(df.loc[item]))
            targets.extend(list(range(len(df.index), len(df.index) + len(df.loc[item]))))
            values.extend([j for j in df.loc[item].values])
            labels.extend([item[0] + ' -> ' + jtem[0] for jtem in df.loc[item].index])

        colorscales = [dict(label=label, colorscale=[[0, linksColor], [1, linksColor]]) for label in labels]

        if not nodeColors is None:
            for i in range(len(sources)):
                if nodeColors[sources[i]] == nodeColors[targets[i]]:
                    newColor = ','.join(nodeColors[sources[i]].split(',')[:3] + ['0.6)'])
                    colorscales[i] = dict(label=labels[i], colorscale=[[0, newColor], [1, newColor]])

        fig = plotly.graph_objects.Figure(data=[plotly.graph_objects.Sankey(valueformat = '', valuesuffix = '',
            node = dict(pad = 20, thickness = 40, line = dict(color = 'white', width = 0.5), label = nodeLabels, color = nodeColors,),
            link = dict(source = sources, target = targets, value = values, label = labels, colorscales = colorscales))]) #line ={'color':'rgba(255,0,0,0.8)', 'width':0.1}

        if not title is None:
            fig.update_layout(title_text=title, font_size=10)

        fig.write_image(os.path.join(self.saveDir, self.dataName + '.png'), width=600, height=600, scale=quality)

        if interactive:
            fig.show()

        return

    def makePlotOfNewMarkers(self, df_marker_cell_type, df_new_marker_cell_type):

        '''Produce plot of the new markers extracted from the annotated clusters

        Parameters:
            df_marker_cell_type: pandas.DataFrame
                Known markers per cell types

            df_new_marker_cell_type: pandas.DataFrame
                New markers per cell types

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.makePlotOfNewMarkers(df_marker_cell_type, df_new_marker_cell_type)
        '''

        ORDERx = scipy.cluster.hierarchy.dendrogram(scipy.cluster.hierarchy.linkage(df_new_marker_cell_type.values.T, 'ward'), no_plot=True, get_leaves=True)['leaves']
        ORDERy = scipy.cluster.hierarchy.dendrogram(scipy.cluster.hierarchy.linkage(df_new_marker_cell_type.values, 'ward'), no_plot=True, get_leaves=True)['leaves']

        genes = df_new_marker_cell_type.columns.values[ORDERx]
        celltypes = df_new_marker_cell_type.index.values[ORDERy]

        df_marker_cell_type = df_marker_cell_type[[celltype for celltype in celltypes if celltype in df_marker_cell_type.columns]]

        fig, ax = plt.subplots(figsize=(13,3))
        ax.imshow(df_new_marker_cell_type.values[ORDERy,:][:,ORDERx], cmap='Blues', interpolation='None', aspect='auto')
        ax.set_xticks([])
        ax.set_yticks(range(len(celltypes)))
        ax.set_yticklabels(celltypes, rotation=0, fontsize=8)
        ax.set_xlim([-0.5,df_new_marker_cell_type.shape[1] - 0.5])
        ax.set_ylim([-0.5,df_new_marker_cell_type.shape[0] - 0.5])

        for i, celltype in enumerate(celltypes):
            if celltype in df_marker_cell_type.columns:
                known_markers = df_marker_cell_type[celltype][df_marker_cell_type[celltype]>0].index.values
                xy = np.array([np.array([np.where(genes==marker)[0][0], i]) for marker in known_markers if marker in genes])
                print('Overlapping markers of %s: %s (%s)'%(celltype, len(xy), len(known_markers)))
                if len(xy)>0:
                    ax.plot(xy.T[0], xy.T[1], 'ro', ms=0.5)

        ax.set_title('Additional markers along with the overlapping part of the input (red)')
        fig.savefig(os.path.join(self.saveDir, self.dataName + '_new_markers.png'), dpi=600)

        return None

    def makeTtestPlot(self, df, dfp, label=None, reorder=True, p_value_cutoff=0.05):

        '''Produce heatmap plot of t-test p-Values calculated gene-pair-wise
        from the annotated clusters.

        Parameters:
            df: pandas.DataFrame
                t-test p-Values calculated gene-pair-wise

            label: str, Default None
                Lebel to include in the plot
                
            reorder: boolean, Default True
                Reorder values to group similar

        Returns:
            None
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.makeTtestPlot(df)
        '''

        if reorder:

            def metricCommonEuclidean(u,v):

                where_common = (~np.isnan(u)) * (~np.isnan(v))

                return np.sqrt(((u[where_common] - v[where_common]) ** 2).sum())

            order = scipy.cluster.hierarchy.dendrogram(scipy.cluster.hierarchy.linkage(df.values, method='average', metric=metricCommonEuclidean), no_plot=True, get_leaves=True)['leaves']

            df = df[df.columns.values[order]]
            dfp = dfp[dfp.columns.values[order]]
            df = df.loc[df.index.values[order]]
            dfp = dfp.loc[dfp.index.values[order]]

        df = df[df.columns[::-1]]
        dfp = dfp[dfp.columns[::-1]]

        fig = plt.figure(figsize=(5,5))

        ax = fig.add_axes([0.35,0.02,0.63,0.63])
        
        cmap = plt.cm.PuOr_r #BrBG #PiYG #seismic
        cmap.set_bad('grey')

        ax.imshow(df.values.astype(float), cmap=cmap, interpolation='None', aspect='auto')

        wh = np.where(dfp.values.T<=p_value_cutoff)
        ax.plot(wh[0], wh[1], '*k')

        ax.set_xticks(range(df.shape[1]))
        ax.set_yticks(range(df.shape[0]))
        ax.set_xticklabels(df.columns.values, rotation=90, fontsize=8)
        ax.set_yticklabels(df.index.values, rotation=0, fontsize=8)
        ax.set_xlim([-0.5, df.shape[1] - 0.5])
        ax.set_ylim([-0.5, df.shape[0] - 0.5])

        ax.xaxis.tick_top()

        if not label is None:
            ax.text(-0.5, 1.5, label, transform=ax.transAxes, fontsize=10, color='k', ha='left', va='top').set_path_effects(
                [path_effects.Stroke(linewidth=0.5, foreground='blue'),path_effects.Normal()])

        ax.set_title('Two-tailed p-Value (t-test)')

        data = df.values.flatten().astype(float)
        data = data[np.where(~np.isnan(data))]
        dataMin = np.min(data)
        dataMax = np.max(data)

        axisColor = fig.add_axes([0.22,0.75,0.08,0.02])

        norm=matplotlib.colors.Normalize(vmin=dataMin, vmax=dataMax)
        mapp=cm.ScalarMappable(norm=norm, cmap=cmap)
        mapp.set_array(data)
        fig.colorbar(mapp, cax=axisColor, ticks=[dataMax,dataMin], orientation='horizontal')
        axisColor.tick_params(labelsize=4)
        axisColor.set_xlabel('Statistic\n*p-Value < %s'%(p_value_cutoff), fontsize=5)

        axisColor.set_yticklabels([np.round(dataMax,2), np.round(dataMin,2)])

        fig.savefig(os.path.join(self.saveDir, self.dataName + '_ttest_%s.png'%(label.replace('\n', '_'))), dpi=300)

        return None
    
    def makeCellMarkersPiePlot(self, type1, type2, df_marker_cell_type='all', nameToAppend=None):

        '''Make summary of markers comparison between two cell types.

        Parameters:
            type1: str
                Name of the first cell type to compare
                            
            type2: str
                Name of the second cell type to compare

            df_marker_cell_type: pandas.DataFrame or str, Default 'all'
                Celltypes/Markers matrix. If 'expressed', then only expressed markers will be used.
                If 'all' then all markers of the input marker list will be used.
                If an instance of a pandas.DataFrame is passed, then its all markers will be used.

            nameToAppend: str, Default None
                String to append to the figure file name.

        Returns:
            Marker lists split into categories.
        
        Usage:
            DCS = DigitalCellSorter.DigitalCellSorter()

            DCS.makeCellMarkersPiePlot('T cells', 'B cells')
        '''

        if type(df_marker_cell_type) is str:
            if df_marker_cell_type=='expressed':
                df_marker_cell_type = pd.read_excel(os.path.join(self.saveDir, self.dataName + '_voting.xlsx'), 
                                                    sheet_name='Marker cell type weight matrix', index_col=0, header=0).T
                additional_name = 'expressed'
            elif df_marker_cell_type=='all':
                df_marker_cell_type = self.readMarkerFile()
                additional_name = 'all'

            if nameToAppend is None:
                nameToAppend = '.'.join(os.path.basename(self.geneListFileName).split('.')[:-1])
        else:
            additional_name = 'custom'
            
            if nameToAppend is None:
                nameToAppend = ''

        df_marker_cell_type = df_marker_cell_type.fillna(0.)

        try:
            t1p = set(df_marker_cell_type.index[(df_marker_cell_type.loc[:, type1]>.0)].values)
            t1n = set(df_marker_cell_type.index[(df_marker_cell_type.loc[:, type1]<0.)].values)
        except:
            print('Cell type %s not found'%(type1))
            print('Available celltypes are: %s'%(df_marker_cell_type.columns.values.tolist()))
            return None

        try:
            t2p = set(df_marker_cell_type.index[(df_marker_cell_type.loc[:, type2]>0.)].values)
            t2n = set(df_marker_cell_type.index[(df_marker_cell_type.loc[:, type2]<0.)].values)
        except:
            print('Cell type %s not found'%(type2))
            print('Available celltypes are: %s'%(df_marker_cell_type.columns.values.tolist()))
            return None

        diff = lambda a, b: a.difference(b)
        inter = lambda a, b: a.intersection(b)

        labels = '+/*', '+/-', '*/-', '-/-', '-/*', '-/+', '*/+', '+/+'
        colors = 'limegreen', 'thistle', 'lightcoral', 'red', 'lightcoral', 'thistle', 'limegreen', 'green'

        p1 = t1p.intersection(t2n)
        p3 = t1n.intersection(t2n)
        p5 = t1n.intersection(t2p)
        p7 = t1p.intersection(t2p)
        p0 = t1p - p1 - p7
        p2 = t2n - p1 - p3
        p4 = t1n - p3 - p5
        p6 = t2p - p5 - p7

        all = t1p.union(t2p).union(t1n).union(t2n)

        sets = [p0, p1, p2, p3, p4, p5, p6, p7]

        titles = ['Positive in %s:'%(type1),
                  'Positive in %s, Negative in %s:'%(type1, type2),
                  'Negative in %s:'%(type2),
                  'Negative in both:',
                  'Negative in %s:'%(type1),
                  'Negative in %s, Positive in %s:'%(type1, type2),
                  'Positive in %s:'%(type2),
                  'Positive in both:']

        str_sets = [str(sorted([i for i in list(item)])).replace("'", "").replace(']','').replace('[','').replace(' ','') for item in sets]

        def findAll(a, b):

            start = 0

            while True:
                start = a.find(b, start)

                if start == -1: 
                    return

                yield start

                start += len(b)

            return

        for i, item in enumerate(str_sets):

            all = item.split(',')
            new_item = all[0]
            temp_item = all[0]
            limit = 75

            for gene in all[1:]:
                if len(temp_item)>limit:
                    temp_item = '\n' + gene
                    new_item += '\n' + gene
                else:
                    new_item += ', ' + gene
                    temp_item += ', ' + gene

            str_sets[i] = titles[i] + '\n' + new_item

        sizes = [len(item) for item in sets]
        labels = [(label if size>0 else '') for label, size in zip(labels, sizes)]
        explode = (0.0, 0.02, 0.0, 0.0, 0.0, 0.02, 0.0, 0.0)

        fig = plt.figure(figsize=(8,4))
        ax = fig.add_axes([0.25,0.25,0.5,0.5])

        wedges, texts, autotexts = ax.pie(sizes, explode=explode, labels=labels, colors=colors, labeldistance=1.05, 
                                          textprops={'size':6, 'weight':'semibold', 'color':'b'},
                                          autopct=lambda pct: "{:d}".format(int(pct/100.*np.sum(sizes))) if pct>0 else '', wedgeprops={'linewidth': 0.5, 'edgecolor':'aqua', 'width': 0.7},
                                          shadow=False, startangle=-180, frame=False, rotatelabels=False)

        plt.setp(autotexts, size=6, weight="semibold", color='k')

        bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="k", lw=0.5)
        kw = dict(arrowprops=dict(arrowstyle="-"), bbox=bbox_props, zorder=0, va="center")

        for i, p in enumerate(wedges):

            if len(sets[i])==0:
                continue

            ang = (p.theta2 - p.theta1)/2. + p.theta1
            y = np.sin(np.deg2rad(ang))
            x = np.cos(np.deg2rad(ang))

            horizontalalignment = {-1: "right", 1: "left"}[int(np.sign(x))]

            connectionstyle = "angle,angleA=0,angleB={}".format(ang)

            kw["arrowprops"].update({"connectionstyle": connectionstyle})

            ax.annotate(str_sets[i], xy=(x, y), xytext=(1.6*np.sign(x), 1.8*y), fontsize=3.5, horizontalalignment=horizontalalignment, **kw)

        fig.suptitle('%s & %s'%(type1, type2), fontsize=11, color='b')
        ax.axis('equal')

        if self.saveDir is not None:
            fig.savefig(os.path.join(self.saveDir, self.dataName + 'Markers_of_%s_vs_%s_(%s)_%s.png'%(type1.replace('/',''), 
                                                                                                  type2.replace('/',''), 
                                                                                                  nameToAppend, 
                                                                                                  additional_name)), dpi=300)

        return dict(zip(labels, sets))