diff --git a/README.md b/README.md
index edeea9b95470245d46bc0c9a3d63392594da8965..be4b1492968aa2fc1bae9681463e98e4a709a7c1 100755
--- a/README.md
+++ b/README.md
@@ -97,6 +97,7 @@ The present repository of Cute Little R functions is for beta testing. Ultimatel
 | **fun_gg_point_rast()** | ggplot2 raster scatterplot layer |
 | **fun_gg_boxplot()** | ggplot2 boxplot + dots + means + median/mean values |
 | **fun_gg_scatter()** | ggplot2 scatterplot + lines (up to 6 overlays totally) |
+| **fun_gg_donut()** | ggplot2 donut |
 | **fun_gg_empty_graph()** | generate an empty graphic device with text in the middle |
 
 | Graphic extraction | |
@@ -172,7 +173,9 @@ Gitlab developers
 
 ### v11.9.0
 
-Argument inf.values added in the check() function
+1) Argument inf.values added in the check() function
+
+2) gg_donut added
 
 
 ### v11.8.0
diff --git a/cute_little_R_functions.R b/cute_little_R_functions.R
index 21d2e22c44a9ce43e9ad7b98bbffc6594fb91495..d7de73f9bf1112e8cebb829e8f428de01fd496c5 100755
--- a/cute_little_R_functions.R
+++ b/cute_little_R_functions.R
@@ -9140,6 +9140,711 @@ fun_get_message <- function(
 
 
 
+fun_gg_donut <- function(
+    data1, 
+    freq, 
+    categ, 
+    fill.palette = NULL,
+    fill.color = NULL, 
+    hole.size = 0.5, 
+    hole.text.size = 14, 
+    border.color = "gray50", 
+    border.size = 0.2, 
+    title = "", 
+    title.text.size = 12, 
+    annotation = NULL,
+    annotation.distance = 0,
+    annotation.size = 3,
+    annotation.force = 1,
+    annotation.force.pull = 100,
+    legend.show = TRUE, 
+    legend.width = 0.25, 
+    legend.name = NULL, 
+    legend.limit = NULL, 
+    legend.add.prop = FALSE,
+    add = NULL, 
+    return = FALSE, 
+    return.ggplot = FALSE,
+    return.gtable = TRUE,
+    plot = TRUE, 
+    warn.print = FALSE, 
+    lib.path = NULL
+){
+    # AIM
+    # Plot a ggplot2 donut using contingency data, systematically in the decreasing order of frequencies, starting at the top and turning clockwise
+    # For ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html
+    # WARNINGS
+    # Rows containing NA in data1[, c(freq, categ)] will be removed before processing, with a warning (see below)
+    # Size arguments (hole.text.size, border.size, title.text.size and annotation.size) are in mm. See Hadley comment in https://stackoverflow.com/questions/17311917/ggplot2-the-unit-of-size. See also http://sape.inf.usi.ch/quick-reference/ggplot2/size). Unit object are not accepted, but conversion can be used (e.g., grid::convertUnit(grid::unit(0.2, "inches"), "mm", valueOnly = TRUE))
+    # ARGUMENTS
+    # data1: a dataframe compatible with ggplot2
+    # freq: single character string of the data1 column name of the frequencies
+    # categ: single character string of the data1 column name of categories (qualitative variable)
+    # fill.palette: single character string of a palette name (see ?ggplot2::scale_fill_brewer() for the list).Ignored if fill.color is not NULL
+    # fill.color: either (1) NULL, or (2) a vector of character strings or integers of same length as the number of classes in categ. Colors can be color names (see ?colors() in R), hexadecimal color codes, or integers (according to the ggplot2 palette). The order of the elements will be used according to the frequency values, from highest to lowest. An easy way to use this argument is to sort data1 according to the frequencies values, add a color column with the corresponding desired colors and use the content of this column as values of fill.color. If color is NULL and fill.palette is NULL, default colors of ggplot2 are used. If color is not NULL, it overrides fill.palette
+    # hole.size: single positive proportion of donut central hole, 0 meaning no hole and 1 no donut
+    # hole.text.size: single positive numeric value of the title font size in mm
+    # border.color: a single character string or integer. Colors can be color names (see ?colors() in R), hexadecimal color codes, or integers (according to the ggplot2 palette)
+    # border.size: single numeric value of border tickness in mm. Write zero for no dot border
+    # title: single character string of the graph title
+    # title.text.size: single numeric value of the title font size in mm
+    # annotation: single character string of the data1 column name of annotations. Values inside this column will be displayed over the corresponding slices of the donut. Write NULL if not required
+    # annotation.distance: single positive numeric value of the distance from the center of the slice. 0 means center of the slice, 0.5 means at the edge. Above 0.5, the donut will be reduced to make place for the annotation. Ignored if annotation is NULL
+    # annotation.size: single positive numeric value of the annotation font size in mm. Ignored if annotation is NULL
+    # annotation.force: single positive numeric value of the force of repulsion between overlapping text labels. See ?ggrepel::geom_text_repel() in R. Ignored if annotation is NULL
+    # annotation.force.pull: single positive numeric value of the force of attraction between a text label and its corresponding data point. See ?ggrepel::geom_text_repel() in R. Ignored if annotation is NULL
+    # legend.show: logical (either TRUE or FALSE). Show legend? 
+    # legend.width: single proportion (between 0 and 1) indicating the relative width of the legend sector (on the right of the plot) relative to the width of the plot. Value 1 means that the window device width is split in 2, half for the plot and half for the legend. Value 0 means no room for the legend, which will overlay the plot region. Write NULL to inactivate the legend sector. In such case, ggplot2 will manage the room required for the legend display, meaning that the width of the plotting region can vary between graphs, depending on the text in the legend
+    # legend.name: character string of the legend title. If legend.name is NULL then legend.name is the value of the categ argument. Write legend.name = "" to remove the legend
+    # legend.limit: single positive proportion of the classes displayed in the legend for which the corresponding proportion is over legend.limit. Write NULL to display all the classes
+    # legend.add.prop: logical (either TRUE or FALSE). add the proportion after the class names in the legend ?
+    # add: character string allowing to add more ggplot2 features (dots, lines, themes, facet, etc.). Ignored if NULL
+    # WARNING: (1) the string must start with "+", (2) the string must finish with ")" and (3) each function must be preceded by "ggplot2::". Example: "+ ggplot2::coord_flip() + ggplot2::theme_bw()"
+    # If the character string contains the "ggplot2::theme" string, then the article argument of fun_gg_donut() (see above) is ignored with a warning. In addition, some arguments can be overwritten, like x.angle (check all the arguments)
+    # Handle the add argument with caution since added functions can create conflicts with the preexisting internal ggplot2 functions
+    # WARNING: the call of objects inside the quotes of add can lead to an error if the name of these objects are some of the fun_gg_donut() arguments. Indeed, the function will use the internal argument instead of the global environment object. Example article <- "a" in the working environment and add = '+ ggplot2::ggtitle(article)'. The risk here is to have TRUE as title. To solve this, use add = '+ ggplot2::ggtitle(get("article", envir = .GlobalEnv))'
+    # return: logical (either TRUE or FALSE). Return the graph parameters?
+    # return.ggplot: logical (either TRUE or FALSE). Return the ggplot object in the output list? Ignored if return argument is FALSE. WARNING: always assign the fun_gg_donut() function (e.g., a <- fun_gg_donut()) into something if the return.ggplot argument is TRUE, otherwise, double plotting is performed. See $ggplot in the RETURN section below for more details
+    # return.gtable: logical (either TRUE or FALSE). Return the ggplot object as gtable of grobs in the output list? Ignored if plot argument is FALSE. Indeed, the graph must be plotted to get the grobs dispositions. See $gtable in the RETURN section below for more details
+    # plot: logical (either TRUE or FALSE). Plot the graphic? If FALSE and return argument is TRUE, graphical parameters and associated warnings are provided without plotting
+    # warn.print: logical (either TRUE or FALSE). Print warnings at the end of the execution? ? If FALSE, warning messages are never printed, but can still be recovered in the returned list. Some of the warning messages (those delivered by the internal ggplot2 functions) are not apparent when using the argument plot = FALSE
+    # lib.path: vector of character strings indicating the absolute path of the required packages (see below). if NULL, the function will use the R library default folders
+    # RETURN
+    # a donut plot if plot argument is TRUE
+    # a list of the graph info if return argument is TRUE:
+    # $data: the initial data with modifications and with graphic information added
+    # $removed.row.nb: a list of the removed rows numbers in data frames (because of NA). NULL if no row removed
+    # $removed.rows: a list of the removed rows in data frames (because of NA). NULL if no row removed
+    # $panel: the variable names used for the panels (NULL if no panels). WARNING: NA can be present according to ggplot2 upgrade to v3.3.0
+    # $axes: the x-axis and y-axis info
+    # $warn: the warning messages. Use cat() for proper display. NULL if no warning. WARNING: warning messages delivered by the internal ggplot2 functions are not apparent when using the argument plot = FALSE
+    # $ggplot: ggplot object that can be used for reprint (use print($ggplot) or update (use $ggplot + ggplot2::...). NULL if return.ggplot argument is FALSE. Warning: the legend is not in $ggplot as it is in a separated grob (use $gtable to get it). Of note, a non-null $ggplot in the output list is sometimes annoying as the manipulation of this list prints the plot
+    # $gtable: gtable object that can be used for reprint (use gridExtra::grid.arrange(...$ggplot) or with additionnal grobs (see the grob decomposition in the examples). NULL if return.ggplot argument is FALSE. Contrary to $ggplot, a non-NULL $gtable in the output list is not annoying as the manipulation of this list does not print the plot
+    # REQUIRED PACKAGES
+    # ggplot2
+    # gridExtra
+    # grid
+    # lemon (in case of use in the add argument)
+    # ggrepel
+    # REQUIRED FUNCTIONS FROM THE cute PACKAGE
+    # fun_gg_palette()
+    # fun_gg_get_legend()
+    # fun_pack()
+    # fun_check()
+    # EXAMPLES
+    # obs1 <- data.frame(Km = c(20, 10, 1, 5), Car = c("TUUT", "WIIM", "BIP", "WROUM"), Color1 = 1:4, color2 = c("red", "blue", "green", "black"), Country = c("FR", "UK", "US", NA), stringsAsFactors = TRUE) ; fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car", annotation = "Country")
+    # DEBUGGING
+    # obs1 <- data.frame(Km = c(20, 10, 1, 5), Car = c("TUUT", "WIIM", "BIP", "WROUM"), Color1 = 1:4, color2 = c("red", "blue", "green", "black"), Country = c("FR", "UK", "US", NA), stringsAsFactors = TRUE) ; data1 = obs1 ; freq = "Km" ; categ = "Car" ; fill.palette = NULL ; fill.color = NULL ; hole.size = 0.5 ; hole.text.size = 12 ; border.color = "gray50" ; border.size = 0.1 ; title = "" ; title.text.size = 12 ; annotation = "Country" ; annotation.distance = 0.5 ; annotation.size = 3 ; annotation.force = 1 ; annotation.force.pull = 100 ; legend.show = TRUE ; legend.width = 0.5 ; legend.name = NULL ; legend.limit = NULL ; legend.add.prop = FALSE ; add = NULL ; return = TRUE ; return.ggplot = FALSE ; return.gtable = TRUE ; plot = TRUE ; warn.print = FALSE ; lib.path = NULL
+    # function name
+    function.name <- paste0(as.list(match.call(expand.dots=FALSE))[[1]], "()")
+    arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments
+    arg.user.setting <- as.list(match.call(expand.dots=FALSE))[-1] # list of the argument settings (excluding default values not provided by the user)
+    # end function name
+    # required function checking
+    req.function <- c(
+        "fun_check", 
+        "fun_gg_palette", 
+        "fun_gg_get_legend", 
+        "fun_pack"
+    )
+    tempo <- NULL
+    for(i1 in req.function){
+        if(length(find(i1, mode = "function"))== 0L){
+            tempo <- c(tempo, i1)
+        }
+    }
+    if( ! is.null(tempo)){
+        tempo.cat <- paste0("ERROR IN ", function.name, "\nREQUIRED cute FUNCTION", ifelse(length(tempo) > 1, "S ARE", " IS"), " MISSING IN THE R ENVIRONMENT:\n", paste0(tempo, collapse = "()\n"))
+        stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between ==
+    }
+    # end required function checking
+    # reserved words to avoid bugs (used in this function)
+    # end reserved words to avoid bugs (used in this function)
+    # arg with no default values
+    mandat.args <- c(
+        "data1", 
+        "freq", 
+        "categ"
+    )
+    tempo <- eval(parse(text = paste0("missing(", paste0(mandat.args, collapse = ") | missing("), ")")))
+    if(any(tempo)){
+        tempo.cat <- paste0("ERROR IN ", function.name, "\nFOLLOWING ARGUMENT", ifelse(length(mandat.args) > 1, "S HAVE", "HAS"), " NO DEFAULT VALUE AND REQUIRE ONE:\n", paste0(mandat.args, collapse = "\n"))
+        stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between ==
+    }
+    # end arg with no default values
+    # argument primary checking
+    arg.check <- NULL #
+    text.check <- NULL #
+    checked.arg.names <- NULL # for function debbuging: used by r_debugging_tools
+    ee <- expression(arg.check <- c(arg.check, tempo$problem) , text.check <- c(text.check, tempo$text) , checked.arg.names <- c(checked.arg.names, tempo$object.name))
+    tempo <- fun_check(data = data1, class = "data.frame", na.contain = TRUE, fun.name = function.name) ; eval(ee)
+    tempo <- fun_check(data = freq, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+    tempo <- fun_check(data = categ, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+    if( ! is.null(fill.palette)){
+        tempo <- fun_check(data = fill.palette, options = c("BrBG", "PiYG", "PRGn", "PuOr", "RdBu", "RdGy", "RdYlBu", "RdYlGn", "Spectral", "Accent", "Dark2", "Paired", "Pastel1", "Pastel2", "Set1", "Set2", "Set3", "Blues", "BuGn", "BuPu", "GnBu", "Greens", "Greys", "Oranges", "OrRd", "PuBu", "PuBuGn", "PuRd", "Purples", "RdPu", "Reds", "YlGn", "YlGnBu", "YlOrBr", "YlOrRd"), length = 1, fun.name = function.name) ; eval(ee)
+    }else{
+        # no fun_check test here, it is just for checked.arg.names
+        tempo <- fun_check(data = fill.palette, class = "vector")
+        checked.arg.names <- c(checked.arg.names, tempo$object.name)
+    }
+    if( ! is.null(fill.color)){
+        tempo1 <- fun_check(data = fill.color, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name)
+        tempo2 <- fun_check(data = fill.color, class = "factor", na.contain = TRUE, fun.name = function.name)
+        tempo3 <- fun_check(data = fill.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, neg.values = FALSE, fun.name = function.name) # not need to test inf with integers
+        if(tempo1$problem == TRUE & tempo2$problem == TRUE & tempo3$problem == TRUE){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT MUST BE A VECTOR OF (1) HEXADECIMAL COLOR STRINGS STARTING BY #, OR (2) COLOR NAMES GIVEN BY colors(), OR (3) POSITIVE INTEGER VALUES")
+            text.check <- c(text.check, tempo.cat)
+            arg.check <- c(arg.check, TRUE)
+            checked.arg.names <- c(checked.arg.names, tempo1$object.name)
+        }else if(tempo3$problem == FALSE & any(is.infinite(fill.color))){ # is.infinite() deals with NA as FALSE
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT CANNOT CONTAIN Inf VALUES AMONG POSITIVE INTEGER VALUES")
+            text.check <- c(text.check, tempo.cat)
+            arg.check <- c(arg.check, TRUE)
+            checked.arg.names <- c(checked.arg.names, tempo1$object.name)
+        }else if(tempo3$problem == FALSE & any(fill.color == 0, na.rm = TRUE)){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT CANNOT CONTAIN 0 AMONG POSITIVE INTEGER VALUES")
+            text.check <- c(text.check, tempo.cat)
+            arg.check <- c(arg.check, TRUE)
+            checked.arg.names <- c(checked.arg.names, tempo1$object.name)
+        }
+    }
+    tempo <- fun_check(data = hole.size, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee)
+    tempo <- fun_check(data = hole.text.size, class = "vector", mode = "numeric", neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+    tempo1 <- fun_check(data = border.color, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name)
+    tempo2 <- fun_check(data = border.color, class = "integer", double.as.integer.allowed = TRUE, neg.values = FALSE, na.contain = FALSE, length = 1, fun.name = function.name) # not need to test inf with integers
+    if(tempo1$problem == TRUE & tempo2$problem == TRUE){
+        tempo.cat <- paste0("ERROR IN ", function.name, "\nborder.color ARGUMENT MUST BE A SINGLE CHARACTER STRING OR POSITIVE INTEGER")
+        text.check <- c(text.check, tempo.cat)
+        arg.check <- c(arg.check, TRUE)
+        checked.arg.names <- c(checked.arg.names, tempo1$object.name)
+    }
+    tempo <- fun_check(data = border.size, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+    tempo <- fun_check(data = title, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+    tempo <- fun_check(data = title.text.size, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+    if( ! is.null(annotation)){
+        tempo <- fun_check(data = annotation, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+        tempo <- fun_check(data = annotation.distance, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+        tempo <- fun_check(data = annotation.size, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+        tempo <- fun_check(data = annotation.force, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+        tempo <- fun_check(data = annotation.force.pull, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+    }else{
+        # no fun_check test here, it is just for checked.arg.names
+        tempo <- fun_check(data = annotation, class = "vector")
+        checked.arg.names <- c(checked.arg.names, tempo$object.name)
+    }
+    tempo <- fun_check(data = legend.show, class = "logical", length = 1, fun.name = function.name) ; eval(ee)
+    if( ! is.null(legend.width)){
+        tempo <- fun_check(data = legend.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee)
+    }else{
+        # no fun_check test here, it is just for checked.arg.names
+        tempo <- fun_check(data = legend.width, class = "vector")
+        checked.arg.names <- c(checked.arg.names, tempo$object.name)
+    }
+    if( ! is.null(legend.name)){
+        tempo <- fun_check(data = legend.name, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+    }else{
+        # no fun_check test here, it is just for checked.arg.names
+        tempo <- fun_check(data = legend.name, class = "vector")
+        checked.arg.names <- c(checked.arg.names, tempo$object.name)
+    }
+    if( ! is.null(legend.limit)){
+        tempo <- fun_check(data = legend.limit, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee)
+    }else{
+        # no fun_check test here, it is just for checked.arg.names
+        tempo <- fun_check(data = legend.limit, class = "vector")
+        checked.arg.names <- c(checked.arg.names, tempo$object.name)
+    }
+    tempo <- fun_check(data = legend.add.prop, class = "logical", length = 1, fun.name = function.name) ; eval(ee)
+    if( ! is.null(add)){
+        tempo <- fun_check(data = add, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee)
+    }else{
+        # no fun_check test here, it is just for checked.arg.names
+        tempo <- fun_check(data = add, class = "vector")
+        checked.arg.names <- c(checked.arg.names, tempo$object.name)
+    }
+    tempo <- fun_check(data = return, class = "logical", length = 1, fun.name = function.name) ; eval(ee)
+    tempo <- fun_check(data = return.ggplot, class = "logical", length = 1, fun.name = function.name) ; eval(ee)
+    tempo <- fun_check(data = return.gtable, class = "logical", length = 1, fun.name = function.name) ; eval(ee)
+    tempo <- fun_check(data = plot, class = "logical", length = 1, fun.name = function.name) ; eval(ee)
+    tempo <- fun_check(data = warn.print, class = "logical", length = 1, fun.name = function.name) ; eval(ee)
+    if( ! is.null(lib.path)){
+        tempo <- fun_check(data = lib.path, class = "vector", mode = "character", fun.name = function.name) ; eval(ee) # several possible paths
+    }else{
+        # no fun_check test here, it is just for checked.arg.names
+        tempo <- fun_check(data = lib.path, class = "vector")
+        checked.arg.names <- c(checked.arg.names, tempo$object.name)
+    }
+    if(any(arg.check) == TRUE){
+        stop(paste0("\n\n================\n\n", paste(text.check[arg.check], collapse = "\n"), "\n\n================\n\n"), call. = FALSE) #
+    }
+    # source("C:/Users/Gael/Documents/Git_versions_to_use/debugging_tools_for_r_dev-v1.7/r_debugging_tools-v1.7.R") ; eval(parse(text = str_basic_arg_check_dev)) ; eval(parse(text = str_arg_check_with_fun_check_dev)) # activate this line and use the function (with no arguments left as NULL) to check arguments status and if they have been checked using fun_check()
+    # end argument primary checking
+    # second round of checking and data preparation
+    # management of NA arguments
+    tempo.arg <- names(arg.user.setting) # values provided by the user
+    tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length)== 1L # no argument provided by the user can be just NA
+    if(any(tempo.log) == TRUE){
+        tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE JUST NA")
+        stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between ==
+    }
+    # end management of NA arguments
+    # management of NULL arguments
+    tempo.arg <-c(
+        "data1", 
+        "freq", 
+        "categ", 
+        # "fill.palette", # inactivated because can be null
+        # "fill.color", # inactivated because can be null
+        "hole.size", 
+        "hole.text.size", 
+        "border.color", 
+        "border.size", 
+        "title", 
+        "title.text.size", 
+        # "annotation", # inactivated because can be null
+        "annotation.distance", 
+        "annotation.size", 
+        "annotation.force", 
+        "annotation.force.pull", 
+        "legend.show", 
+        # "legend.width", # inactivated because can be null
+        # "legend.name", # inactivated because can be null
+        # "legend.limit", # inactivated because can be null
+        "legend.add.prop", 
+        # "add", # inactivated because can be null
+        "return", 
+        "return.ggplot", 
+        "return.gtable", 
+        "plot", 
+        "warn.print"
+        # "lib.path" # inactivated because can be null
+    )
+    tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null)
+    if(any(tempo.log) == TRUE){
+        tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL")
+        stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between ==
+    }
+    # end management of NULL arguments
+    # code that protects set.seed() in the global environment
+    # end code that protects set.seed() in the global environment
+    # warning initiation
+    ini.warning.length <- options()$warning.length
+    options(warning.length = 8170)
+    warn <- NULL
+    warn.count <- 0
+    # end warning initiation
+    # other checkings
+    removed.row.nb <- NULL
+    removed.rows <- data.frame(stringsAsFactors = FALSE)
+    data1.ini <- data1 # strictly identical to data1
+    if( ! freq %in% names(data1)){
+        tempo.cat <- paste0("ERROR IN ", function.name, "\nfreq ARGUMENT MUST BE A COLUMN NAME OF THE data1 ARGUMENT")
+        stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+    }else{
+        if(all(is.na(data1[ , freq]) | is.infinite(data1[ , freq]))){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE freq COLUMN OF data1 CANNOT BE JUST NA OR Inf")
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }
+        tempo <- fun_check(data = data1[ , freq], mode = "numeric", neg.values = FALSE, fun.name = function.name)
+        if(tempo$problem == TRUE){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\n", tempo$text)
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }
+        # Inf and NA removal
+        if(any(is.infinite(data1[, freq]) | is.na(data1[, freq]))){
+            warn.count <- warn.count + 1
+            tempo.warn <- paste0("(", warn.count,") PRESENCE OF Inf OR NA VALUES IN THE ", freq, " COLUMN OF THE data1 ARGUMENT AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)")
+            warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+            tempo <- which(is.infinite(data1.ini[, freq]) | is.na(data1.ini[, freq])) # data.ini used for the output
+            removed.row.nb <- c(removed.row.nb, tempo)
+            removed.rows <- rbind(removed.rows, data1.ini[tempo, ], stringsAsFactors = FALSE) # data.ini used for the output
+            data1 <- data1[ ! (is.infinite(data1[, freq]) | is.na(data1[, freq])), ] #
+        }
+        # end Inf and NA removal
+        # 0 removal
+        if(any(data1[, freq] == 0)){
+            warn.count <- warn.count + 1
+            tempo.warn <- paste0("(", warn.count,") PRESENCE OF 0 VALUES IN THE ", freq, " COLUMN OF THE data1 ARGUMENT AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)")
+            warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+            tempo <- which(data1[, freq] == 0) # data.ini used for the output
+            removed.row.nb <- c(removed.row.nb, tempo)
+            removed.rows <- rbind(removed.rows, data1.ini[tempo, ], stringsAsFactors = FALSE) # data.ini used for the output
+            data1 <- data1[ data1[, freq] != 0, ] #
+        }
+        # end 0 removal
+    }
+    
+    if( ! categ %in% names(data1)){
+        tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg ARGUMENT MUST BE A COLUMN NAME OF THE data1 ARGUMENT")
+        stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+    }else{
+        if(all(is.na(data1[ , categ]))){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE categ COLUMN OF data1 CANNOT BE JUST NA")
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }
+        tempo1 <- fun_check(data = categ, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name)
+        tempo2 <- fun_check(data = categ, class = "factor", na.contain = TRUE, fun.name = function.name)
+        if(tempo1$problem == TRUE & tempo2$problem == TRUE){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE categ COLUMN OF data1 MUST BE CLASS \"factor\" OR \"character\"")
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }
+        # NA removal
+        if(any(is.na(data1[, categ]))){
+            warn.count <- warn.count + 1
+            tempo.warn <- paste0("(", warn.count,") PRESENCE OF NA VALUES IN THE ", categ, " COLUMN OF THE data1 ARGUMENT AND CORRESPONDING ROWS REMOVED (SEE $removed.row.nb AND $removed.rows)")
+            warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+            tempo <- which(is.na(data1.ini[, categ])) # data.ini used for the output
+            removed.row.nb <- c(removed.row.nb, tempo)
+            removed.rows <- rbind(removed.rows, data1.ini[tempo, ], stringsAsFactors = FALSE) # data.ini used for the output
+            data1 <- data1[ ! is.na(data1[, categ]), ] #
+        }
+        # end Inf and NA removal
+        if(any(duplicated(data1[, categ]))){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE categ COLUMN OF data1 CANNOT CONTAIN DUPLICATED VALUES\n", paste(data1[, categ][duplicated(data1[, categ])], collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }
+    }
+    
+    if( ! is.null(annotation)){
+        if( ! annotation %in% names(data1)){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nannotation ARGUMENT MUST BE A COLUMN NAME OF THE data1 ARGUMENT")
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }else{
+            if(all(is.na(data1[ , annotation]))){
+                tempo.cat <- paste0("ERROR IN ", function.name, "\nIF NON NULL, THE annotation COLUMN OF data1 CANNOT BE JUST NA")
+                stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+            }
+            tempo1 <- fun_check(data = annotation, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name)
+            tempo2 <- fun_check(data = annotation, class = "factor", na.contain = TRUE, fun.name = function.name)
+            if(tempo1$problem == TRUE & tempo2$problem == TRUE){
+                tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE annotation COLUMN OF data1 MUST BE CLASS \"factor\" OR \"character\"")
+                stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+            }
+            if(any(duplicated(data1[, annotation]))){
+                warn.count <- warn.count + 1
+                tempo.warn <- paste0("(", warn.count,") PRESENCE OF DUPLICATED VALUES IN THE ", annotation, " COLUMN OF THE data1 ARGUMENT: ", paste0(data1[, annotation][duplicated(data1[, annotation])], collapse = " "))
+                warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+            }
+        }
+    }
+    if(length(data1) == 0){
+        tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE data1 ARGUMENT IS EMPTY AFTER Inf, NA AND 0 REMOVAL IN THE ", freq, ifelse(is.null(annotation), " AND ", ", "), categ, ifelse(is.null(annotation), "", " AND "), " COLUMNS")
+        stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+    }
+    if( ! is.null(fill.color)){
+        if( ! is.numeric(fill.color)){
+            if( ! all(fill.color[ ! is.na(fill.color)] %in% colors() | grepl(pattern = "^#", fill.color[ ! is.na(fill.color)]), na.rm = TRUE)){
+                tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT MUST BE A VECTOR OF (1) HEXADECIMAL COLOR STRINGS STARTING BY #, OR (2) COLOR NAMES GIVEN BY colors(), OR (3) INTEGER VALUES")
+                stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+            }else{
+                fill.color <- as.character(fill.color) # remove class factor is any
+            }
+        }
+    }
+    if( ! is.numeric(border.color)){
+        if( ! (border.color %in% colors() | grepl(pattern = "^#", border.color))){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT MUST BE (1) A HEXADECIMAL COLOR STRING STARTING BY #, OR (2) A COLOR NAME GIVEN BY colors(), OR (3) AN INTEGER VALUE")
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }else{
+            border.color <- as.character(border.color) # remove class factor is any
+        }
+    }
+    # legend name filling
+    if(is.null(legend.name)){
+        legend.name <- categ
+    }
+    # legend.name not NULL anymore
+    # end legend name filling
+    # verif of add
+    if( ! is.null(add)){
+        if( ! grepl(pattern = "^\\s*\\+", add)){ # check that the add string start by +
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+            
+        }else if( ! grepl(pattern = "(ggplot2|lemon)\\s*::", add)){ #
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nFOR EASIER FUNCTION DETECTION, add ARGUMENT MUST CONTAIN \"ggplot2::\" OR \"lemon::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }else if( ! grepl(pattern = ")\\s*$", add)){ # check that the add string finished by )
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }
+    }
+    # end verif of add
+    # management of add containing facet
+    facet.categ <- NULL
+    if( ! is.null(add)){
+        facet.check <- TRUE
+        tempo <- unlist(strsplit(x = add, split = "\\s*\\+\\s*(ggplot2|lemon)\\s*::\\s*")) #
+        tempo <- sub(x = tempo, pattern = "^facet_wrap", replacement = "ggplot2::facet_wrap")
+        tempo <- sub(x = tempo, pattern = "^facet_grid", replacement = "ggplot2::facet_grid")
+        tempo <- sub(x = tempo, pattern = "^facet_rep", replacement = "lemon::facet_rep")
+        
+        if(any(grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap"))){
+            tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap")])))
+            facet.categ <- list(names(tempo1$params$facets)) # list of length 1
+            tempo.text <- "facet_wrap OR facet_rep_wrap"
+            facet.check <- FALSE
+        }else if(grepl(x = add, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")){
+            tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")])))
+            facet.categ <- list(c(names(tempo1$params$rows), names(tempo1$params$cols))) # list of length 1
+            tempo.text <- "facet_grid OR facet_rep_grid"
+            facet.check <- FALSE
+        }
+        if(facet.check == FALSE & ! all(facet.categ %in% names(data1))){ # WARNING: all(facet.categ %in% names(data1)) is TRUE when facet.categ is NULL
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF \"", tempo.text, "\" STRING IN THE add ARGUMENT BUT PROBLEM OF VARIABLE DETECTION (COLUMN NAMES OF data1)\nTHE DETECTED VARIABLES ARE:\n", paste(facet.categ, collapse = " "), "\nTHE data1 COLUMN NAMES ARE:\n", paste(names(data1), collapse = " "), "\nPLEASE REWRITE THE add STRING AND RERUN")
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }
+    }
+    # if facet.categ is not NULL, it is a list of length 1 now
+    # end management of add containing facet
+    if( ! is.null(lib.path)){
+        if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nDIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n"))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }
+    }
+    # end other checkings
+    # reserved word checking
+    if( ! (is.null(add))){
+        if(any(sapply(X = arg.names, FUN = grepl, x = add), na.rm = TRUE)){
+            warn.count <- warn.count + 1
+            tempo.warn <- paste0("(", warn.count,") NAMES OF ", function.name, " ARGUMENTS DETECTED IN THE add STRING:\n", paste(arg.names[sapply(X = arg.names, FUN = grepl, x = add)], collapse = "\n"), "\nRISK OF WRONG OBJECT USAGE INSIDE ", function.name)
+            warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+        }
+    }
+    # verif of add
+    if( ! is.null(add)){
+        if( ! grepl(pattern = "^\\s*\\+", add)){ # check that the add string start by +
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between ==
+        }else if( ! grepl(pattern = "(ggplot2|lemon)\\s*::", add)){ #
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nFOR EASIER FUNCTION DETECTION, add ARGUMENT MUST CONTAIN \"ggplot2::\" OR \"lemon::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between ==
+        }else if( ! grepl(pattern = ")\\s*$", add)){ # check that the add string finished by )
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between ==
+        }
+    }
+    # end verif of add
+    # management of add containing facet
+    facet.categ <- NULL
+    if( ! is.null(add)){
+        facet.check <- TRUE
+        tempo <- unlist(strsplit(x = add, split = "\\s*\\+\\s*(ggplot2|lemon)\\s*::\\s*")) #
+        tempo <- sub(x = tempo, pattern = "^facet_wrap", replacement = "ggplot2::facet_wrap")
+        tempo <- sub(x = tempo, pattern = "^facet_grid", replacement = "ggplot2::facet_grid")
+        tempo <- sub(x = tempo, pattern = "^facet_rep", replacement = "lemon::facet_rep")
+        if(any(grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap"), na.rm = TRUE)){
+            tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap")])))
+            facet.categ <- names(tempo1$params$facets)
+            tempo.text <- "facet_wrap OR facet_rep_wrap"
+            facet.check <- FALSE
+        }else if(grepl(x = add, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")){
+            tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")])))
+            facet.categ <- c(names(tempo1$params$rows), names(tempo1$params$cols))
+            tempo.text <- "facet_grid OR facet_rep_grid"
+            facet.check <- FALSE
+        }
+        if(facet.check == FALSE & ! all(facet.categ %in% names(data1))){ # WARNING: all(facet.categ %in% names(data1)) is TRUE when facet.categ is NULL # all() without na.rm -> ok because facet.categ cannot be NA (tested above)
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF \"", tempo.text, "\" STRING IN THE add ARGUMENT BUT PROBLEM OF VARIABLE DETECTION (COLUMN NAMES OF data1)\nTHE DETECTED VARIABLES ARE:\n", paste(facet.categ, collapse = " "), "\nTHE data1 COLUMN NAMES ARE:\n", paste(names(data1), collapse = " "), "\nPLEASE REWRITE THE add STRING AND RERUN")
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between ==
+        }
+    }
+    # end management of add containing facet
+    # end reserved word checking
+    # end second round of checking and data preparation
+    
+    # package checking
+    fun_pack(req.package = c(
+        "gridExtra", 
+        "ggplot2", 
+        "lemon",
+        "grid",
+        "ggrepel"
+    ), lib.path = lib.path)
+    # end package checking
+    
+    # main code
+    data1 <- data.frame(data1, prop = data1[ , freq] / sum(data1[ , freq]))
+    if(legend.add.prop == TRUE){
+        data1[ , categ] <- paste0(data1[ , categ], " (", round(data1$prop, 2), ")")
+    }
+    data1[ , categ] <- factor(data1[ , categ], levels = data1[ , categ][order(data1$prop, decreasing = TRUE)]) # reorder so that the donut is according to decreasing proportion starting at the top in a clockwise direction
+    data1 <- data1[order(as.numeric(data1[ , categ]), decreasing = FALSE), ] # data1[ , categ] with rows in decreasing order, according to prop
+    data1 <- data.frame(data1, x = 0) # staked bar at the origin of the donut set to x = 0
+    tempo.gg.name <- "gg.indiv.plot."
+    tempo.gg.count <- 0
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add added here to have the facets
+    bar_width = 1
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_col(
+        data = data1,
+        mapping = ggplot2::aes_string(x = "x", y = freq, fill = categ), 
+        color = border.color, 
+        size = border.size, 
+        width = bar_width
+    )) # size is size of the separation in the donut
+    # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text(
+    #     ggplot2::aes(label = Freq), 
+    #     position = ggplot2::position_stack(vjust = 0.5)
+    # ))
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_x_continuous(
+        expand = c(0, 0), # prevent extra limits in x axis
+        limits = c(- bar_width / 2 - (bar_width * hole.size) / (1 - hole.size), max(bar_width / 2, annotation.distance))
+    )) # must be centered on x = 0
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ylim(c(0, max(cumsum(data1[ , freq])))))
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(
+        geom = "text", 
+        x = - bar_width / 2 - (bar_width * hole.size) / (1 - hole.size), 
+        y = 0, 
+        label = sum(data1[ , freq]), 
+        size = hole.text.size
+    ))
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coord_polar(theta = "y", direction = -1, start = 0, clip = "on"))
+    if(is.null(fill.color) & ! is.null(fill.palette)){
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_brewer(palette = fill.palette, name = legend.name))
+    }else if( ! is.null(fill.color)){
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_manual(values = fill.color, name = legend.name, na.value = "white"))
+    }else if(! is.null(legend.name)){
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::labs(fill = legend.name))
+    }
+    
+    if( ! is.null(add)){ # if add is NULL, then = 0
+        if(grepl(pattern = "ggplot2\\s*::\\s*theme", add) == TRUE){
+            warn.count <- warn.count + 1
+            tempo.warn <- paste0("(", warn.count,") \"ggplot2::theme\" STRING DETECTED IN THE add ARGUMENT\n-> INTERNAL GGPLOT2 THEME FUNCTIONS theme_void() HAS BEEN INACTIVATED, SO THAT THE USER THEME CAN BE EFFECTIVE")
+            warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+            add.check <- FALSE
+        }else{
+            assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_void())
+        }
+    }else{
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_void())
+    }
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(
+        fill = ggplot2::guide_legend(override.aes = list(color = "white", size  = 2, stroke = 1))
+    )) # remove border of squares in legend
+    
+    # annotations on slices
+    if( ! is.null(annotation)){
+        tempo <- rev(cumsum(rev(data1[ , freq])))
+        data1 <- data.frame(data1, text_y = tempo - (tempo - c(tempo[-1], 0)) / 2)
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggrepel::geom_text_repel(
+            data = data1, 
+            mapping = ggplot2::aes_string(
+                x = "x", 
+                y = "text_y", 
+                label = annotation
+            ), 
+            size = annotation.size, 
+            force = annotation.force, 
+            force_pull = annotation.force.pull, 
+            nudge_x = annotation.distance, # knowing that the bar is centered on x = 0 and that the right edge is at bar_width / 2, 0 means center of the slice, 0.5 means at the edge if bar_width = 1
+            show.legend = FALSE
+        ))
+    }
+    # end annotations on slices
+    
+    # legend management
+    # removal of part of the legend 
+    if( ! is.null(legend.limit)){
+        if(sum(data1$prop >= legend.limit) == 0){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE legend.limit PARAMETER VALUE (", legend.limit, ") IS TOO HIGH FOR THE PROPORTIONS IN THE DONUT PLOT:\n", paste0(data1$prop, collapse = "\n"))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }else{
+            assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_discrete(
+                breaks = as.character(data1[ , categ][data1$prop >= legend.limit])
+            ))
+        }
+    }
+    # end removal of part of the legend
+    if(legend.show == FALSE){ # must be here because must be before bef.final.plot
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = "none")) # inactivate the initial legend
+    }
+    bef.final.plot <- suppressWarnings(suppressMessages(ggplot2::ggplot_build(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "))))))
+    if( ! is.null(legend.width)){
+        legend.final <- suppressWarnings(suppressMessages(fun_gg_get_legend(ggplot_built = bef.final.plot, fun.name = function.name, lib.path = lib.path))) # get legend
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = "none")) # inactivate the initial legend
+        if(is.null(legend.final) & plot == TRUE){ # even if any(unlist(legend.disp)) is TRUE
+            legend.final <- ggplot2::ggplot()+ggplot2::theme_void() # empty graph instead of legend
+            warn.count <- warn.count + 1
+            tempo.warn <- paste0("(", warn.count,") LEGEND REQUESTED (legend.show ARGUMENT SET TO TRUE)\nBUT IT SEEMS THAT THE PLOT HAS NO LEGEND -> EMPTY LEGEND SPACE CREATED BECAUSE OF THE NON NULL legend.width ARGUMENT\n")
+            warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+        }
+    }
+    # end legend management
+    
+    # drawing
+    final.plot <- eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + ")))
+    
+    # title
+    if(title != ""){
+        title.grob <- grid::textGrob(
+            label = title,
+            x = grid::unit(0, "lines"), 
+            y = grid::unit(0, "lines"),
+            hjust = 0,
+            vjust = 0,
+            gp = grid::gpar(fontsize = title.text.size)
+        )
+        pdf(NULL)
+        final.plot <- suppressMessages(suppressWarnings(gridExtra::arrangeGrob(final.plot, top = title.grob, left = " ", right = " "))) # , left = " ", right = " " : trick to add margins in the plot. padding =  unit(0.5, "inch") is for top margin above the title
+        dev.off()
+    }
+    # end title
+    
+    grob.save <- NULL
+    if(plot == TRUE){
+        if( ! is.null(legend.width)){
+            grob.save <- suppressMessages(suppressWarnings(gridExtra::grid.arrange(final.plot, legend.final, ncol=2, widths=c(1, legend.width))))
+        }else{
+            grob.save <- suppressMessages(suppressWarnings(print(final.plot)))
+        }
+    }else{
+        warn.count <- warn.count + 1
+        tempo.warn <- paste0("(", warn.count,") PLOT NOT SHOWN AS REQUESTED")
+        warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+    }
+    # end drawing
+    
+    
+    
+    # output
+    if(warn.print == TRUE & ! is.null(warn)){
+        on.exit(warning(paste0("FROM ", function.name, ":\n\n", warn), call. = FALSE))
+    }
+    on.exit(exp = options(warning.length = ini.warning.length), add = TRUE)
+    if(return == TRUE){
+        output <- suppressMessages(ggplot2::ggplot_build(final.plot))
+        if(is.null(unlist(removed.row.nb))){
+            removed.row.nb <- NULL
+            removed.rows <- NULL
+        }
+        tempo <- output$layout$panel_params[[1]]
+        output <- list(
+            data = data1, 
+            removed.row.nb = removed.row.nb, 
+            removed.rows = removed.rows, 
+            plot = output$data, 
+            panel = facet.categ, 
+            axes = list(
+                x.range = tempo$x.range, 
+                x.labels = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{tempo$x$scale$get_labels()}, # is.null(attributes(tempo$x$breaks)) test if it is number (TRUE) or character (FALSE)
+                x.positions = if(is.null(attributes(tempo$x$breaks))){tempo$x$breaks}else{unlist(attributes(tempo$x$breaks))}, 
+                y.range = tempo$y.range, 
+                y.labels = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{tempo$y$scale$get_labels()}, 
+                y.positions = if(is.null(attributes(tempo$y$breaks))){tempo$y$breaks}else{unlist(attributes(tempo$y$breaks))}
+            ), 
+            warn = paste0("\n", warn, "\n\n"), 
+            ggplot = if(return.ggplot == TRUE){final.plot}else{NULL}, # final.plot plots the graph if return == TRUE
+            gtable = if(return.gtable == TRUE){grob.save}else{NULL} #
+        )
+        return(output) # this plots the graph if return.ggplot is TRUE and if no assignment
+    }
+    # end output
+    # end main code
+}
+
+
 
 # Error: class order not good when a class is removed due to NA
 # Error: line 136 in check 20201126 with add argument
