## lsdmap_knickpointplotting.py
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
## These functions are tools for analysing and plotting knickpoint data
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
## FJC
## 05/06/2017
##=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
from matplotlib import colors
import lsdplottingtools.lsdmap_pointtools as LSDMap_PD
import matplotlib.pyplot as plt
import time as clock
from matplotlib import rcParams
import matplotlib.cm as cm
from lsdmapfigure.plottingraster import MapFigure
from lsdmapfigure.plottingraster import BaseRaster
from lsdmapfigure import plottinghelpers as Helper
from lsdplottingtools import colours as lsdcolours
from lsdplottingtools import statsutilities as SUT
from lsdplottingtools import init_plotting_DV
from matplotlib.patches import Rectangle
from matplotlib.ticker import MaxNLocator
from lsdviztools.lsdplottingtools import lsdmap_pointtools as LSDP
from lsdviztools.lsdplottingtools import lsdmap_vectortools as LSDV
import sys
import os
import pandas as pd
from scipy.stats import norm
pd.options.mode.chained_assignment = None



class KP_plotting(object):
    """
        This class is a development version of the knickpoint algorithm. 
        Its aim is to deal with knickpoint picking stuffs via python code, 
        in a development aim as I am significantly changing methods relatively often.
        B.G.
    """


    def __init__(self, fpath,fprefix, basin_key = [], source_key = [], min_length = 0, cut_off_val = [0,0,0,0], main_stem = False, normalisation = None, size_kp = [],coeff_size = 1):
        """
            This creates a knickpoint object to help with plotting. One object helps you to select certain river and or basins; provide some tool to pre-sort the dataset, and generate
            stats figures, profiles and map. It requires the output of LSDTopoTools.
            params:
                fpath (str): path to the files generated by LSDTopoTool
                fprefix (str): common prefix to all the files
                basin_key (list of int): list of basin to extract from the global dataset. All basins selected if empty list provided. WARNING: if you select some sources that are not in your basins, it won't be selected.
                source_key (list of int): list of sources to extract from the global dataset. All basins selected if empty list provided. WARNING: if you select some sources that are not in your basins, it won't be selected.
                min_length (float): You can choose to ignore all the rivers below a threshold, to avoid dealing with tiny rivers.
                cut_off_val (list of float): cut_off values for the knickpoints magnitude: [negative_delta_ksn,positive_delta_ksn,negative_delta_segelev,positive_delta_segelev].
                    for example [-1,2,-2,3] would select the data where (dksn<-1 AND dksn >2) OR (dsegelev<-2 AND dsegelev)
                main_stem (bool): Only the main stem of each basins
                normalisation (str): "relative" or "absolute" reset the elevation to the minimum/maximum of a basin
                size_of_kp: leave blank for auto, otherwise [min_value,size,max_value,size]. For example [1.5,5,4.5,10] would plot all the knickpoints <= 1.5 with a size of 5, all the knickpoints >= 4.5 with a size of 10 and everything in between graduadly increasing.
            author: B.G - 2017/2018

        """

        print("Let me first preprocess and check your files")

        # Loading the attributes
        self.fpath = fpath # the path of your file : /home/your/path/
        self.fprefix = fprefix # the common prefix of all your files

        # Loading the files

        print("Loading the knickpoint-related files")
        
        try:
            self.df_river = Helper.ReadMChiSegCSV(self.fpath, self.fprefix, type = "knickpoint") # Contains the river info
            self.df_rivraw = Helper.ReadMChiSegCSV(self.fpath, self.fprefix, type = "knickpoint") # Contains the river info (will not be thinned by your selection choices)
            self.df_kp_raw = Helper.ReadKnickpointCSV(self.fpath, self.fprefix, ftype = "raw") # Contains the raw knickpint info (before TVD or else) -> Debugging purposes
            self.df_kp = Helper.ReadKnickpointCSV(self.fpath, self.fprefix) # Contains the knickpoint location and informations
            self.df_SK = Helper.readSKKPstats(self.fpath, self.fprefix) # Contains few metrics per river keys
            self.df_kp_ksn = self.df_kp[self.df_kp["delta_ksn"] != 0]
            self.df_kp_stepped = self.df_kp[self.df_kp["delta_segelev"] > 0]

        except IOError:
            print("I didnae find your knickpoint related files make sure that:")
            print("- You ran the knickpoint analysis")
            print("- Your path is good and finishing by '/' e.g. /home/name/kazakhstan/ ")
            print("- Check your writing path in your param file used with chi_mapping_tool.exe, a wrong path won't trigger error but won't write files.")

            quit()


        print("Managing the data:")

        if(normalisation != None):
            self.normalise_elevation(method = normalisation)

        if(isinstance(cut_off_val,str) and (cut_off_val == "auto")):
            cut_off_val = [self.df_kp_ksn["delta_ksn"].quantile(0.25),self.df_kp_ksn["delta_ksn"].quantile(0.75),-10000,self.df_kp_stepped["delta_segelev"].quantile(0.75)]

        if(cut_off_val != [0,0,0,0]):
            print("I am selecting your knickpoints")
            # This selection process is a bit messy, but really efficient with pandas!
            print(cut_off_val)
            self.df_kp = self.df_kp[((self.df_kp["delta_ksn"] <= cut_off_val[0]) | (self.df_kp["delta_ksn"] >= cut_off_val[1])) | ((self.df_kp["delta_segelev"] <= cut_off_val[2]) | (self.df_kp["delta_segelev"] >= cut_off_val[3]))]
            self.df_kp_ksn = self.df_kp[((self.df_kp["delta_ksn"] <= cut_off_val[0]) | (self.df_kp["delta_ksn"] >= cut_off_val[1]))]
            self.df_kp_stepped = self.df_kp[((self.df_kp["delta_segelev"] <= cut_off_val[2]) | (self.df_kp["delta_segelev"] >= cut_off_val[3]))]
        

        # Selection of Basins and sources
        if(basin_key == []):
            print("All the basins are selected:")
            print(self.df_SK["basin_key"].unique().tolist())
        else:
            print("You selected the following basins:")
            print(basin_key)
            self.df_river = self.df_river[self.df_river["basin_key"].isin(basin_key)]
            self.df_kp_raw = self.df_kp_raw[self.df_kp_raw["basin_key"].isin(basin_key)]
            self.df_kp = self.df_kp[self.df_kp["basin_key"].isin(basin_key)]
            self.df_SK = self.df_SK[self.df_SK["basin_key"].isin(basin_key)]
            self.df_kp_ksn = self.df_kp_ksn[self.df_kp_ksn["basin_key"].isin(basin_key)]
            self.df_kp_stepped = self.df_kp_stepped[self.df_kp_stepped["basin_key"].isin(basin_key)]


        if(source_key == [] and min_length == 0):
            print("All the sources are selected:")
            print(self.df_SK["source_key"].unique().tolist())
        elif(min_length > 0):
            print("Let me remove the river smaller than " +str(min_length))
            self.df_SK = self.df_SK[self.df_SK["length"]>min_length]
            source_key = self.df_SK["source_key"].unique()
            self.df_river = self.df_river[self.df_river["source_key"].isin(source_key)]
            self.df_kp_raw = self.df_kp_raw[self.df_kp_raw["source_key"].isin(source_key)]
            self.df_kp = self.df_kp[self.df_kp["source_key"].isin(source_key)]
            self.df_SK = self.df_SK[self.df_SK["source_key"].isin(source_key)]
            self.df_kp_ksn = self.df_kp_ksn[self.df_kp_ksn["source_key"].isin(source_key)]
            self.df_kp_stepped = self.df_kp_stepped[self.df_kp_stepped["source_key"].isin(source_key)]
            print("You selected the following Sources: ")
            print(source_key)

        else:
            print("You selected the following Sources: ")
            print(source_key)
            self.df_river = self.df_river[self.df_river["source_key"].isin(source_key)]
            self.df_kp_raw = self.df_kp_raw[self.df_kp_raw["source_key"].isin(source_key)]
            self.df_kp = self.df_kp[self.df_kp["source_key"].isin(source_key)]
            self.df_SK = self.df_SK[self.df_SK["source_key"].isin(source_key)]
            self.df_kp_ksn = self.df_kp_ksn[self.df_kp_ksn["source_key"].isin(source_key)]
            self.df_kp_stepped = self.df_kp_stepped[self.df_kp_stepped["source_key"].isin(source_key)]

        if(main_stem):
            print("Wait, you just want the main stem, let me deal with that")
            source_key = []
            for bas in self.df_SK["basin_key"].unique():
                TSK = self.df_SK[self.df_SK["basin_key"] == bas]
                ts = TSK["source_key"][TSK["length"] == TSK["length"].max()].values[0]
                source_key.append(ts)

            self.df_river = self.df_river[self.df_river["source_key"].isin(source_key)]
            self.df_kp_raw = self.df_kp_raw[self.df_kp_raw["source_key"].isin(source_key)]
            self.df_kp = self.df_kp[self.df_kp["source_key"].isin(source_key)]
            self.df_SK = self.df_SK[self.df_SK["source_key"].isin(source_key)]
            self.df_kp_ksn = self.df_kp_ksn[self.df_kp_ksn["source_key"].isin(source_key)]
            self.df_kp_stepped = self.df_kp_stepped[self.df_kp_stepped["source_key"].isin(source_key)]
            print("final source_keys are: ")
            print(source_key)


        #### Now dealing with the size of knickpoints on map/profile.
        # By default I am setting the minimum size to the 1st quartile and the maximum to the 3rd quartile
        
        if(len(size_kp)!= 4):
            size_kp.append(self.df_kp_ksn["delta_ksn"].abs().quantile(0.25)) # min val for sizing dksn kps = every knickpoints below this value will have the same (minimum) size
            size_kp.append(self.df_kp_ksn["delta_segelev"].abs().quantile(0.25)) # MIN VALUE FOR STEPPED KNICKPOINT IS under work
            size_kp.append(self.df_kp_ksn["delta_ksn"].abs().quantile(0.75)) # max val for sizing dksn kps = every knickpoints below this value will have the same (maximum) size
            size_kp.append(self.df_kp_ksn["delta_segelev"].abs().quantile(0.75)) # MAX VALUE FOR STEPPED KNICKPOINT IS under work
            print("SIZE GLOBAL WARNING :Automatically sizing your knickpoint range: all knikcpoints below %s will have the minimum size and all knickpoints above %s the maximum in absolute values." %(size_kp[0],size_kp[2]))


        # applying the size column 
        ## I Normalize the size
        minsize = 0.2

        self.df_kp_ksn["size_kp"] = pd.Series(data = self.df_kp_ksn["delta_ksn"].abs(), index = self.df_kp_ksn.index)

        ## Recasting the knickpoints into a range (everything below a threshold will have the same minimum value and above another thrshold another maximum value)
        self.df_kp_ksn["size_kp"][self.df_kp_ksn["delta_ksn"].abs() <= size_kp[0]] = size_kp[0]
        self.df_kp_ksn["size_kp"][self.df_kp_ksn["delta_ksn"].abs() >= size_kp[2]] = size_kp[2]

        ## Applying a coeff
        # self.df_kp_ksn["size_kp"] += 0.01
        self.df_kp_ksn["size_kp"] = self.df_kp_ksn["size_kp"] /self.df_kp_ksn["size_kp"].max() 
        self.df_kp_ksn["size_kp"] = self.df_kp_ksn["size_kp"] - self.df_kp_ksn["size_kp"].min() + minsize
        self.df_kp_ksn["size_kp"] *= coeff_size

        # plt.hist(self.df_kp_ksn["size_kp"].values, bins = 35)
        # plt.savefig("TESTHIST.png")
        # quit()


        # Same the general dataset
        self.df_kp["size_kp"] = pd.Series(data = self.df_kp["delta_ksn"].abs(), index = self.df_kp.index)
        self.df_kp["size_kp"][self.df_kp["delta_ksn"].abs() <= size_kp[0]] = size_kp[0]
        self.df_kp["size_kp"][self.df_kp["delta_ksn"].abs() >= size_kp[2]] = size_kp[2]
        # self.df_kp["size_kp"] += 0.01
        self.df_kp["size_kp"] = self.df_kp["size_kp"]/self.df_kp["size_kp"].max()
        self.df_kp["size_kp"] = self.df_kp["size_kp"] - self.df_kp["size_kp"].min() +minsize
        self.df_kp["size_kp"] *= coeff_size

        # applying the size column to the step
        ## I Normalize the size
        self.df_kp_stepped["size_kp_step"] = pd.Series(data = self.df_kp_stepped["delta_segelev"].abs(), index = self.df_kp_stepped.index)

        ## Recasting the knickpoints into a range (everything below a threshold will have the same minimum value and above another thrshold another maximum value)
        # self.df_kp_stepped["size_kp_step"][self.df_kp_stepped["delta_segelev"].abs() <= size_kp[1]] = size_kp[1]
        self.df_kp_stepped["size_kp_step"][self.df_kp_stepped["delta_segelev"].abs() >= size_kp[3]] = size_kp[3]
        ## Applying a coeff
        # self.df_kp_stepped["size_kp_step"] += 0.01
        self.df_kp_stepped["size_kp_step"] = self.df_kp_stepped["size_kp_step"]/self.df_kp_stepped["size_kp_step"].max()
        self.df_kp_stepped["size_kp_step"] = self.df_kp_stepped["size_kp_step"] - self.df_kp_stepped["size_kp_step"].min() + minsize
        self.df_kp_stepped["size_kp_step"] *= coeff_size

        # Same the general dataset
        self.df_kp["size_kp_step"] = pd.Series(data = self.df_kp["delta_segelev"].abs(), index = self.df_kp.index)
        # self.df_kp["size_kp_step"][self.df_kp["delta_segelev"].abs() <= size_kp[1]] = size_kp[1]
        self.df_kp["size_kp_step"][self.df_kp["delta_segelev"].abs() >= size_kp[3]] = size_kp[3]
        # self.df_kp["size_kp_step"] += 0.01
        self.df_kp["size_kp_step"] =self.df_kp["size_kp_step"]/self.df_kp["size_kp_step"].max()
        self.df_kp["size_kp_step"] = self.df_kp["size_kp_step"] - self.df_kp["size_kp_step"].min() + minsize
        self.df_kp["size_kp_step"] *= coeff_size
        


        # Just getting rid of few NoData
        self.df_river["m_chi"][self.df_river["m_chi"] == -9999] = 0
        print("Min dksn: %s - max dksn: %s - min dseg: %s - max dseg: %s" %(self.df_kp["delta_ksn"].min(),self.df_kp["delta_ksn"].max(),self.df_kp["delta_segelev"].min(), self.df_kp["delta_segelev"].max()))
        print("After all the thinning process, it remains %s dksn knickpoints, and %s dsegelev knickpoints" %(self.df_kp_ksn.shape[0],self.df_kp_stepped.shape[0]))
        print("Done now")

    ######################################################################################################################
    ####################### A first set of general functions to prepare the data/figures #################################
    ######################################################################################################################


                                            #                  .
                                            #              /\ /l
                                            #             ((.Y(!
                                            #              \ |/
                                            #              /  6~6,
                                            #              \ _    +-.
                                            #               \`-=--^-'
                                            #                \ \
                                            #               _/  \
                                            #              (  .  Y
                                            #             /"\ `--^--v--.
                                            #            / _ `--"T~\/~\/
                                            #           / " ~\.  !
                                            #     _    Y      Y./'
                                            #    Y^|   |      |~~7
                                            #    | l   |     / ./'
                                            #    | `L  | Y .^/~T
                                            #    |  l  ! | |/| |          
                                            #    | .`\/' | Y | !
                                            #    l  "~   j l j_L______
                                            #     \,____{ __"~ __ ,\_,\_
                                            # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



    def get_fig_right_size(self, size = "esurf", n_axis =1 ,facecolor = 'white'):
        """
        return a matplotlib fig object presized
        param:
            size = size code (esurf,geomorphology,big or tuple/array of size)
            n_axis: number of axis
        """

        # Cheching the type of input

        if (isinstance(size,str)):
            if size.lower() == "geomorphology":
                fig = plt.figure(n_axis, facecolor = facecolor, figsize=(6.25,3.5))            
            elif size.lower() == "big":
                fig = plt.figure(n_axis, facecolor = facecolor, figsize=(16,9))            
            elif size.lower() == "esurf":
                fig = plt.figure(n_axis, facecolor = facecolor, figsize=(4.92126,3.5))
            else:
                print("I did not understood your format input (%s), I am defaulting to esurf." %(size))
                fig = plt.figure(n_axis, facecolor = facecolor, figsize=(4.92126,3.5))
        if ((isinstance(size,tuple)) or (isinstance(size,list))):
            if len(size) == 2:
                fig = plt.figure(n_axis, facecolor = facecolor, figsize=(size[0], size[1]))
            else:
                print("I did not understood your format input (%s), I am defaulting to esurf." %(size))
                fig = plt.figure(n_axis, facecolor = facecolor, dpi = 600) 

        return fig

    def get_figwidth_right_size(self, size = "esurf"):
        """
        return a matplotlib fig object presized
        param:
            size = size code (esurf,geomorphology,big or tuple/array of size)
            n_axis: number of axis
        """

        # Cheching the type of input

        if (isinstance(size,str)):
            if size.lower() == "geomorphology":
                wsize = 6.25            
            elif size.lower() == "big":
                wsize = 16           
            elif size.lower() == "esurf":
                wsize = 4.92126
            else:
                print("I did not understood your format input (%s), I am defaulting to esurf." %(size))
                wsize = 4.92126
        if ((isinstance(size,tuple)) or (isinstance(size,list))):
            if len(size) == 2:
                wsize = size[0]
            else:
                print("I did not understood your format input (%s), I am defaulting to esurf." %(size))
                wsize = 4.92126

        return wsize

    def normalise_elevation(self,method = "relative"):
        """
            Normalise the elevation to the outlet of the basin in a relative way (outlet = 0 and elevation = old elevation - outlet elevation) or
            absolute way: outlet = 0 and maximum elevation = 1 
        """

        for bas in self.df_SK["basin_key"].unique():
            norm_elev = self.df_river["elevation"][self.df_river["basin_key"] == bas].min()
            self.df_river["elevation"][self.df_river["basin_key"] == bas] -= norm_elev
            self.df_rivraw["elevation"][self.df_rivraw["basin_key"] == bas] -= norm_elev 
            self.df_kp_raw["elevation"][self.df_kp_raw["basin_key"] == bas] -= norm_elev 
            self.df_kp["elevation"][self.df_kp["basin_key"] == bas] -= norm_elev 
            self.df_kp_ksn["elevation"][self.df_kp_ksn["basin_key"] == bas] -= norm_elev 
            self.df_kp_stepped["elevation"][self.df_kp_stepped["basin_key"] == bas] -= norm_elev 
            if(method == "absolute"):
                norm_elev = self.df_river["elevation"][self.df_river["basin_key"] == bas].max()
                self.df_river["elevation"][self.df_river["basin_key"] == bas] /= norm_elev
                self.df_rivraw["elevation"][self.df_rivraw["basin_key"] == bas] /= norm_elev 
                self.df_kp_raw["elevation"][self.df_kp_raw["basin_key"] == bas] /= norm_elev 
                self.df_kp["elevation"][self.df_kp["basin_key"] == bas] /= norm_elev 
                self.df_kp_ksn["elevation"][self.df_kp_ksn["basin_key"] == bas] /= norm_elev 
                self.df_kp_stepped["elevation"][self.df_kp_stepped["basin_key"] == bas] /= norm_elev 






 





    ######################################################################################################################
    ########################################### The plotting figures #####################################################
    ######################################################################################################################

                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$**$$$$$$$$$**$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$"   ^$$$$$$F    *$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$     z$$$$$$L    ^$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$    e$$$$$$$$$e  J$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$eee$$$$$$$$$$$$$e$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$b$$$$$$$$$$$$$$$$$$*$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$)$$$$P"e^$$$F$r*$$$$F"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$d$$$$  "z$$$$"  $$$$%  $3$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$*"""*$$$  .$$$$$$ z$$$*   ^$e*$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$"     *$$ee$$$$$$$$$$*"     $$$C$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$.      "***$$"*"$$""        $$$$e*$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$b          "$b.$$"          $$$$$b"$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$c.         """            $$$$$$$^$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$e..                     $$$$$$$$^$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$eeee..            J$$$$$$$$b"$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$r          z$$$$$$$$$$r$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"         z$$$$$**$$$$$^$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$*"          z$$$P"   ^*$$$ $$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$*"           .d$$$$       $$$ $$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$"           .e$$$$$F       3$$ $$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$.         .d$$$$$$$         $PJ$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$eeeeeeed$*""""**""         $\$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$                  $d$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$.                 $$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$e.              d$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$eeeeeee$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
                    # $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$


    def print_ksn_profile(self,size = "big", format = "png", x_axis = "chi", y_axis = "m_chi" , knickpoint = True, title = "none", label_size = 8, facecolor = 'white',
        size_of_ksn = 4, legend = True, size_of_TVD_ksn = 3):

        """
        print a plot for each source keys selected with ksn value function to Chi or Flow Distance.
        param:
            size: the size of the figure, default big.
            format: format of the output: "png", "svg" or "show".
            x_axis: The coordinates to print the data: "chi" or "flow distance"
            knickpoint: True or False to display it or not.
            title: "none" for no title, "auto" for displaying the source key.
            size_of_ksn: size of the m_chi (ksn) points before processing
            legend: if True, will plot the legend
        Author: B.G. - 25/01/2018
        """

        # check if a directory exists for the chi plots. If not then make it.
        out_directory = self.fpath+'river_plots/'
        if not os.path.isdir(out_directory):
            print("I am creating the river_plot/ directory to save your figures")
            os.makedirs(out_directory)


        # Adjust the corrected y_axis
        if(y_axis == "m_chi"):
            corrected_y_axis = "TVD_ksn"
            y_kp = "delta_ksn"
            ylab = r"$k_{sn}$"
        elif(y_axis == "b_chi"):
            corrected_y_axis = "TVD_b_chi"
            y_kp = "delta_segelev"
            ylab = r"$b_{\chi}$"
        elif(y_axis == "segmented_elevation"):
            y_axis = "segdrop"
            corrected_y_axis = "TVD_elevation"
            y_kp = "delta_segelev"
            ylab = r"$elevation$"
        else:
            print("Non valid y-axis it has to be b_chi or m_chi (= ksn)")
            quit()
        
        # Set up fonts for plots
        rcParams['font.family'] = 'sans-serif'
        rcParams['font.sans-serif'] = ['Liberation Sans'] # Liberation Sans is a free alternative to Arial. Albeit being quite universal, Arial is propietary. #PRAISE_FREE_AND_OPENSOURCE
        rcParams['font.size'] = label_size


        for sources in self.df_SK["source_key"].unique():

            # Select the data
            this_df_SK = self.df_SK[self.df_SK["source_key"] == sources]
            this_df_kp = self.df_kp[self.df_kp["source_key"] == sources]
            this_df_kp = this_df_kp[this_df_kp["out"] == 1]
            this_df_kp_raw = self.df_kp_raw[self.df_kp_raw["source_key"] == sources]
            this_df_river = self.df_river[self.df_river["source_key"] == sources]

            
            # Create a figure with required dimensions
            n_axis = 1
            fig = self.get_fig_right_size(size = size, n_axis =1 , facecolor = facecolor)

            # create the axis using the gridspec method
            gs = plt.GridSpec(100,100,bottom=0.15,left=0.10,right=0.90,top=0.95)
            ax2 = fig.add_subplot(gs[0:100,0:100], facecolor = "None")
            gs = plt.GridSpec(100,100,bottom=0.15,left=0.10,right=0.90,top=0.95)
            ax1 = fig.add_subplot(gs[0:100,0:100], facecolor = "None")


            # plotting the ksn
            ## not processed (in the back and quite transparent)
            ax1.scatter(this_df_river[x_axis],this_df_river[y_axis], s = size_of_ksn, c = "r", lw =0, alpha = 0.3, label = "ksn (before TVD)")
            ax1.scatter(this_df_river[x_axis],this_df_river[corrected_y_axis], s = size_of_TVD_ksn, c ="k", lw =0, alpha = 1, label = "ksn (TVD)")
            ## Getting the extents of this first plot to apply it to the knickpoint one
            this_xlim = ax1.get_xlim()

            # Label
            if(x_axis == "chi"):
                xlab = r"$\chi$"
            elif(x_axis == "flow_distance"):
                xlab = "Distance from the outlet (m)"
            ax1.set_xlabel(xlab)
            ax1.set_ylabel(ylab)



            if(knickpoint):
                this_df_kp_pos = this_df_kp[this_df_kp[y_kp]>0]
                this_df_kp_neg = this_df_kp[this_df_kp[y_kp]<0]
                ax2.scatter(this_df_kp_pos[x_axis], this_df_kp_pos[y_kp], marker = "s", s = 5, c = "#E79A00")
                ax2.scatter(this_df_kp_neg[x_axis], this_df_kp_neg[y_kp], marker = "s", s = 5, c = "#2939FF")

            # Adapting hte extents 
            ax2.set_xlim(this_xlim)

            # Title
            if(title.lower() == "auto"):
                this_title = "source %s" %(sources)
            elif(title.lower() != "none"):
                this_title = title

            if(title.lower() != "none"):
                extra = ax1.add_patch(Rectangle((0, 0), 1, 1, fc="w", fill=False, edgecolor='none', linewidth=0, label = this_title))



            # Legend
            ax1.legend(loc = 0) # 1 = upper right 0 - best choice
            ax2.xaxis.set_visible(False)
            if(knickpoint):
                ax2.yaxis.set_ticks_position("right")
                ax2.yaxis.set_label_position("right")
                ax2.set_ylabel(r"$Knickpoint \/ \Delta k_{sn}$") # the \/ add a space in between, the mathematical expression compiling can screw this a bit
            else:
                ax2.yaxis.set_visible(False)

            # Saving the figure
            plt.savefig(out_directory + self.fprefix+"_ksn_source_%s_%s.%s"%(sources,y_axis,format), dpi = 500)
            plt.clf()
            # switching to the next figure



 

        # End of this function

    def print_river_profile(self,size = "big", format = "png", x_axis = "chi", knickpoint = True, title = "none", label_size = 8, facecolor = 'white',
        size_of_river = 0.5, legend = True, size_of_TVD_ksn = 3, up_set = 40, kalib = False, binning = "source_key", print_seg_elev = False, size_recasting = []):

        """
        """

         # check if a directory exists for the chi plots. If not then make it.
        out_directory = self.fpath+'river_plots/'
        if not os.path.isdir(out_directory):
            print("I am creating the river_plot/ directory to save your figures")
            os.makedirs(out_directory)

        
        # Set up fonts for plots
        rcParams['font.family'] = 'sans-serif'
        rcParams['font.sans-serif'] = ['Liberation Sans'] # Liberation Sans is a free alternative to Arial. Albeit being quite universal, Arial is propietary. #PRAISE_FREE_AND_OPENSOURCE
        rcParams['font.size'] = label_size


        for sources in self.df_SK[binning].unique():

            # Select the data
            this_df_SK = self.df_SK[self.df_SK[binning] == sources]
            this_df_kp_ksn = self.df_kp_ksn[self.df_kp_ksn[binning] == sources]
            this_df_kp_stepped = self.df_kp_stepped[self.df_kp_stepped[binning] == sources]
            this_df_dksn_pos = this_df_kp_ksn[this_df_kp_ksn["sign"] == 1]
            this_df_dksn_neg = this_df_kp_ksn[this_df_kp_ksn["sign"] == -1]
            this_df_dsegelev_pos = this_df_kp_stepped[this_df_kp_stepped["delta_segelev"]> 0]

            this_df_kp_raw = self.df_kp_raw[self.df_kp_raw[binning] == sources]
            this_df_river = self.df_river[self.df_river[binning] == sources]

            # Dealing with the knickpoint offset

            up_set = (this_df_river["elevation"].max() - this_df_river["elevation"].min())*0.1

            if(this_df_kp_ksn.shape[0]> 0 or this_df_dsegelev_pos.shape[0] > 0):
                # Create a figure with required dimensions
                n_axis = 1
                fig = self.get_fig_right_size(size = size, n_axis =1 , facecolor = facecolor)

                gs = plt.GridSpec(100,100,bottom=0.15, left=0.10, right=0.95, top=0.95)
                ax1 = fig.add_subplot(gs[0:100,0:100], facecolor = "None")

                #plot the long/Chi profile
                ax1.scatter(this_df_river[x_axis], this_df_river["elevation"], lw =0 , s = size_of_river, c = "#174D9C" , zorder = 3)
                # Plotting the river noes that does contain knickpoint to check combining
                ax1.scatter(this_df_river[x_axis][this_df_river["ksnkp"]!=0], this_df_river["elevation"][this_df_river["ksnkp"]!=0], lw =0 , s = size_of_river, c = this_df_river["ksnkp"][this_df_river["ksnkp"]!=0], cmap = "RdBu_r" , zorder = 4)

                # this can be printed to adapt the ksn extraction parameters from Mudd et al. 2014
                if (print_seg_elev):    
                    cb1 = ax1.scatter(this_df_river[x_axis], this_df_river["segmented_elevation"], lw =0 , s = size_of_river/2, c = "k", alpha = 0.5, zorder = 3)

                # Plot the dksn knickpionts
                ## First normalized the size

                # sizing = self.df_kp_ksn.copy()
                # sizing["delta_ksn"] = sizing["delta_ksn"].abs()
                # sizing = sizing["delta_ksn"][sizing[binning] == sources].values
                # if(len(size_recasting) == 2):
                #     sizing[sizing<size_recasting[0]] = size_recasting[0]
                #     sizing[sizing>size_recasting[1]] = size_recasting[1]

                try:
                    # sizing = sizing/np.max(sizing)
                    # sizing += 0.01
                    # sizing = sizing * coeff_size

                    ## plot the triangles
                    ax1.scatter(this_df_dksn_pos[x_axis], this_df_dksn_pos["elevation"] + up_set, s = this_df_dksn_pos["size_kp"], lw = 0, marker = "^", c = "r", alpha = 0.95, zorder = 5)
                    ax1.scatter(this_df_dksn_neg[x_axis], this_df_dksn_neg["elevation"] + up_set, s = this_df_dksn_neg["size_kp"], lw = 0, marker = "v", c = "b", alpha = 0.95, zorder = 5)
                    ## plot the contours
                    ax1.scatter(this_df_dksn_pos[x_axis], this_df_dksn_pos["elevation"] + up_set, s = this_df_dksn_pos["size_kp"], lw = 0.5, marker = "^", facecolor = "none", edgecolor = "k", alpha = 0.95, zorder = 5)
                    ax1.scatter(this_df_dksn_neg[x_axis], this_df_dksn_neg["elevation"] + up_set, s = this_df_dksn_neg["size_kp"], lw = 0.5, marker = "v", facecolor = "none", edgecolor = "k", alpha = 0.95, zorder = 5)

                except ValueError:
                    print("No ksn knickpoint on source " + str(sources))
                # Plot the dksn knickpionts
                ## First normalized the size
                # size_pos = this_df_dsegelev_pos["delta_segelev"]/this_df_kp_stepped["delta_segelev"].max()*3
                ##plt the bars
                ax1.scatter(this_df_dsegelev_pos[x_axis], this_df_dsegelev_pos["elevation"] - up_set, s = 10, lw = 1.5, marker = "|", c = "#CB9A00", alpha = 0.95, zorder = 5)
                #Plot vertical bars in beetween
                ax1.vlines(this_df_dksn_neg[x_axis], this_df_dksn_neg["elevation"], this_df_dksn_neg["elevation"] + up_set, zorder = 1, lw = 0.15 )
                ax1.vlines(this_df_dksn_pos[x_axis], this_df_dksn_pos["elevation"], this_df_dksn_pos["elevation"] + up_set, zorder = 1, lw = 0.15 )
                ax1.vlines(this_df_dsegelev_pos[x_axis], this_df_dsegelev_pos["elevation"] - up_set, this_df_dsegelev_pos["elevation"], zorder = 1, lw = 0.15 )
                


                if(kalib):

                    kal = pd.read_csv("/home/s1675537/PhD/LSDTopoData/knickpoint/test_location_paper/Smugglers_SC/field_kp/calib_jointed.csv")
                    kal = kal[kal[binning] == sources]
                    colaray = kal["type"].values
                    colaray[colaray == "bases"] = "#A002D3"
                    colaray[colaray == "lips"] = "#57B300"
                    ax1.scatter(kal[x_axis],kal["elevation"], marker = "x", s = 7, lw = 0.4, zorder = 2, c = colaray)    


                if(x_axis == "chi"):
                    ax1.set_xlabel(r"$\chi$ (m)")
                else:
                    ax1.set_xlabel("Distance from the outlet (m)")

                ax1.set_ylabel("z (m)")

                # Title
                if(title.lower() == "auto"):
                    this_title = "source %s" %(sources)
                elif(title.lower() != "none"):
                    this_title = title

                if(title.lower() != "none"):
                    extra = ax1.add_patch(Rectangle((0, 0), 1, 1, fc="w", fill=False, edgecolor='none', linewidth=0, label = this_title))

                    ax1.legend([extra],[this_title], loc = 0) # 1 = upper right 0 - best choice

                # Saving the figure
                plt.savefig(out_directory + self.fprefix + "_%s_%s_%s.%s"%(binning,sources,x_axis,format), dpi = 500)
                plt.clf()
            # switching to the next figure


    def print_classic_basin_profile(self,size = "big", format = "png", x_axis = "chi", knickpoint = True, label_size = 8, facecolor = 'white',
        size_of_river = 0.5, kalib = False, binning = "basin_key", print_seg_elev = False, size_recasting = [], neg = True, pos = False, step = False):

        """
        This function will print a basic version of a basin-wide river profile, displaying the knickpoints in a "classical way": important concavity-to-convexity change. 
        """

         # check if a directory exists for the chi plots. If not then make it.
        out_directory = self.fpath+'river_plots/'
        if not os.path.isdir(out_directory):
            print("I am creating the river_plot/ directory to save your figures")
            os.makedirs(out_directory)
        
        # Set up fonts for plots
        rcParams['font.family'] = 'sans-serif'
        rcParams['font.sans-serif'] = ['Liberation Sans'] # Liberation Sans is a free alternative to Arial. Albeit being quite universal, Arial is propietary. #PRAISE_FREE_AND_OPENSOURCE
        rcParams['font.size'] = label_size

        for sources in self.df_SK[binning].unique():

            # Select the data
            this_df_SK = self.df_SK[self.df_SK[binning] == sources]
            this_df_kp_ksn = self.df_kp_ksn[self.df_kp_ksn[binning] == sources]
            this_df_kp_stepped = self.df_kp_stepped[self.df_kp_stepped[binning] == sources]
            this_df_dksn_pos = this_df_kp_ksn[this_df_kp_ksn["sign"] == 1]
            this_df_dksn_neg = this_df_kp_ksn[this_df_kp_ksn["sign"] == -1]
            this_df_dsegelev_pos = this_df_kp_stepped[this_df_kp_stepped["delta_segelev"]> 0]

            this_df_kp_raw = self.df_kp_raw[self.df_kp_raw[binning] == sources]
            this_df_river = self.df_river[self.df_river[binning] == sources]

            if(this_df_kp_ksn.shape[0]> 0 or this_df_dsegelev_pos.shape[0] > 0):
                # Create a figure with required dimensions

                n_axis = 1
                fig = self.get_fig_right_size(size = size, n_axis =1 , facecolor = facecolor)

                gs = plt.GridSpec(100,100,bottom=0.15, left=0.10, right=0.95, top=0.95)
                ax1 = fig.add_subplot(gs[0:100,0:100], facecolor = "None")

                #plot the long/Chi profile
                ax1.scatter(this_df_river[x_axis], this_df_river["elevation"], lw =0 , s = size_of_river, c = "#174D9C" , zorder = 3, label = "")
                # Plotting the river noes that does contain knickpoint to check combining
                # ax1.scatter(this_df_river[x_axis][this_df_river["ksnkp"]!=0], this_df_river["elevation"][this_df_river["ksnkp"]!=0], lw =0 , s = size_of_river, c = this_df_river["ksnkp"][this_df_river["ksnkp"]!=0], cmap = "RdBu_r" , zorder = 4)

                # this can be printed to adapt the ksn extraction parameters from Mudd et al. 2014
                if (print_seg_elev):    
                    cb1 = ax1.scatter(this_df_river[x_axis], this_df_river["segmented_elevation"], lw =0 , s = size_of_river/2, c = "k", alpha = 0.5, zorder = 3, label = "")

                try:

                    ## plot the triangles
                    if(neg):
                        ax1.scatter(this_df_dksn_neg[x_axis], this_df_dksn_neg["elevation"], s = this_df_dksn_neg["size_kp"], lw = 0, marker = "o", c = "#00F1EA", alpha = 0.95, zorder = 5, label = r"-$\Delta k_{sn}$")
                        ax1.scatter(this_df_dksn_neg[x_axis], this_df_dksn_neg["elevation"], s = this_df_dksn_neg["size_kp"], lw = 0.5, marker = "o", facecolor = "none", edgecolor = "k", alpha = 0.90, zorder = 5, label = "")
                        
                    ## plot the contours
                    if(pos):
                        ax1.scatter(this_df_dksn_pos[x_axis], this_df_dksn_pos["elevation"], s = this_df_dksn_pos["size_kp"], lw = 0, marker = "o", c = "r", alpha = 0.95, zorder = 5, label = r"+$\Delta k_{sn}$")
                        ax1.scatter(this_df_dksn_pos[x_axis], this_df_dksn_pos["elevation"], s = this_df_dksn_pos["size_kp"], lw = 0.5, marker = "o", facecolor = "none", edgecolor = "k", alpha = 0.95, zorder = 5, label = "")

                except ValueError:
                    print("No ksn knickpoint on source " + str(sources))
                if(step):
                    ax1.scatter(this_df_dsegelev_pos[x_axis], this_df_dsegelev_pos["elevation"], s = 50, lw = 0, marker = "s", c = "yellow", alpha = 0.95, zorder = 4.5, label = "Step")
                    ax1.scatter(this_df_dsegelev_pos[x_axis], this_df_dsegelev_pos["elevation"], s = 50, lw = 0.5, marker = "s", edgecolor = "k",facecolor = "none", alpha = 0.95, zorder = 4.5, label = "")

                if(kalib):

                    kal = pd.read_csv("/home/s1675537/PhD/LSDTopoData/knickpoint/test_location_paper/Smugglers_SC/field_kp/calib_jointed.csv")
                    kal = kal[kal[binning] == sources]
                    colaray = kal["type"].values
                    colaray[colaray == "bases"] = "#A002D3"
                    colaray[colaray == "lips"] = "#57B300"
                    ax1.scatter(kal[x_axis],kal["elevation"], marker = "x", s = 7, lw = 0.4, c = colaray, label = "", zorder = 250)    

                # Cleaning the legend
                handles, labels = ax1.get_legend_handles_labels()
                thandles= []
                tlabels = []
                for l in range(len(labels)):
                    if labels[l] != "":
                        thandles.append(handles[l])
                        tlabels.append(labels[l])
           
                ax1.legend(handles,labels)

                if(x_axis == "chi"):
                    ax1.set_xlabel(r"$\chi$ (m)")
                else:
                    ax1.set_xlabel("Distance from the outlet (m)")

                ax1.set_ylabel("z (m)")

                # Saving the figure
                particule = "_classical_%s_%s_%s"%(binning,sources,x_axis)
                if(neg):
                    particule+= "_drop"
                if(pos):
                    particule+= "_raise"
                if(step):
                    particule+= "_step"                

                plt.savefig(out_directory + self.fprefix+particule + ".%s"%(format), dpi = 500)
                plt.clf()
            # switching to the next figure


    def print_classic_map(self,size = "big", format = "png", black_bg = False, scale_points = True, label_size = 8, size_kp = 20, return_fig = False, 
        extent_cmap = [], kalib = False,lith_raster = False,cml = None, unicolor_kp = None, size_stepped_kp_map = 1.8, neg = True, pos = False, step = False):

            # check if a directory exists for the chi plots. If not then make it.
        raster_directory = self.fpath+'raster_plots/'
        if not os.path.isdir(raster_directory):
            os.makedirs(raster_directory)

        # Set up fonts for plots
        rcParams['font.family'] = 'sans-serif'
        rcParams['font.sans-serif'] = ['Liberation Sans'] # Liberation Sans is a free alternative to Arial. Albeit being quite universal, Arial is propietary. #PRAISE_FREE_AND_OPENSOURCE
        rcParams['font.size'] = label_size

        # set figure sizes based on format
        fig_width_inches = self.get_figwidth_right_size(size = size)

        # get the rasters
        raster_ext = '.bil'
        BackgroundRasterName = self.fprefix+raster_ext
        HillshadeName = self.fprefix+'_hs'+raster_ext
        BasinsName = self.fprefix+'_AllBasins'+raster_ext

        
        # create the map figure
        MF = MapFigure(HillshadeName, self.fpath, coord_type="UTM_km", alpha = 0.7)
        if(black_bg):
            MF.add_drape_image(HillshadeName,self.fpath,colourmap = "gray",alpha=1,colour_min_max = [10000,10001],modify_raster_values=False,old_values=[], new_values=[],NFF_opti = True)

        if(lith_raster and cml != None):
            color_map_litho  = cml
            df_litho_size = pd.read_csv(self.fpath+self.fprefix+"_lithokey.csv")
            LithoMap = self.fprefix+"_LITHRAST.bil"

            MF.add_drape_image(LithoMap,self.fpath,colourmap = color_map_litho,
                                alpha=0.6,
                                show_colourbar = False,
                                colorbarlabel = "Colourbar", discrete_cmap=False, 
                                norm = "None",
                                colour_min_max = [0,df_litho_size["rocktype"].max()-1],
                                modify_raster_values=False,
                                old_values=[], new_values=[], cbar_type=int,
                                NFF_opti = True, custom_min_max = [])
        
        # plot the basin outlines
        Basins = LSDV.GetBasinOutlines(self.fpath, BasinsName)
        MF.plot_polygon_outlines(Basins, linewidth = 0.5)

        # add the channel network without color
        ## First calibration of the points: I calculated a ratio of niceness from different tests - Probably need more work to adapt it to different figure styles
        min_corrected = size_kp*0.4/20
        max_corrected = size_kp*1.5/20
        raw_min_corrected = size_kp*0.1/20
        raw_max_corrected = size_kp*0.4/20

        rivnet = LSDP.LSDMap_PointData(self.df_river, data_type = "pandas", PANDEX = True)
        rawriv = LSDP.LSDMap_PointData(self.df_rivraw, data_type = "pandas", PANDEX = True)
        MF.add_point_data(rawriv, show_colourbar="False", scale_points=True,scaled_data_in_log= True, column_for_scaling='drainage_area',alpha=0.1,max_point_size = raw_max_corrected,min_point_size = raw_min_corrected,zorder=195)
        MF.add_point_data(rivnet, column_for_plotting = "m_chi", show_colourbar="False", scale_points=True,scaled_data_in_log= True, column_for_scaling='drainage_area',alpha=0.1,max_point_size = max_corrected,min_point_size = min_corrected,zorder=195)

        # add the knickpoints plots

        kp_pos = LSDP.LSDMap_PointData(self.df_kp[self.df_kp["sign"] == 1], data_type = "pandas", PANDEX = True)
        kp_neg = LSDP.LSDMap_PointData(self.df_kp[self.df_kp["sign"] == -1], data_type = "pandas", PANDEX = True)
        kp_step = LSDP.LSDMap_PointData(self.df_kp_stepped[self.df_kp_stepped["delta_segelev"] > 0], data_type = "pandas", PANDEX = True)

        # Dealing with the legend that you can manually change
        if(len(extent_cmap) != 2):
            extent_cmap = [0,self.df_kp["delta_ksn"].abs().max()]


        # Ignore that, that is for paper purposes
        if(kalib):

            kal = pd.read_csv("/home/s1675537/PhD/LSDTopoData/knickpoint/test_location_paper/Smugglers_SC/field_kp/calib_jointed.csv")
            colaray = kal["type"].values
            colaray[colaray == "bases"] = "#A002D3"
            colaray[colaray == "lips"] = "#57B300"
            kal["van_gogh"] = pd.Series(data = colaray, index = kal.index )
            kalpoint = LSDP.LSDMap_PointData(kal, data_type = "pandas", PANDEX = True)
            MF.add_point_data(kalpoint, marker ="*", column_for_plotting = "van_gogh", alpha=1, manual_size = 20, zorder = 250, unicolor = kal["van_gogh"].values )


        if(unicolor_kp == None):
            if(pos):
                MF.add_point_data(kp_pos,unicolor = "red", black_contours = True, marker ="o", scale_points = scale_points, scaled_data_in_log= False, column_for_scaling = 'size_kp', scale_in_absolute = True , alpha=1, max_point_size = 50, min_point_size = 4, zorder=200)
            if(neg):
                MF.add_point_data(kp_neg,unicolor = "#00F1EA", black_contours = True, marker ="o", scale_points = scale_points, scaled_data_in_log= False, column_for_scaling = 'size_kp', scale_in_absolute = True , alpha=1, max_point_size = 50, min_point_size = 4, zorder=200)
        
        else:
            if(pos):
                MF.add_point_data(kp_pos, unicolor = unicolor_kp, black_contours = True, marker ="o", scale_points = scale_points, alpha=1, max_point_size = self.df_kp[self.df_kp["sign"] == 1]["size_kp"].max(), min_point_size = self.df_kp[self.df_kp["sign"] == 1]["size_kp"].min(),zorder=200,manual_size = size_kp)
            if(neg):
                MF.add_point_data(kp_neg, unicolor = unicolor_kp, black_contours = True, marker ="o", scale_points = scale_points, alpha=1, max_point_size = self.df_kp[self.df_kp["sign"] == -1]["size_kp"].max(), min_point_size = self.df_kp[self.df_kp["sign"] == -1].min(),zorder=200,manual_size = size_kp)

        if(step):
            MF.add_point_data(kp_step,unicolor = "#CB9A00",marker ="o", black_contours = True,scale_points = False,scaled_data_in_log = False, column_for_scaling = "none", scale_in_absolute = True, max_point_size = 50, min_point_size = 49, alpha=1,zorder=300,manual_size=50)

        if(black_bg):
            suffix = "dark"
        elif (lith_raster == True and cml != None):
            suffix = "lith"
        else:
            suffix = "hs"

        if(neg):
            suffix += "_drop"
        if(pos):
            suffix += "_raise"
        if(step):
            suffix += "_step" 

        ImageName = raster_directory+self.fprefix+"_ksnkp_map_%s."%(suffix) + format
        

        if(return_fig):
            return plt.gcf()

        else:
            MF.save_fig(fig_width_inches = fig_width_inches, FigFileName = ImageName, FigFormat = format, Fig_dpi = 500) # Save the figure
            plt.clf()




    def print_map_of_kp(self,size = "big", format = "png", black_bg = False, scale_points = True, label_size = 8, size_kp = 20, return_fig = False, 
        extent_cmap = [], kalib = False,lith_raster = False,cml = None, unicolor_kp = None, size_stepped_kp_map = 1.8):

            # check if a directory exists for the chi plots. If not then make it.
        raster_directory = self.fpath+'raster_plots/'
        if not os.path.isdir(raster_directory):
            os.makedirs(raster_directory)

        # Set up fonts for plots
        rcParams['font.family'] = 'sans-serif'
        rcParams['font.sans-serif'] = ['Liberation Sans'] # Liberation Sans is a free alternative to Arial. Albeit being quite universal, Arial is propietary. #PRAISE_FREE_AND_OPENSOURCE
        rcParams['font.size'] = label_size

        # set figure sizes based on format
        fig_width_inches = self.get_figwidth_right_size(size = size)

        # get the rasters
        raster_ext = '.bil'
        BackgroundRasterName = self.fprefix+raster_ext
        HillshadeName = self.fprefix+'_hs'+raster_ext
        BasinsName = self.fprefix+'_AllBasins'+raster_ext

        
        # create the map figure
        MF = MapFigure(HillshadeName, self.fpath, coord_type="UTM_km", alpha = 0.7)
        if(black_bg):
            MF.add_drape_image(HillshadeName,self.fpath,colourmap = "gray",alpha=1,colour_min_max = [10000,10001],modify_raster_values=False,old_values=[], new_values=[],NFF_opti = True)

        if(lith_raster and cml != None):
            color_map_litho  = cml
            df_litho_size = pd.read_csv(self.fpath+self.fprefix+"_lithokey.csv")
            LithoMap = self.fprefix+"_LITHRAST.bil"

            MF.add_drape_image(LithoMap,self.fpath,colourmap = color_map_litho,
                                alpha=0.6,
                                show_colourbar = False,
                                colorbarlabel = "Colourbar", discrete_cmap=False, 
                                norm = "None",
                                colour_min_max = [0,df_litho_size["rocktype"].max()-1],
                                modify_raster_values=False,
                                old_values=[], new_values=[], cbar_type=int,
                                NFF_opti = True, custom_min_max = [])
        
        # plot the basin outlines
        Basins = LSDP.GetBasinOutlines(self.fpath, BasinsName)
        MF.plot_polygon_outlines(Basins, linewidth = 0.5)

        # add the channel network without color
        ## First calibration of the points: I calculated a ratio of niceness from different tests - Probably need more work to adapt it to different figure styles
        min_corrected = size_kp*0.4/20
        max_corrected = size_kp*1.5/20
        raw_min_corrected = size_kp*0.1/20
        raw_max_corrected = size_kp*0.4/20

        rivnet = LSDP.LSDMap_PointData(self.df_river, data_type = "pandas", PANDEX = True)
        rawriv = LSDP.LSDMap_PointData(self.df_rivraw, data_type = "pandas", PANDEX = True)
        MF.add_point_data(rawriv, show_colourbar="False", scale_points=True,scaled_data_in_log= True, column_for_scaling='drainage_area',alpha=0.1,max_point_size = raw_max_corrected,min_point_size = raw_min_corrected,zorder=195)
        MF.add_point_data(rivnet, column_for_plotting = "m_chi", show_colourbar="False", scale_points=True,scaled_data_in_log= True, column_for_scaling='drainage_area',alpha=0.1,max_point_size = max_corrected,min_point_size = min_corrected,zorder=195)

        # add the knickpoints plots

        kp_pos = LSDP.LSDMap_PointData(self.df_kp[self.df_kp["sign"] == 1], data_type = "pandas", PANDEX = True)
        kp_neg = LSDP.LSDMap_PointData(self.df_kp[self.df_kp["sign"] == -1], data_type = "pandas", PANDEX = True)
        kp_step = LSDP.LSDMap_PointData(self.df_kp_stepped[self.df_kp_stepped["delta_segelev"] > 0], data_type = "pandas", PANDEX = True)

        # Dealing with the legend that you can manually change
        if(len(extent_cmap) != 2):
            extent_cmap = [0,self.df_kp["delta_ksn"].abs().max()]


        # Ignore that, that is for paper purposes
        if(kalib):

            kal = pd.read_csv("/home/s1675537/PhD/LSDTopoData/knickpoint/test_location_paper/Smugglers_SC/field_kp/calib_jointed.csv")
            colaray = kal["type"].values
            colaray[colaray == "bases"] = "#A002D3"
            colaray[colaray == "lips"] = "#57B300"
            kal["van_gogh"] = pd.Series(data = colaray, index = kal.index )
            kalpoint = LSDP.LSDMap_PointData(kal, data_type = "pandas", PANDEX = True)
            MF.add_point_data(kalpoint, marker ="*", column_for_plotting = "van_gogh", alpha=1, manual_size = 20, zorder = 199, unicolor = kal["van_gogh"].values )


        if(unicolor_kp == None):
            MF.add_point_data(kp_pos,unicolor = "r", marker ="^", scale_points = scale_points, scaled_data_in_log= False, column_for_scaling = 'size_kp', scale_in_absolute = True , alpha=1, max_point_size = 15, min_point_size = 4, zorder=200)
            MF.add_point_data(kp_neg,unicolor = "b", marker ="v", scale_points = scale_points, scaled_data_in_log= False, column_for_scaling = 'size_kp', scale_in_absolute = True , alpha=1, max_point_size = 15, min_point_size = 4, zorder=200)
        
        else:
            MF.add_point_data(kp_pos, unicolor = unicolor_kp, marker ="^", scale_points = scale_points, alpha=1, max_point_size = self.df_kp[self.df_kp["sign"] == 1]["size_kp"].max(), min_point_size = self.df_kp[self.df_kp["sign"] == 1]["size_kp"].min(),zorder=200,manual_size = size_kp)
            MF.add_point_data(kp_neg, unicolor = unicolor_kp, marker ="v", scale_points = scale_points, alpha=1, max_point_size = self.df_kp[self.df_kp["sign"] == -1]["size_kp"].max(), min_point_size = self.df_kp[self.df_kp["sign"] == -1].min(),zorder=200,manual_size = size_kp)


        MF.add_point_data(kp_step,unicolor = "#CB9A00",marker ="s", column_for_plotting = "none", max_point_size = 50, min_point_size = 10, alpha=1,zorder=200,)

        if(black_bg):
            suffix = "dark"
        elif (lith_raster == True and cml != None):
            suffix = "lith"
        else:
            suffix = "hs"
        ImageName = raster_directory+self.fprefix+"_ksnkp_map_%s."%(suffix) + format
        

        if(return_fig):
            return plt.gcf()

        else:
            MF.save_fig(fig_width_inches = fig_width_inches, FigFileName = ImageName, FigFormat = format, Fig_dpi = 500) # Save the figure
            plt.clf()





    def print_histogram(self,size = "big", format = "png", label_size = 8, n_bin = 'auto', facecolor = "white", grid = True, data = "delta_ksn", x_extents = []):
        """
        This figure print an histogram of the knickpoint repartition for the selected basins/sources

        """

        print("I am plotting an histogram for the " + data)
        # check if a directory exists for the chi plots. If not then make it.
        out_directory = self.fpath+'stat_plots/'
        if not os.path.isdir(out_directory):
            print("I am creating the river_plot/ directory to save your figures")
            os.makedirs(out_directory)

        
        # Set up fonts for plots
        rcParams['font.family'] = 'sans-serif'
        rcParams['font.sans-serif'] = ['Liberation Sans'] # Liberation Sans is a free alternative to Arial. Albeit being quite universal, Arial is propietary. #PRAISE_FREE_AND_OPENSOURCE
        rcParams['font.size'] = label_size


        # Create a figure with required dimensions
        n_axis = 1
        fig = self.get_fig_right_size(size = size, n_axis =1 , facecolor = facecolor)

        gs = plt.GridSpec(100,100,bottom=0.15, left=0.10, right=0.95, top=0.95)
        ax1 = fig.add_subplot(gs[0:100,0:100], facecolor = "white")

        if(grid):
             ax1.grid(ls = 'dotted', lw = 0.1, c = "k", zorder = 5)

        if (data == "delta_segelev"):
            tdf = self.df_kp.copy()
            tdf[tdf[data]<0] = 0
        else:
            tdf = self.df_kp.copy()

        # I am thinning the data by removing all the 0 values
        tdf = tdf[tdf[data]!=0]

        ax1.hist(tdf[data], bins = n_bin, fc = "#848484", lw = 0.5, edgecolor = "k", zorder = 10)
        
        print("I printed the histogram")
        ## saving the y limits
        limites_y = ax1.get_ylim() 
        print("printing the vlines")

        ax1.vlines(tdf[data].quantile(0.25), 0 , 1 , transform = ax1.get_xaxis_transform(), lw = 0.7, linestyles = 'dashed', zorder = 11)
        ax1.vlines(tdf[data].quantile(0.75), 0 , 1 , transform = ax1.get_xaxis_transform(), lw = 0.7, linestyles = 'dashed', zorder = 11)

        ## resetting the y limits - this is a trick to get full vertical lines
        ax1.set_ylim(limites_y)

        # delaing with x extents
        if len(x_extents) ==2:
            ax1.set_xlim(x_extents[0],x_extents[1])

        print("setting the text")

        ax1.text(0.65,0.8,"Mean: %.3f " %(tdf[data].mean())+"\n"+ r"Median: %.3f " %(tdf[data].median())+"\n"+ r"1$^{st}$/3$^{rd}$ quartiles: %.3f/%.3f" %(tdf[data].quantile(0.25),tdf[data].quantile(0.75)) +"\n" + "# knickpoints: %s" %(tdf[data][tdf[data] != 0].shape[0]),zorder = 50, transform=ax1.transAxes)

        if(data == "delta_ksn"):
            xlab = r"$\Delta k_{sn}$"
        elif (data == "delta_segelev"):
            xlab = r"$\Delta$ seg. elevation"

        ax1.set_xlabel(xlab)
        ax1.set_ylabel("n knickpoints")

        ax1.xaxis.set_major_locator(MaxNLocator(15))

        print("saving the figure")

        plt.savefig(out_directory + self.fprefix + "_kp_hist%s.%s"%(data,format), dpi = 500)
        plt.clf()


    def print_box_and_whisker(self,size = "big", format = "png", label_size = 8, binning = 'source_key', facecolor = "white", grid = True):
        """
        This figure print an histogram of the knickpoint repartition for the selected basins/sources

        """


        # check if a directory exists for the chi plots. If not then make it.
        out_directory = self.fpath+'stat_plots/'
        if not os.path.isdir(out_directory):
            print("I am creating the river_plot/ directory to save your figures")
            os.makedirs(out_directory)

        
        # Set up fonts for plots
        rcParams['font.family'] = 'sans-serif'
        rcParams['font.sans-serif'] = ['Liberation Sans'] # Liberation Sans is a free alternative to Arial. Albeit being quite universal, Arial is propietary. #PRAISE_FREE_AND_OPENSOURCE
        rcParams['font.size'] = label_size


        # Create a figure with required dimensions
        n_axis = 1
        fig = self.get_fig_right_size(size = size, n_axis =1 , facecolor = facecolor)

        gs = plt.GridSpec(100,100,bottom=0.15, left=0.10, right=0.95, top=0.95)
        ax1 = fig.add_subplot(gs[0:100,0:100], facecolor = "white")

        if(grid):
            ax1.grid(ls = 'dotted', lw = 0.3, c = "k", zorder = 5)


        # aggregating the data
        data_to_plot = []
        data_name = []
        n_data = []

        for bing in self.df_kp[binning].unique():
            if(self.df_kp["delta_ksn"][self.df_kp[binning] == bing].shape[0]>0):
                data_to_plot.append(self.df_kp["delta_ksn"][self.df_kp[binning] == bing].values)
                data_name.append(str(bing) + "\nn = "+str(self.df_kp["delta_ksn"][self.df_kp[binning] == bing].shape[0]))
                #n_data.append(self.df_kp["delta_ksn"][self.df_kp[binning] == bing].shape[0])



        bp = ax1.boxplot(data_to_plot, labels = data_name, patch_artist = True)

        ## change outline color, fill color and linewidth of the boxes
        for box in bp['boxes']:
            # change outline color
            box.set( color='k', linewidth=1.5)
            # change fill color
            box.set( facecolor = '#848484' )

        ## change color and linewidth of the whiskers
        for whisker in bp['whiskers']:
            whisker.set(color='k', linewidth=1)

        ## change color and linewidth of the caps
        for cap in bp['caps']:
            cap.set(color='#2C2C2C', linewidth=1)

        ## change color and linewidth of the medians
        for median in bp['medians']:
            median.set(color='#E0E0E0', linewidth=1)

        ## change the style of fliers and their fill
        for flier in bp['fliers']:
            flier.set(marker='+', color='#A0A0A0', alpha=0.5)

        ax1.set_ylabel(r"$\Delta k_{sn}$")

        if(binning == "source_key"):
            xlabo = "Source keys"
        elif(binning == "basin_key"):
            xlabo = "Basin keys"
        else:
            xlabo = binning

        ax1.set_xlabel(xlabo)


        plt.savefig(out_directory + self.fprefix + "_kp_baw_%s.%s"%(binning,format), dpi = 500)
        plt.clf()


    def print_map_topo(self,size = "big", format = "png", label_size = 8, return_fig = False, extent_cmap = [], kalib = False):

        # check if a directory exists for the chi plots. If not then make it.
        raster_directory = self.fpath+'raster_plots/'
        if not os.path.isdir(raster_directory):
            os.makedirs(raster_directory)

        # Set up fonts for plots
        rcParams['font.family'] = 'sans-serif'
        rcParams['font.sans-serif'] = ['Liberation Sans'] # Liberation Sans is a free alternative to Arial. Albeit being quite universal, Arial is propietary. #PRAISE_FREE_AND_OPENSOURCE
        rcParams['font.size'] = label_size

        # set figure sizes based on format
        fig_width_inches = self.get_figwidth_right_size(size = size)

        # get the rasters
        raster_ext = '.bil'

        # create the map figure
        HillshadeName = self.fprefix+'_hs'+raster_ext
        MF = MapFigure(HillshadeName, self.fpath, coord_type="UTM_km", alpha = 0.7)
        MF.add_drape_image(HillshadeName,self.fpath,colourmap = "gray",alpha=1,NFF_opti = True)
        

        try:
            BackgroundRasterName = self.fprefix+"_PP"+raster_ext
            MF.add_drape_image(BackgroundRasterName,self.fpath,colourmap = "terrain",alpha=0.4,NFF_opti = True)
        except Exception as e:
            BackgroundRasterName = self.fprefix+raster_ext
            MF.add_drape_image(BackgroundRasterName,self.fpath,colourmap = "terrain",alpha=0.4,NFF_opti = True)
        
        # Ignore that, it is specific for the paper need       
        if(kalib):

            kal = pd.read_csv("/home/s1675537/PhD/LSDTopoData/knickpoint/test_location_paper/Smugglers_SC/field_kp/calib_jointed.csv")
            colaray = kal["type"].values
            colaray[colaray == "bases"] = "#A002D3"
            colaray[colaray == "lips"] = "#57B300"
            kal["van_gogh"] = pd.Series(data = colaray, index = kal.index )
            kalpoint = LSDP.LSDMap_PointData(kal, data_type = "pandas", PANDEX = True)
            MF.add_point_data(kalpoint, marker ="*", column_for_plotting = "van_gogh", alpha=1, manual_size = 20, zorder = 199, unicolor = kal["van_gogh"].values )



        ImageName = raster_directory+self.fprefix+"_topo_map."+ format
        

        if(return_fig):
            return plt.gcf()

        else:
            MF.save_fig(fig_width_inches = fig_width_inches, FigFileName = ImageName, FigFormat = format, Fig_dpi = 500) # Save the figure
            plt.clf()




    def stradivarius_analysis(self,size = "big", format = "png", label_size = 8):
        """
        Will plot a compilation of violin plot to illustrate  the distribution of the knickpoints function to some parameters
        """
        # check if a directory exists for the chi plots. If not then make it.
        out_directory = self.fpath+'stat_plots/'
        if not os.path.isdir(out_directory):
            print("I am creating the river_plot/ directory to save your figures")
            os.makedirs(out_directory)

        
        # Set up fonts for plots
        rcParams['font.family'] = 'sans-serif'
        rcParams['font.sans-serif'] = ['Liberation Sans'] # Liberation Sans is a free alternative to Arial. Albeit being quite universal, Arial is propietary. #PRAISE_FREE_AND_OPENSOURCE
        rcParams['font.size'] = label_size


        # Create a figure with required dimensions
        n_axis = 1
        fig = self.get_fig_right_size(size = size, n_axis =1 , facecolor = "white")

        toplot = ["elevation", "chi" , "flow_distance", "drainage_area"]
        color = ["#CACACA","#29E1DF","#0049FF", "#03C845"]
        xalabela = ["Elevation (m)", r"$\chi$ (m)", "Distance from\n outlet (m)", r"DA ($m^{2}$)"]
        paddy = [-15,-15,-15,-12]


        ax = []
        n_vplot = len(toplot)
        gs = plt.GridSpec(1,n_vplot,bottom=0.15, left=0.10, right=0.95, top=0.95)
        for triceratops in range(n_vplot):
            ax.append(fig.add_subplot(gs[0,triceratops], facecolor = "white"))
            vp = ax[-1].violinplot(self.df_kp[toplot[triceratops]].values, bw_method = 0.05, widths = 0.75 , points = 1000000)
            vp['cbars'].set(color = "#5B5B5B", linewidths = 1, alpha =0.75 )
            vp['cmins'].set(color = "#5B5B5B", linewidths = 1 , alpha =0.75)
            vp['cmaxes'].set(color = "#5B5B5B", linewidths = 1 , alpha =0.75)
            for bulbe in vp['bodies']:
                bulbe.set(color = color[triceratops], alpha = 1)
            ax[-1].set_xlabel(xalabela[triceratops])
            ax[-1].set_xticks([1])
            ax[-1].set_xticklabels([""])
            ax[-1].grid(alpha = 0.5)


            ax[-1].get_yaxis().set_tick_params(direction='in', pad = paddy[triceratops], size = 4)
            for tick in ax[-1].yaxis.get_major_ticks():
                tick.label.set_fontsize(4) 
            if(toplot[triceratops] == "drainage_area"):
                ax[-1].set_yscale("log", nonposy='clip')



        plt.subplots_adjust(left=0.05, bottom=0.05, right=0.95, top=0.95, wspace=0.05, hspace=0.05)
        plt.savefig(out_directory + self.fprefix + "_stradivarius.%s"%(format), dpi = 500)
        plt.clf()






    def save_output_csv(self):
        """
        If selected, will save the selected knickpoints into a csv file
        """

        print("I am saving the selected knickpoints in a new csv file")
        self.df_kp_ksn.to_csv(self.fpath+self.fprefix+"_output_ksn.csv", index = False)
        self.df_kp_stepped.to_csv(self.fpath+self.fprefix+"_output_stepped.csv", index = False)
        for i in self.df_kp_ksn['basin_key'].unique():
            tdf = self.df_kp_ksn[self.df_kp_ksn['basin_key'] == i]
            tdf.to_csv(self.fpath+self.fprefix+"_output_ksn_basin_%s.csv"%(i), index = False)






#
#
#
#
#
#
# 
# 
# 
# 
# 
# 
# 
# 
# 
# 
# 
# End of the file, I am adding these blank lines before because my text editor delete them when I save but I hate working on the extreme bottom of my screen


#                      ____,------------------,______
#                  ___/    \            /            \_____
#               __/         \__________/              \___ \___
# ,^------.____/\           /          \              /   `----\_
# | (O))      /  \_________/            \____________/         \ \
# \_____,--' /   /         \            /            \          \ \
#   \___,---|___/_______,---`----------'----,_________\__________\_\
#             /  :__________________________/  :___________________/
#            /   :          /   :          /   :          /   :
#           /    :         /    :         /    :         /    :
#       (~~~     )     (~~~     )     (~~~     )     (~~~     )
#        ~~~~~~~~       ~~~~~~~~       ~~~~~~~~       ~~~~~~~~