@@ -11363,6 +12068,7 @@ fun_gg_boxplot <- function(
 
 
 
+
 # add density
 # rasterise all kind: https://cran.r-project.org/web/packages/ggrastr/vignettes/Raster_geoms.html
 
@@ -13728,6 +14434,3 @@ if(return == TRUE){
 
 
 
-
-
-
diff --git a/examples_fun_gg_donut.R b/examples_fun_gg_donut.R
new file mode 100644
index 0000000000000000000000000000000000000000..10ee97b3073ead03dd5dc0a45c60ae04da64a6e2
--- /dev/null
+++ b/examples_fun_gg_donut.R
@@ -0,0 +1,193 @@
+# EXAMPLES
+
+## Data set
+obs1 <- data.frame(
+    Km = c(20, 10, 1, 5), 
+    Car = c("TUUT", "WIIM", "BIP", "WROUM"), 
+    Color1 = c(1:3, NA), 
+    Color2 = c("burlywood1", "cadetblue1", "coral", "darkmagenta"), 
+    Color3 = c("#F8766D", "#00BA38", NA, "#619CFF"), 
+    Country = c("FR", "UK", "US", NA), 
+    stringsAsFactors = TRUE)
+obs1
+fun_open(pdf = FALSE, width = 5, height = 5)
+
+## Mandatory arguments
+# Note that the order is systematically in the decreasing order of frequencies, starting at the top and turning clockwise
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car")
+
+## Color modification
+# Default colors
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    fill.palette = NULL,
+    fill.color = NULL
+)
+# Using the fill.palette argument
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    fill.palette = "BrBG",
+    fill.color = NULL
+)
+# Using the fill.color argument (as positive integers). NA are white
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    fill.palette = NULL,
+    fill.color = obs1$Color1
+)
+
+# The fill.color argument overrides the fill.palette argument
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    fill.palette = "BrBG",
+    fill.color = obs1$Color1
+)
+# Using the fill.color argument (as elements from colors())
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    fill.palette = NULL,
+    fill.color = obs1$Color2
+)
+# Warning: the colors order in fill.color is the one according to the decreasing values of the freq argument. Thus, to have the correct association between obs1$Car and obs1$Color2, obs1 must be sorted
+obs1 <- obs1[order(obs1$Km, decreasing = TRUE), ]
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    fill.palette = NULL,
+    fill.color = obs1$Color2
+)
+# Using the fill.color argument, as hexadecimal values
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    fill.palette = NULL,
+    fill.color = obs1$Color3
+)
+
+## hole of the donut
+# hole being 25% of the radius of the donut
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    hole.size = 0.25,
+    hole.text.size = 8 # size in mm
+)
+
+## border of slices
+# positive integer
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    border.color = 2, 
+    border.size = 2 # in mm
+)
+# element of colors()
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    border.color = "green", 
+    border.size = 2 # in mm
+)
+# hexadecimal value
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    border.color = "#619CFF", 
+    border.size = 2 # in mm
+)
+
+## title of the plot
+# hole being 25% of the radius of the donut
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    title = "DONUT", 
+    title.text.size = 20, 
+)
+
+## Annotation of the slices
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car",
+    annotation = "Country",
+    annotation.distance = 1, # annotation.distance = 0 means center of the slice, 0.5 means at the edge. Above 0.5, the donut will be reduced to make place for the annotation
+    annotation.size = 5, # size of the texte in mm
+    annotation.force = 0, # force of repulsion between overlapping text labels
+    annotation.force.pull = 0, # force of attraction between a text label and its corresponding data point
+)
+
+## Management of the legend area
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car", 
+    legend.show = TRUE, # FALSE remove the legend, not the area of the legend
+    legend.width = 0.5, # between 0 (no area for the legend) to 1 (half the device width for the legend area)
+    legend.name = "Noise", # legend title added
+    legend.limit = NULL, # all the slices lower that 0.2 in proportion are not displayed in the legend
+    legend.add.prop = TRUE # proportion after the class names added
+)
+
+## the add argument
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car", 
+               add = "+ggplot2::theme_gray()"
+)
+fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car", 
+               add = "+ggplot2::facet_wrap(facets = 'Country', labeller = 'label_both') + ggplot2::theme(strip.background = ggplot2::element_rect(color = 'grey', size = 0.5), strip.text = ggplot2::element_text(size = 10, face = 'bold'), panel.spacing = ggplot2::unit(0.5, 'lines'))"  # or ggplot2::vars(Country) instead of 'Country'. See https://ggplot2.tidyverse.org/reference/labeller.html
+)
+
+
+## Other parameters
+res <- fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car", 
+                      return = TRUE, # output returned and assigned into res. If FALSE, res is NULL
+                      return.ggplot = TRUE, # ggplot object added in res (without the legend
+                      return.gtable = TRUE, # ggplot object as gtable of grobs in res ($gtable is NULL if plot = FALSE)
+                      plot = TRUE, # plot displayed during asignation into res
+                      warn.print = TRUE, 
+                      lib.path = NULL
+)
+# plot the result
+fun_open(pdf = FALSE)
+res$ggplot
+# The advantage of $ggplot is that it is easy to update the plot
+res$ggplot + ggplot2::annotate(geom = "text", x = -0.5, y = 0, label = "NOT GOOD", size = 20, angle = 45)
+# However, manipulation of res triggers plotting when $ggplot is not NULL (NULL $ggplot obtained using return.ggplot = FALSE), which is annoying, as explain in the function description. Thus, it is preferable to use return.ggplot = FALSE
+
+
+## Notes about the gtable output
+res2 <- fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car", 
+                       return = TRUE, 
+                       return.ggplot = FALSE,
+                       return.gtable = TRUE, # return.gtable must be TRUE to have a non NULL $gtable output into res2
+                       plot = TRUE, # plot must be TRUE to have a non NULL $gtable output into res2
+                       warn.print = FALSE, 
+                       lib.path = NULL
+)
+
+# display the results (does not plot the graph, contrary to $ggplot)
+fun_open(pdf = FALSE)
+res2
+
+# replot
+fun_open(pdf = FALSE)
+gridExtra::grid.arrange(res2$gtable) # Contrary to $ggplot, $gtable cannot be easily updated (see https://stackoverflow.com/questions/26499608/inverse-of-ggplotgrob)
+# plot the first grob
+fun_open(pdf = FALSE)
+gridExtra::grid.arrange(res2$gtable[1,1])
+# plot the second grob
+fun_open(pdf = FALSE)
+gridExtra::grid.arrange(res2$gtable[1,2])
+
+
+## All the arguments
+fun_gg_donut(
+    data1 = obs1, 
+    freq = "Km", 
+    categ = "Car", 
+    fill.palette = NULL,
+    fill.color = NULL, 
+    hole.size = 0.5, 
+    hole.text.size = 14, 
+    border.color = "gray50", 
+    border.size = 0.2, 
+    title = "", 
+    title.text.size = 12, 
+    annotation = NULL,
+    annotation.distance = 0,
+    annotation.size = 3,
+    annotation.force = 1,
+    annotation.force.pull = 100,
+    legend.show = TRUE, 
+    legend.width = 0.25, 
+    legend.name = NULL, 
+    legend.limit = NULL, 
+    legend.add.prop = FALSE,
+    add = NULL, 
+    return = FALSE, 
+    return.ggplot = FALSE,
+    return.gtable = TRUE,
+    plot = TRUE, 
+    warn.print = FALSE, 
+    lib.path = NULL
+)
+
+
+
+
+
diff --git a/fun_gg_donut.R b/fun_gg_donut.R
index 117ccaaed61fbbb26b93362c898db1b0d2a7c051..9b7121c9e2c8a8c74f61d8fd4b16b5cdb1935db5 100644
--- a/fun_gg_donut.R
+++ b/fun_gg_donut.R
@@ -7,17 +7,18 @@ fun_gg_donut <- function(
     fill.palette = NULL,
     fill.color = NULL, 
     hole.size = 0.5, 
-    hole.text.size = 12, 
+    hole.text.size = 14, 
     border.color = "gray50", 
-    border.size = 12, 
+    border.size = 0.2, 
     title = "", 
     title.text.size = 12, 
     annotation = NULL,
+    annotation.distance = 0,
     annotation.size = 3,
     annotation.force = 1,
     annotation.force.pull = 100,
     legend.show = TRUE, 
-    legend.width = 0.5, 
+    legend.width = 0.25, 
     legend.name = NULL, 
     legend.limit = NULL, 
     legend.add.prop = FALSE,
@@ -30,7 +31,7 @@ fun_gg_donut <- function(
     lib.path = NULL
 ){
     # AIM
-    # Plot a ggplot2 donut using contingency data, with frequencies sorted from the hisgest to the lowest, starting at the top and turning clockwise
+    # Plot a ggplot2 donut using contingency data, systematically in the decreasing order of frequencies, starting at the top and turning clockwise
     # For ggplot2 specifications, see: https://ggplot2.tidyverse.org/articles/ggplot2-specs.html
     # WARNINGS
     # Rows containing NA in data1[, c(freq, categ)] will be removed before processing, with a warning (see below)
@@ -40,29 +41,30 @@ fun_gg_donut <- function(
     # freq: single character string of the data1 column name of the frequencies
     # categ: single character string of the data1 column name of categories (qualitative variable)
     # fill.palette: single character string of a palette name (see ?ggplot2::scale_fill_brewer() for the list).Ignored if fill.color is not NULL
-    # fill.color: either (1) NULL, or (2) a vector of character strings or integers of same length as the number of classes in categ. Colors can be color names (see ?colors() in R), hexadecimal color codes, or integers (see ?palette() in R). The order of the elements will be used according to the frequency values, from highest to lowest. An easy way to use this argument is to sort data1 according to the frequencies values, add a color column with the corresponding desired colors and use the content of this column as values of fill.color. If color is NULL and fill.palette is NULL, default colors of ggplot2 are used. If color is not NULL, it overrides fill.palette
+    # fill.color: either (1) NULL, or (2) a vector of character strings or integers of same length as the number of classes in categ. Colors can be color names (see ?colors() in R), hexadecimal color codes, or integers (according to the ggplot2 palette). The order of the elements will be used according to the frequency values, from highest to lowest. An easy way to use this argument is to sort data1 according to the frequencies values, add a color column with the corresponding desired colors and use the content of this column as values of fill.color. If color is NULL and fill.palette is NULL, default colors of ggplot2 are used. If color is not NULL, it overrides fill.palette
     # hole.size: single positive proportion of donut central hole, 0 meaning no hole and 1 no donut
     # hole.text.size: single positive numeric value of the title font size in mm
-    # border.color: a single character string or integer. Colors can be color names (see ?colors() in R), hexadecimal color codes, or integers (see ?palette() in R)
+    # border.color: a single character string or integer. Colors can be color names (see ?colors() in R), hexadecimal color codes, or integers (according to the ggplot2 palette)
     # border.size: single numeric value of border tickness in mm. Write zero for no dot border
     # title: single character string of the graph title
     # title.text.size: single numeric value of the title font size in mm
     # annotation: single character string of the data1 column name of annotations. Values inside this column will be displayed over the corresponding slices of the donut. Write NULL if not required
+    # annotation.distance: single positive numeric value of the distance from the center of the slice. 0 means center of the slice, 0.5 means at the edge. Above 0.5, the donut will be reduced to make place for the annotation. Ignored if annotation is NULL
     # annotation.size: single positive numeric value of the annotation font size in mm. Ignored if annotation is NULL
     # annotation.force: single positive numeric value of the force of repulsion between overlapping text labels. See ?ggrepel::geom_text_repel() in R. Ignored if annotation is NULL
     # annotation.force.pull: single positive numeric value of the force of attraction between a text label and its corresponding data point. See ?ggrepel::geom_text_repel() in R. Ignored if annotation is NULL
     # legend.show: logical (either TRUE or FALSE). Show legend? 
     # legend.width: single proportion (between 0 and 1) indicating the relative width of the legend sector (on the right of the plot) relative to the width of the plot. Value 1 means that the window device width is split in 2, half for the plot and half for the legend. Value 0 means no room for the legend, which will overlay the plot region. Write NULL to inactivate the legend sector. In such case, ggplot2 will manage the room required for the legend display, meaning that the width of the plotting region can vary between graphs, depending on the text in the legend
-    # legend.name: character string of the legend title. If legend.name is NULL then legend.name is the value of the categ argument
+    # legend.name: character string of the legend title. If legend.name is NULL then legend.name is the value of the categ argument. Write legend.name = "" to remove the legend
     # legend.limit: single positive proportion of the classes displayed in the legend for which the corresponding proportion is over legend.limit. Write NULL to display all the classes
     # legend.add.prop: logical (either TRUE or FALSE). add the proportion after the class names in the legend ?
     # add: character string allowing to add more ggplot2 features (dots, lines, themes, facet, etc.). Ignored if NULL
     # WARNING: (1) the string must start with "+", (2) the string must finish with ")" and (3) each function must be preceded by "ggplot2::". Example: "+ ggplot2::coord_flip() + ggplot2::theme_bw()"
-    # If the character string contains the "ggplot2::theme" string, then the article argument of fun_gg_scatter() (see above) is ignored with a warning. In addition, some arguments can be overwritten, like x.angle (check all the arguments)
+    # If the character string contains the "ggplot2::theme" string, then the article argument of fun_gg_donut() (see above) is ignored with a warning. In addition, some arguments can be overwritten, like x.angle (check all the arguments)
     # Handle the add argument with caution since added functions can create conflicts with the preexisting internal ggplot2 functions
-    # WARNING: the call of objects inside the quotes of add can lead to an error if the name of these objects are some of the fun_gg_scatter() arguments. Indeed, the function will use the internal argument instead of the global environment object. Example article <- "a" in the working environment and add = '+ ggplot2::ggtitle(article)'. The risk here is to have TRUE as title. To solve this, use add = '+ ggplot2::ggtitle(get("article", envir = .GlobalEnv))'
+    # WARNING: the call of objects inside the quotes of add can lead to an error if the name of these objects are some of the fun_gg_donut() arguments. Indeed, the function will use the internal argument instead of the global environment object. Example article <- "a" in the working environment and add = '+ ggplot2::ggtitle(article)'. The risk here is to have TRUE as title. To solve this, use add = '+ ggplot2::ggtitle(get("article", envir = .GlobalEnv))'
     # return: logical (either TRUE or FALSE). Return the graph parameters?
-    # return.ggplot: logical (either TRUE or FALSE). Return the ggplot object in the output list? Ignored if return argument is FALSE. WARNING: always assign the fun_gg_scatter() function (e.g., a <- fun_gg_scatter()) if return.ggplot argument is TRUE, otherwise, double plotting is performed. See $ggplot in the RETURN section below for more details
+    # return.ggplot: logical (either TRUE or FALSE). Return the ggplot object in the output list? Ignored if return argument is FALSE. WARNING: always assign the fun_gg_donut() function (e.g., a <- fun_gg_donut()) into something if the return.ggplot argument is TRUE, otherwise, double plotting is performed. See $ggplot in the RETURN section below for more details
     # return.gtable: logical (either TRUE or FALSE). Return the ggplot object as gtable of grobs in the output list? Ignored if plot argument is FALSE. Indeed, the graph must be plotted to get the grobs dispositions. See $gtable in the RETURN section below for more details
     # plot: logical (either TRUE or FALSE). Plot the graphic? If FALSE and return argument is TRUE, graphical parameters and associated warnings are provided without plotting
     # warn.print: logical (either TRUE or FALSE). Print warnings at the end of the execution? ? If FALSE, warning messages are never printed, but can still be recovered in the returned list. Some of the warning messages (those delivered by the internal ggplot2 functions) are not apparent when using the argument plot = FALSE
@@ -76,7 +78,7 @@ fun_gg_donut <- function(
     # $panel: the variable names used for the panels (NULL if no panels). WARNING: NA can be present according to ggplot2 upgrade to v3.3.0
     # $axes: the x-axis and y-axis info
     # $warn: the warning messages. Use cat() for proper display. NULL if no warning. WARNING: warning messages delivered by the internal ggplot2 functions are not apparent when using the argument plot = FALSE
-    # $ggplot: ggplot object that can be used for reprint (use print($ggplot) or update (use $ggplot + ggplot2::...). NULL if return.ggplot argument is FALSE. Of note, a non-null $ggplot in the output list is sometimes annoying as the manipulation of this list prints the plot
+    # $ggplot: ggplot object that can be used for reprint (use print($ggplot) or update (use $ggplot + ggplot2::...). NULL if return.ggplot argument is FALSE. Warning: the legend is not in $ggplot as it is in a separated grob (use $gtable to get it). Of note, a non-null $ggplot in the output list is sometimes annoying as the manipulation of this list prints the plot
     # $gtable: gtable object that can be used for reprint (use gridExtra::grid.arrange(...$ggplot) or with additionnal grobs (see the grob decomposition in the examples). NULL if return.ggplot argument is FALSE. Contrary to $ggplot, a non-NULL $gtable in the output list is not annoying as the manipulation of this list does not print the plot
     # REQUIRED PACKAGES
     # ggplot2
@@ -90,9 +92,9 @@ fun_gg_donut <- function(
     # fun_pack()
     # fun_check()
     # EXAMPLES
-    # obs1 <- data.frame(Km = c(20, 10, 1, 5), Car = c("TUUT", "WIIM", "BIP", "WROUM"), Color1 = 1:4, color2 = fun_gg_palette(4), Country = c("FR", "UK", "US", NA), stringsAsFactors = TRUE) ; fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car", annotation = "Country")
+    # obs1 <- data.frame(Km = c(20, 10, 1, 5), Car = c("TUUT", "WIIM", "BIP", "WROUM"), Color1 = 1:4, color2 = c("red", "blue", "green", "black"), Country = c("FR", "UK", "US", NA), stringsAsFactors = TRUE) ; fun_gg_donut(data1 = obs1, freq = "Km", categ = "Car", annotation = "Country")
     # DEBUGGING
-    #
+    # obs1 <- data.frame(Km = c(20, 10, 1, 5), Car = c("TUUT", "WIIM", "BIP", "WROUM"), Color1 = 1:4, color2 = c("red", "blue", "green", "black"), Country = c("FR", "UK", "US", NA), stringsAsFactors = TRUE) ; data1 = obs1 ; freq = "Km" ; categ = "Car" ; fill.palette = NULL ; fill.color = NULL ; hole.size = 0.5 ; hole.text.size = 12 ; border.color = "gray50" ; border.size = 0.1 ; title = "" ; title.text.size = 12 ; annotation = "Country" ; annotation.distance = 0.5 ; annotation.size = 3 ; annotation.force = 1 ; annotation.force.pull = 100 ; legend.show = TRUE ; legend.width = 0.5 ; legend.name = NULL ; legend.limit = NULL ; legend.add.prop = FALSE ; add = NULL ; return = TRUE ; return.ggplot = FALSE ; return.gtable = TRUE ; plot = TRUE ; warn.print = FALSE ; lib.path = NULL
     # function name
     function.name <- paste0(as.list(match.call(expand.dots=FALSE))[[1]], "()")
     arg.names <- names(formals(fun = sys.function(sys.parent(n = 2)))) # names of all the arguments
@@ -150,12 +152,17 @@ fun_gg_donut <- function(
         tempo2 <- fun_check(data = fill.color, class = "factor", na.contain = TRUE, fun.name = function.name)
         tempo3 <- fun_check(data = fill.color, class = "integer", double.as.integer.allowed = TRUE, na.contain = TRUE, neg.values = FALSE, fun.name = function.name) # not need to test inf with integers
         if(tempo1$problem == TRUE & tempo2$problem == TRUE & tempo3$problem == TRUE){
-            tempo.cat <- paste0("ERROR IN ", function.name, ": fill.color ARGUMENT MUST BE A VECTOR OF (1) HEXADECIMAL COLOR STRINGS STARTING BY #, OR (2) COLOR NAMES GIVEN BY colors(), OR (3) POSITIVE INTEGER VALUES")
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT MUST BE A VECTOR OF (1) HEXADECIMAL COLOR STRINGS STARTING BY #, OR (2) COLOR NAMES GIVEN BY colors(), OR (3) POSITIVE INTEGER VALUES")
             text.check <- c(text.check, tempo.cat)
             arg.check <- c(arg.check, TRUE)
             checked.arg.names <- c(checked.arg.names, tempo1$object.name)
-        }else if(tempo3$problem == FALSE & ! is.finite(fill.color)){
-            tempo.cat <- paste0("ERROR IN ", function.name, ": fill.color ARGUMENT CANNOT CONTAIN Inf VALUES AMONG POSITIVE INTEGER VALUES")
+        }else if(tempo3$problem == FALSE & any(is.infinite(fill.color))){ # is.infinite() deals with NA as FALSE
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT CANNOT CONTAIN Inf VALUES AMONG POSITIVE INTEGER VALUES")
+            text.check <- c(text.check, tempo.cat)
+            arg.check <- c(arg.check, TRUE)
+            checked.arg.names <- c(checked.arg.names, tempo1$object.name)
+        }else if(tempo3$problem == FALSE & any(fill.color == 0, na.rm = TRUE)){
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT CANNOT CONTAIN 0 AMONG POSITIVE INTEGER VALUES")
             text.check <- c(text.check, tempo.cat)
             arg.check <- c(arg.check, TRUE)
             checked.arg.names <- c(checked.arg.names, tempo1$object.name)
@@ -166,7 +173,7 @@ fun_gg_donut <- function(
     tempo1 <- fun_check(data = border.color, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name)
     tempo2 <- fun_check(data = border.color, class = "integer", double.as.integer.allowed = TRUE, neg.values = FALSE, na.contain = FALSE, length = 1, fun.name = function.name) # not need to test inf with integers
     if(tempo1$problem == TRUE & tempo2$problem == TRUE){
-        tempo.cat <- paste0("ERROR IN ", function.name, ": border.color ARGUMENT MUST BE A SINGLE CHARACTER STRING OR POSITIVE INTEGER")
+        tempo.cat <- paste0("ERROR IN ", function.name, "\nborder.color ARGUMENT MUST BE A SINGLE CHARACTER STRING OR POSITIVE INTEGER")
         text.check <- c(text.check, tempo.cat)
         arg.check <- c(arg.check, TRUE)
         checked.arg.names <- c(checked.arg.names, tempo1$object.name)
@@ -176,16 +183,15 @@ fun_gg_donut <- function(
     tempo <- fun_check(data = title.text.size, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
     if( ! is.null(annotation)){
             tempo <- fun_check(data = annotation, class = "vector", mode = "character", na.contain = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+            tempo <- fun_check(data = annotation.distance, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+            tempo <- fun_check(data = annotation.size, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+            tempo <- fun_check(data = annotation.force, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
+            tempo <- fun_check(data = annotation.force.pull, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
     }else{
         # no fun_check test here, it is just for checked.arg.names
         tempo <- fun_check(data = annotation, class = "vector")
         checked.arg.names <- c(checked.arg.names, tempo$object.name)
     }
-    tempo <- fun_check(data = title, class = "vector", mode = "character", length = 1, fun.name = function.name) ; eval(ee)
-    tempo <- fun_check(data = title.text.size, class = "vector", mode = "numeric", length = 1, neg.values = FALSE, inf.values = FALSE, fun.name = function.name) ; eval(ee)
-    tempo <- fun_check(data = annotation.size, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
-    tempo <- fun_check(data = annotation.force, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
-    tempo <- fun_check(data = annotation.force.pull, class = "vector", mode = "numeric", na.contain = FALSE, neg.values = FALSE, inf.values = FALSE, length = 1, fun.name = function.name) ; eval(ee)
     tempo <- fun_check(data = legend.show, class = "logical", length = 1, fun.name = function.name) ; eval(ee)
     if( ! is.null(legend.width)){
         tempo <- fun_check(data = legend.width, prop = TRUE, length = 1, fun.name = function.name) ; eval(ee)
@@ -238,7 +244,7 @@ fun_gg_donut <- function(
     tempo.arg <- names(arg.user.setting) # values provided by the user
     tempo.log <- suppressWarnings(sapply(lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.na), FUN = any)) & lapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = length)== 1L # no argument provided by the user can be just NA
     if(any(tempo.log) == TRUE){
-        tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE JUST NA")
+        tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE JUST NA")
         stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between ==
     }
     # end management of NA arguments
@@ -256,6 +262,7 @@ fun_gg_donut <- function(
         "title", 
         "title.text.size", 
         # "annotation", # inactivated because can be null
+        "annotation.distance", 
         "annotation.size", 
         "annotation.force", 
         "annotation.force.pull", 
@@ -274,7 +281,7 @@ fun_gg_donut <- function(
     )
     tempo.log <- sapply(lapply(tempo.arg, FUN = get, env = sys.nframe(), inherit = FALSE), FUN = is.null)
     if(any(tempo.log) == TRUE){
-        tempo.cat <- paste0("ERROR IN ", function.name, ":\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL")
+        tempo.cat <- paste0("ERROR IN ", function.name, "\n", ifelse(sum(tempo.log, na.rm = TRUE) > 1, "THESE ARGUMENTS\n", "THIS ARGUMENT\n"), paste0(tempo.arg[tempo.log], collapse = "\n"),"\nCANNOT BE NULL")
         stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n"), call. = FALSE) # == in stop() to be able to add several messages between ==
     }
     # end management of NULL arguments
@@ -291,16 +298,16 @@ fun_gg_donut <- function(
     removed.rows <- data.frame(stringsAsFactors = FALSE)
     data1.ini <- data1 # strictly identical to data1
     if( ! freq %in% names(data1)){
-        tempo.cat <- paste0("ERROR IN ", function.name, ": freq ARGUMENT MUST BE A COLUMN NAME OF THE data1 ARGUMENT")
+        tempo.cat <- paste0("ERROR IN ", function.name, "\nfreq ARGUMENT MUST BE A COLUMN NAME OF THE data1 ARGUMENT")
         stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
     }else{
         if(all(is.na(data1[ , freq]) | is.infinite(data1[ , freq]))){
-            tempo.cat <- paste0("ERROR IN ", function.name, ":\nTHE freq COLUMN OF data1 CANNOT BE JUST NA OR Inf")
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE freq COLUMN OF data1 CANNOT BE JUST NA OR Inf")
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }
         tempo <- fun_check(data = data1[ , freq], mode = "numeric", neg.values = FALSE, fun.name = function.name)
         if(tempo$problem == TRUE){
-            tempo.cat <- paste0("ERROR IN ", function.name, ":\n", tempo$text)
+            tempo.cat <- paste0("ERROR IN ", function.name, "\n", tempo$text)
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }
         # Inf and NA removal
@@ -328,17 +335,17 @@ fun_gg_donut <- function(
     }
 
     if( ! categ %in% names(data1)){
-        tempo.cat <- paste0("ERROR IN ", function.name, ": categ ARGUMENT MUST BE A COLUMN NAME OF THE data1 ARGUMENT")
+        tempo.cat <- paste0("ERROR IN ", function.name, "\ncateg ARGUMENT MUST BE A COLUMN NAME OF THE data1 ARGUMENT")
         stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
     }else{
         if(all(is.na(data1[ , categ]))){
-            tempo.cat <- paste0("ERROR IN ", function.name, ":\nTHE categ COLUMN OF data1 CANNOT BE JUST NA")
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE categ COLUMN OF data1 CANNOT BE JUST NA")
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }
         tempo1 <- fun_check(data = categ, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name)
         tempo2 <- fun_check(data = categ, class = "factor", na.contain = TRUE, fun.name = function.name)
         if(tempo1$problem == TRUE & tempo2$problem == TRUE){
-            tempo.cat <- paste0("ERROR IN ", function.name, ":\nTHE categ COLUMN OF data1 MUST BE CLASS \"factor\" OR \"character\"")
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE categ COLUMN OF data1 MUST BE CLASS \"factor\" OR \"character\"")
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }
         # NA removal
@@ -353,24 +360,24 @@ fun_gg_donut <- function(
         }
         # end Inf and NA removal
         if(any(duplicated(data1[, categ]))){
-            tempo.cat <- paste0("ERROR IN ", function.name, ":\nTHE categ COLUMN OF data1 CANNOT CONTAIN DUPLICATED VALUES\n", paste(data1[, categ][duplicated(data1[, categ])], collapse = " "))
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE categ COLUMN OF data1 CANNOT CONTAIN DUPLICATED VALUES\n", paste(data1[, categ][duplicated(data1[, categ])], collapse = " "))
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }
     }
 
     if( ! is.null(annotation)){
         if( ! annotation %in% names(data1)){
-            tempo.cat <- paste0("ERROR IN ", function.name, ": annotation ARGUMENT MUST BE A COLUMN NAME OF THE data1 ARGUMENT")
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nannotation ARGUMENT MUST BE A COLUMN NAME OF THE data1 ARGUMENT")
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }else{
             if(all(is.na(data1[ , annotation]))){
-                tempo.cat <- paste0("ERROR IN ", function.name, ":\nIF NON NULL, THE annotation COLUMN OF data1 CANNOT BE JUST NA")
+                tempo.cat <- paste0("ERROR IN ", function.name, "\nIF NON NULL, THE annotation COLUMN OF data1 CANNOT BE JUST NA")
                 stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
             }
             tempo1 <- fun_check(data = annotation, class = "vector", mode = "character", na.contain = TRUE, fun.name = function.name)
             tempo2 <- fun_check(data = annotation, class = "factor", na.contain = TRUE, fun.name = function.name)
             if(tempo1$problem == TRUE & tempo2$problem == TRUE){
-                tempo.cat <- paste0("ERROR IN ", function.name, ":\nTHE annotation COLUMN OF data1 MUST BE CLASS \"factor\" OR \"character\"")
+                tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE annotation COLUMN OF data1 MUST BE CLASS \"factor\" OR \"character\"")
                 stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
             }
             if(any(duplicated(data1[, annotation]))){
@@ -381,22 +388,26 @@ fun_gg_donut <- function(
         }
     }
     if(length(data1) == 0){
-        tempo.cat <- paste0("ERROR IN ", function.name, ": THE data1 ARGUMENT IS EMPTY AFTER Inf, NA AND 0 REMOVAL IN THE ", freq, ifelse(is.null(annotation), " AND ", ", "), categ, ifelse(is.null(annotation), "", " AND "), " COLUMNS")
+        tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE data1 ARGUMENT IS EMPTY AFTER Inf, NA AND 0 REMOVAL IN THE ", freq, ifelse(is.null(annotation), " AND ", ", "), categ, ifelse(is.null(annotation), "", " AND "), " COLUMNS")
         stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
     }
-
-    if( ! is.numeric(fill.color)){
-        if( ! all(fill.color %in% colors() | grepl(pattern = "^#", fill.color), na.rm = TRUE)){
-            tempo.cat <- paste0("ERROR IN ", function.name, ": fill.color ARGUMENT MUST BE A VECTOR OF (1) HEXADECIMAL COLOR STRINGS STARTING BY #, OR (2) COLOR NAMES GIVEN BY colors(), OR (3) INTEGER VALUES")
-            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+    if( ! is.null(fill.color)){
+        if( ! is.numeric(fill.color)){
+            if( ! all(fill.color[ ! is.na(fill.color)] %in% colors() | grepl(pattern = "^#", fill.color[ ! is.na(fill.color)]), na.rm = TRUE)){
+                tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT MUST BE A VECTOR OF (1) HEXADECIMAL COLOR STRINGS STARTING BY #, OR (2) COLOR NAMES GIVEN BY colors(), OR (3) INTEGER VALUES")
+                stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+            }else{
+                fill.color <- as.character(fill.color) # remove class factor is any
+            }
         }
     }
     if( ! is.numeric(border.color)){
         if( ! (border.color %in% colors() | grepl(pattern = "^#", border.color))){
-            tempo.cat <- paste0("ERROR IN ", function.name, ": fill.color ARGUMENT MUST BE (1) A HEXADECIMAL COLOR STRING STARTING BY #, OR (2) A COLOR NAME GIVEN BY colors(), OR (3) AN INTEGER VALUE")
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nfill.color ARGUMENT MUST BE (1) A HEXADECIMAL COLOR STRING STARTING BY #, OR (2) A COLOR NAME GIVEN BY colors(), OR (3) AN INTEGER VALUE")
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
+        }else{
+            border.color <- as.character(border.color) # remove class factor is any
         }
-
     }
     # legend name filling
     if(is.null(legend.name)){
@@ -407,14 +418,14 @@ fun_gg_donut <- function(
     # verif of add
     if( ! is.null(add)){
         if( ! grepl(pattern = "^\\s*\\+", add)){ # check that the add string start by +
-            tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " "))
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " "))
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
             
         }else if( ! grepl(pattern = "(ggplot2|lemon)\\s*::", add)){ #
-            tempo.cat <- paste0("ERROR IN ", function.name, ": FOR EASIER FUNCTION DETECTION, add ARGUMENT MUST CONTAIN \"ggplot2::\" OR \"lemon::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " "))
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nFOR EASIER FUNCTION DETECTION, add ARGUMENT MUST CONTAIN \"ggplot2::\" OR \"lemon::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " "))
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }else if( ! grepl(pattern = ")\\s*$", add)){ # check that the add string finished by )
-            tempo.cat <- paste0("ERROR IN ", function.name, ": add ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " "))
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " "))
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }
     }
@@ -439,8 +450,8 @@ fun_gg_donut <- function(
             tempo.text <- "facet_grid OR facet_rep_grid"
             facet.check <- FALSE
         }
-        if(facet.check == FALSE & ! all(facet.categ %in% names(data1[[1]]))){ # WARNING: all(facet.categ %in% names(data1)) is TRUE when facet.categ is NULL
-            tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF \"", tempo.text, "\" STRING IN THE add ARGUMENT BUT PROBLEM OF VARIABLE DETECTION (COLUMN NAMES OF data1)\nTHE DETECTED VARIABLES ARE:\n", paste(facet.categ, collapse = " "), "\nTHE data1 COLUMN NAMES ARE:\n", paste(names(data1[[1]]), collapse = " "), "\nPLEASE REWRITE THE add STRING AND RERUN")
+        if(facet.check == FALSE & ! all(facet.categ %in% names(data1))){ # WARNING: all(facet.categ %in% names(data1)) is TRUE when facet.categ is NULL
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF \"", tempo.text, "\" STRING IN THE add ARGUMENT BUT PROBLEM OF VARIABLE DETECTION (COLUMN NAMES OF data1)\nTHE DETECTED VARIABLES ARE:\n", paste(facet.categ, collapse = " "), "\nTHE data1 COLUMN NAMES ARE:\n", paste(names(data1), collapse = " "), "\nPLEASE REWRITE THE add STRING AND RERUN")
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }
     }
@@ -448,13 +459,58 @@ fun_gg_donut <- function(
     # end management of add containing facet
     if( ! is.null(lib.path)){
         if( ! all(dir.exists(lib.path))){ # separation to avoid the problem of tempo$problem == FALSE and lib.path == NA
-            tempo.cat <- paste0("ERROR IN ", function.name, ": DIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n"))
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nDIRECTORY PATH INDICATED IN THE lib.path ARGUMENT DOES NOT EXISTS:\n", paste(lib.path, collapse = "\n"))
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }
     }
     # end other checkings
     # reserved word checking
-    #already done above
+    if( ! (is.null(add))){
+        if(any(sapply(X = arg.names, FUN = grepl, x = add), na.rm = TRUE)){
+            warn.count <- warn.count + 1
+            tempo.warn <- paste0("(", warn.count,") NAMES OF ", function.name, " ARGUMENTS DETECTED IN THE add STRING:\n", paste(arg.names[sapply(X = arg.names, FUN = grepl, x = add)], collapse = "\n"), "\nRISK OF WRONG OBJECT USAGE INSIDE ", function.name)
+            warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+        }
+    }
+    # verif of add
+    if( ! is.null(add)){
+        if( ! grepl(pattern = "^\\s*\\+", add)){ # check that the add string start by +
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST START WITH \"+\": ", paste(unique(add), collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between ==
+        }else if( ! grepl(pattern = "(ggplot2|lemon)\\s*::", add)){ #
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nFOR EASIER FUNCTION DETECTION, add ARGUMENT MUST CONTAIN \"ggplot2::\" OR \"lemon::\" IN FRONT OF EACH GGPLOT2 FUNCTION: ", paste(unique(add), collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between ==
+        }else if( ! grepl(pattern = ")\\s*$", add)){ # check that the add string finished by )
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nadd ARGUMENT MUST FINISH BY \")\": ", paste(unique(add), collapse = " "))
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between ==
+        }
+    }
+    # end verif of add
+    # management of add containing facet
+    facet.categ <- NULL
+    if( ! is.null(add)){
+        facet.check <- TRUE
+        tempo <- unlist(strsplit(x = add, split = "\\s*\\+\\s*(ggplot2|lemon)\\s*::\\s*")) #
+        tempo <- sub(x = tempo, pattern = "^facet_wrap", replacement = "ggplot2::facet_wrap")
+        tempo <- sub(x = tempo, pattern = "^facet_grid", replacement = "ggplot2::facet_grid")
+        tempo <- sub(x = tempo, pattern = "^facet_rep", replacement = "lemon::facet_rep")
+        if(any(grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap"), na.rm = TRUE)){
+            tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_wrap|lemon::facet_rep_wrap")])))
+            facet.categ <- names(tempo1$params$facets)
+            tempo.text <- "facet_wrap OR facet_rep_wrap"
+            facet.check <- FALSE
+        }else if(grepl(x = add, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")){
+            tempo1 <- suppressWarnings(eval(parse(text = tempo[grepl(x = tempo, pattern = "ggplot2::facet_grid|lemon::facet_rep_grid")])))
+            facet.categ <- c(names(tempo1$params$rows), names(tempo1$params$cols))
+            tempo.text <- "facet_grid OR facet_rep_grid"
+            facet.check <- FALSE
+        }
+        if(facet.check == FALSE & ! all(facet.categ %in% names(data1))){ # WARNING: all(facet.categ %in% names(data1)) is TRUE when facet.categ is NULL # all() without na.rm -> ok because facet.categ cannot be NA (tested above)
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nDETECTION OF \"", tempo.text, "\" STRING IN THE add ARGUMENT BUT PROBLEM OF VARIABLE DETECTION (COLUMN NAMES OF data1)\nTHE DETECTED VARIABLES ARE:\n", paste(facet.categ, collapse = " "), "\nTHE data1 COLUMN NAMES ARE:\n", paste(names(data1), collapse = " "), "\nPLEASE REWRITE THE add STRING AND RERUN")
+            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE) # == in stop() to be able to add several messages between ==
+        }
+    }
+    # end management of add containing facet
     # end reserved word checking
     # end second round of checking and data preparation
 
@@ -471,48 +527,66 @@ fun_gg_donut <- function(
     # main code
     data1 <- data.frame(data1, prop = data1[ , freq] / sum(data1[ , freq]))
     if(legend.add.prop == TRUE){
-        data1[ , categ] <- paste0(data1[ , categ], " (", round(data$prop, 2), ")")
+        data1[ , categ] <- paste0(data1[ , categ], " (", round(data1$prop, 2), ")")
     }
     data1[ , categ] <- factor(data1[ , categ], levels = data1[ , categ][order(data1$prop, decreasing = TRUE)]) # reorder so that the donut is according to decreasing proportion starting at the top in a clockwise direction
     data1 <- data1[order(as.numeric(data1[ , categ]), decreasing = FALSE), ] # data1[ , categ] with rows in decreasing order, according to prop
     data1 <- data.frame(data1, x = 0) # staked bar at the origin of the donut set to x = 0
     tempo.gg.name <- "gg.indiv.plot."
     tempo.gg.count <- 0
-    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ggplot(
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), eval(parse(text = paste0("ggplot2::ggplot()", if(is.null(add)){""}else{add})))) # add added here to have the facets
+    bar_width = 1
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_col(
         data = data1,
         mapping = ggplot2::aes_string(x = "x", y = freq, fill = categ), 
-    ))
-    bar_width = 1
-    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_col(color = border.color, size = border.size, width = bar_width)) # size is size of the separation in the donut
+        color = border.color, 
+        size = border.size, 
+        width = bar_width
+    )) # size is size of the separation in the donut
     # assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::geom_text(
     #     ggplot2::aes(label = Freq), 
     #     position = ggplot2::position_stack(vjust = 0.5)
     # ))
     assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_x_continuous(
         expand = c(0, 0), # prevent extra limits in x axis
-        limits = c(- bar_width / 2 - (bar_width * hole.size) / (1 - hole.size), bar_width / 2)
+        limits = c(- bar_width / 2 - (bar_width * hole.size) / (1 - hole.size), max(bar_width / 2, annotation.distance))
     )) # must be centered on x = 0
-    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ylim(c(0, max(cumsum(hole.size)))))
+    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::ylim(c(0, max(cumsum(data1[ , freq])))))
     assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::annotate(
         geom = "text", 
         x = - bar_width / 2 - (bar_width * hole.size) / (1 - hole.size), 
         y = 0, 
-        label = sum(hole.size), 
-        size = 12
+        label = sum(data1[ , freq]), 
+        size = hole.text.size
     ))
     assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::coord_polar(theta = "y", direction = -1, start = 0, clip = "on"))
-    if( ! is.null(fill.palette)){
-        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_brewer(palette = fill.palette))
+    if(is.null(fill.color) & ! is.null(fill.palette)){
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_brewer(palette = fill.palette, name = legend.name))
+    }else if( ! is.null(fill.color)){
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_manual(values = fill.color, name = legend.name, na.value = "white"))
+    }else if(! is.null(legend.name)){
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::labs(fill = legend.name))
+    }
+
+    if( ! is.null(add)){ # if add is NULL, then = 0
+        if(grepl(pattern = "ggplot2\\s*::\\s*theme", add) == TRUE){
+            warn.count <- warn.count + 1
+            tempo.warn <- paste0("(", warn.count,") \"ggplot2::theme\" STRING DETECTED IN THE add ARGUMENT\n-> INTERNAL GGPLOT2 THEME FUNCTIONS theme_void() HAS BEEN INACTIVATED, SO THAT THE USER THEME CAN BE EFFECTIVE")
+            warn <- paste0(ifelse(is.null(warn), tempo.warn, paste0(warn, "\n\n", tempo.warn)))
+            add.check <- FALSE
+        }else{
+            assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_void())
+        }
+    }else{
+        assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_void())
     }
-    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme_void())
-    assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::theme(legend.title = ggplot2::element_blank()))
     assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(
         fill = ggplot2::guide_legend(override.aes = list(color = "white", size  = 2, stroke = 1))
     )) # remove border of squares in legend
 
     # annotations on slices
     if( ! is.null(annotation)){
-        tempo <- rev(cumsum(rev(hole.size)))
+        tempo <- rev(cumsum(rev(data1[ , freq])))
         data1 <- data.frame(data1, text_y = tempo - (tempo - c(tempo[-1], 0)) / 2)
         assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggrepel::geom_text_repel(
             data = data1, 
@@ -524,7 +598,7 @@ fun_gg_donut <- function(
             size = annotation.size, 
             force = annotation.force, 
             force_pull = annotation.force.pull, 
-            nudge_x = bar_width / 2 + (bar_width - bar_width / 2) / 2, # add nudge_x to the center of the bar
+            nudge_x = annotation.distance, # knowing that the bar is centered on x = 0 and that the right edge is at bar_width / 2, 0 means center of the slice, 0.5 means at the edge if bar_width = 1
             show.legend = FALSE
         ))
     }
@@ -534,7 +608,7 @@ fun_gg_donut <- function(
     # removal of part of the legend 
     if( ! is.null(legend.limit)){
         if(sum(data1$prop >= legend.limit) == 0){
-            tempo.cat <- paste0("ERROR IN ", function.name, ": THE legend.limit PARAMETER VALUE (", legend.limit, ") IS TOO HIGH FOR THE PROPORTIONS IN THE DONUT PLOT:\n", paste0(data1$prop, collapse = "\n"))
+            tempo.cat <- paste0("ERROR IN ", function.name, "\nTHE legend.limit PARAMETER VALUE (", legend.limit, ") IS TOO HIGH FOR THE PROPORTIONS IN THE DONUT PLOT:\n", paste0(data1$prop, collapse = "\n"))
             stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
         }else{
             assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::scale_fill_discrete(
@@ -546,9 +620,9 @@ fun_gg_donut <- function(
     if(legend.show == FALSE){ # must be here because must be before bef.final.plot
         assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = "none")) # inactivate the initial legend
     }
-    bef.final.plot <- suppressMessages(ggplot2::ggplot_build(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + ")))))
+    bef.final.plot <- suppressWarnings(suppressMessages(ggplot2::ggplot_build(eval(parse(text = paste(paste0(tempo.gg.name, 1:tempo.gg.count), collapse = " + "))))))
     if( ! is.null(legend.width)){
-        legend.final <- fun_gg_get_legend(ggplot_built = bef.final.plot, fun.name = function.name, lib.path = lib.path) # get legend
+        legend.final <- suppressWarnings(suppressMessages(fun_gg_get_legend(ggplot_built = bef.final.plot, fun.name = function.name, lib.path = lib.path))) # get legend
         assign(paste0(tempo.gg.name, tempo.gg.count <- tempo.gg.count + 1), ggplot2::guides(fill = "none")) # inactivate the initial legend
         if(is.null(legend.final) & plot == TRUE){ # even if any(unlist(legend.disp)) is TRUE
             legend.final <- ggplot2::ggplot()+ggplot2::theme_void() # empty graph instead of legend
@@ -570,7 +644,7 @@ fun_gg_donut <- function(
             y = grid::unit(0, "lines"),
             hjust = 0,
             vjust = 0,
-            gp = grid::gpar(fontsize = 7)
+            gp = grid::gpar(fontsize = title.text.size)
         )
         pdf(NULL)
         final.plot <- suppressMessages(suppressWarnings(gridExtra::arrangeGrob(final.plot, top = title.grob, left = " ", right = " "))) # , left = " ", right = " " : trick to add margins in the plot. padding =  unit(0.5, "inch") is for top margin above the title
@@ -601,13 +675,6 @@ fun_gg_donut <- function(
     on.exit(exp = options(warning.length = ini.warning.length), add = TRUE)
     if(return == TRUE){
         output <- suppressMessages(ggplot2::ggplot_build(final.plot))
-        # output$data <- output$data[-1] # yes for boxplot but not for scatter # remove the first data because corresponds to the initial empty boxplot
-        if(length(output$data) != length(coord.names)){
-            tempo.cat <- paste0("INTERNAL CODE ERROR IN ", function.name, ": length(output$data) AND length(coord.names) MUST BE IDENTICAL. CODE HAS TO BE MODIFIED")
-            stop(paste0("\n\n================\n\n", tempo.cat, "\n\n================\n\n", ifelse(is.null(warn), "", paste0("IN ADDITION\nWARNING", ifelse(warn.count > 1, "S", ""), ":\n\n", warn))), call. = FALSE)
-        }else{
-            names(output$data) <- coord.names
-        }
         if(is.null(unlist(removed.row.nb))){
             removed.row.nb <- NULL
             removed.rows <- NULL
@@ -617,7 +684,7 @@ fun_gg_donut <- function(
             data = data1, 
             removed.row.nb = removed.row.nb, 
             removed.rows = removed.rows, 
-            plot = c(output$data, x.second.tick.values = list(x.second.tick.values), y.second.tick.values = list(y.second.tick.values)), 
+            plot = output$data, 
             panel = facet.categ, 
             axes = list(
                 x.range = tempo$x.range,