diff --git a/DESCRIPTION b/DESCRIPTION index bac36d3..072019b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -21,7 +21,7 @@ Suggests: covr, stringi VignetteBuilder: knitr -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.3 Config/testthat/edition: 3 Depends: R (>= 2.10) diff --git a/NAMESPACE b/NAMESPACE index f425505..65e9fc8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,7 @@ S3method(makeContent,textbox) S3method(makeContext,textbox) S3method(plot,consort) S3method(print,consort) +S3method(print,consort_defaults) S3method(widthDetails,textbox) S3method(xDetails,textbox) S3method(yDetails,textbox) @@ -17,9 +18,12 @@ export(build_grviz) export(connect_box) export(consort_plot) export(gen_text) +export(get_consort_defaults) export(get_coords) export(grid.textbox) +export(init_consort_defaults) export(move_box) +export(set_consort_defaults) export(textbox) import(grid) importFrom(stats,na.omit) diff --git a/NEWS.md b/NEWS.md index 0345d73..d5f1df0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,11 @@ +# consort 1.2.3 + +- Allow user configuration of arrow graphical parameters and padding with `set_consort_defaults()`. +- Allow custom bullet characters +- Figure styles will also be applied to grViz plot. +- Improved documentation. +- Special thanks to @Ramsas88 + # consort 1.2.2 - Use comma separators for large number. diff --git a/R/add_label_box.R b/R/add_label_box.R index 1bb8564..ca35cc1 100644 --- a/R/add_label_box.R +++ b/R/add_label_box.R @@ -10,7 +10,8 @@ #' @param only_terminal If the txt is only for the terminal box, default. Otherwise, the side box will #' also be accounted for. #' @param just The justification for the text: center (default), left or right. -#' @param ... Other parameters pass to \link{textbox}, +#' @param box_fn The function to create the box grob. Default is \code{\link[grid]{roundrectGrob}}. +#' @param ... Other parameters pass to \link{textbox}. #' #' @export #' @seealso \code{\link{add_side_box}} \code{\link{add_split}} \code{\link{textbox}} @@ -24,6 +25,7 @@ add_label_box <- function(prev_box, txt, only_terminal = TRUE, just = c("center", "left", "right"), + box_fn = roundrectGrob, ...) { just <- match.arg(just) @@ -47,13 +49,20 @@ add_label_box <- function(prev_box, stop("prev_box must be consort object") } - cex <- ifelse("cex" %in% names(getOption("txt_gp")), getOption("txt_gp")$cex, 1) + if(!"txt_gp" %in% names(dots)){ + dots$txt_gp <- consort_opt("label_txt_gp") + } + + if(!"box_gp" %in% names(dots)){ + dots$box_gp <- consort_opt("label_box_gp") + } + # Set default values args_list <- list() # args_list$text <- txt - args_list$txt_gp <- gpar(col = "#4F81BD", cex = cex, fontface = "bold") - args_list$box_gp <- gpar(fill = "#A9C7FD") - args_list$box_fn <- roundrectGrob + # args_list$txt_gp <- label_txt_gp + # args_list$box_gp <- label_box_gp + # args_list$box_fn <- roundrectGrob args_list$name <- "label" args_list <- modifyList(args_list, dots) diff --git a/R/build_grviz.R b/R/build_grviz.R index 966ee5d..57b638b 100644 --- a/R/build_grviz.R +++ b/R/build_grviz.R @@ -41,19 +41,17 @@ build_grviz <- function(x) { label_plot <- x[grepl("label", names(x))] lab_txt <- lapply(names(label_plot), function(x){ - txt_lab <- mk_text_align(label_plot[[x]]$text, label_plot[[x]]$just) + box <- label_plot[[x]]$box + grviz_style <- gpar_to_grviz_attrs(box$txt_gp, box$box_gp, box$box_fn) + txt_lab <- mk_text_align(label_plot[[x]]$text, label_plot[[x]]$just, + grviz_style = grviz_style) c(nam = x, node = paste(x, txt_lab), pos = label_plot[[x]]$prev_node) }) lab_txt <- do.call(rbind, lab_txt) - - lab_gpar <- label_plot[[1]]$gpar - - lab_nd <- sprintf("node [shape = rectangle, style = \"rounded,filled\", fillcolor = \"%s\" color = \"%s\"]\n%s\n", - lab_gpar$box_gp$fill, - lab_gpar$txt_gp$col, - paste(lab_txt[,"node"], collapse = "\n")) + + lab_nd <- paste(lab_txt[,"node"], collapse = "\n") lab_edge <- sprintf("edge[style=invis];\n%s;\n", paste(lab_txt[,"nam"], collapse = " -> ")) lab_rnk <- nodes_layout[as.numeric(lab_txt[,"pos"])] @@ -80,8 +78,11 @@ build_grviz <- function(x) { main_txt <- lapply(names(consort_plot), function(nd){ text <- consort_plot[[nd]]$text just <- consort_plot[[nd]]$just - txt_lab <- mk_text_align(text, just) - c(nam = nd, text = text, just = just, node = paste(nd, txt_lab)) + box <- consort_plot[[nd]]$box + grviz_style <- gpar_to_grviz_attrs(box$txt_gp, box$box_gp, box$box_fn) + style_str <- if(is.null(grviz_style)) "" else grviz_style + txt_lab <- mk_text_align(text, just, grviz_style = grviz_style) + c(nam = nd, text = text, just = just, grviz_style = style_str, node = paste(nd, txt_lab)) }) main_txt <- do.call(rbind, main_txt) # Remove empty label @@ -282,21 +283,26 @@ build_grviz <- function(x) { # Build dot + # Edge attributes from arrow settings + edge_attrs <- arrow_to_grviz_attrs(consort_opt("arrow_gp"), + consort_opt("arrow_type"), + consort_opt("arrow_length")) + edge_stmt <- sprintf("edge[%s];", edge_attrs) + grviz_txt <- paste("digraph consort_diagram { graph [layout = dot, splines=ortho]", + "node [shape = rectangle]", lab_nd, lab_edge, - "# node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style=\"\", fillcolor = \"\", color = \"\"]", - paste(main_txt[,"node"], collapse = "\n"), # Node + "# node definitions with substituted label text", + paste(main_txt[,"node"], collapse = "\n"), # Node "\n## Invisible point node for joints", "node [shape = point, width = 0, style=invis]", - paste(inv_nd, collapse = "\n"), # Invisible node + paste(inv_nd, collapse = "\n"), # Invisible node paste(rnk_nd, collapse = "\n"), # Ranks - "edge[style=\"\"];", + edge_stmt, paste(con_nd, collapse = "\n"), # Connections "\n}\n", - # paste(main_txt[,"txt"], collapse = "\n"), # Text sep = "\n\n") return(grviz_txt) @@ -308,15 +314,18 @@ build_grviz <- function(x) { #' @keywords internal update_node_label <- function(main_txt, node, group_name){ - + for(j in seq_along(node)){ idx_row <- main_txt[,1] == node[j] + style <- main_txt[idx_row, "grviz_style"] + if(nchar(style) == 0) style <- NULL main_txt[idx_row, "node"] <- paste(node[j], - mk_text_align(main_txt[idx_row, "text"], + mk_text_align(main_txt[idx_row, "text"], main_txt[idx_row, "just"], - group = group_name[j])) + group = group_name[j], + grviz_style = style)) } - + return(main_txt) } diff --git a/R/connect_box.R b/R/connect_box.R index 85c9032..ed4529d 100644 --- a/R/connect_box.R +++ b/R/connect_box.R @@ -15,14 +15,12 @@ #' will be started in the middle point. #' @param type Should be one the \code{"s"} (strait line), or \code{"p"} (polyline). #' @param name A character identifier of the line grob, passed to \code{\link[grid]{linesGrob}}. -#' @param lwd Line width for the connector line. Defaults to -#' \code{getOption("consort_line_lwd", default = 1)}. -#' @param col Line and arrow fill color. Defaults to -#' \code{getOption("consort_line_col", default = "black")}. +#' @param arrow_gp Graphical parameters \code{\link[grid]{gpar}} object for the +#' connector line. Defaults to \code{grid::gpar(col = "black", lwd = 1)}. #' @param arrow_length Length of the arrowhead as a \code{\link[grid]{unit}} object. -#' Defaults to \code{getOption("consort_arrow_length", default = unit(0.1, "inches"))}. +#' Defaults to \code{unit(0.1, "inches")}. #' @param arrow_type Arrow type, either \code{"closed"} or \code{"open"}. Defaults to -#' \code{getOption("consort_arrow_type", default = "closed")}. +#' \code{"closed"}. #' #' @return A lines grob with arrow. #' @export @@ -36,18 +34,17 @@ #' connect_box(fg1, fg2, connect = "bl", type = "p") #' #' # Customize line width and color -#' connect_box(fg1, fg2, connect = "bl", type = "p", lwd = 2, col = "red") +#' connect_box(fg1, fg2, connect = "bl", type = "p", arrow_gp = grid::gpar(lwd = 2, col = "red")) #' -#' # Or set globally via options -#' options(consort_line_lwd = 2, consort_line_col = "blue") +#' # Or set globally via set_consort_defaults +#' set_consort_defaults(arrow_gp = grid::gpar(lwd = 2, col = "blue")) connect_box <- function(start, end, connect, type = c("s", "p"), name = NULL, - lwd = getOption("consort_line_lwd", default = 1), - col = getOption("consort_line_col", default = "black"), - arrow_length = getOption("consort_arrow_length", default = 0.1), - arrow_type = getOption("consort_arrow_type", default = "closed")) { + arrow_gp = consort_opt("arrow_gp"), + arrow_length = consort_opt("arrow_length"), + arrow_type = consort_opt("arrow_type")) { type <- match.arg(type) @@ -146,11 +143,13 @@ connect_box <- function(start, end, y = unit.c(y_s, y_mid, y_e) ) } + + arrow_gp$fill <- arrow_gp$col linesGrob( x = line_coords$x, y = line_coords$y, - gp = gpar(fill = col, col = col, lwd = lwd), + gp = arrow_gp, arrow = arrow( length = arrow_length, ends = "last", type = arrow_type diff --git a/R/consort.R b/R/consort.R index d36abbb..a00af0c 100644 --- a/R/consort.R +++ b/R/consort.R @@ -5,7 +5,14 @@ #' is done by creating a standardized disposition data, and using this data #' as the source for the creation a standard CONSORT diagram. Human effort #' by supplying text labels on the node can also be achieved. -#' -#' @name consort-package +#' #' @docType package +#' @name consort +#' @keywords internal +"_PACKAGE" + +## usethis namespace: start +#' @import grid +#' @importFrom utils modifyList +## usethis namespace: end NULL diff --git a/R/consort_plot.R b/R/consort_plot.R index d607bec..b4e2981 100644 --- a/R/consort_plot.R +++ b/R/consort_plot.R @@ -22,7 +22,8 @@ #' is defined. #' @param kickoff_sidebox remove (default) the side box observations from the #' following counting. -#' @param cex Multiplier applied to font size, Default is 0.8 +#' @param cex Multiplier applied to font size, default is 0.8. Prefer using +#' \code{\link{set_consort_defaults}(txt_gp = gpar(cex = ...))} instead. #' @param text_width a positive integer giving the target column for wrapping #' lines in the output. String will not be wrapped if not defined (default). #' The \code{\link[stringi]{stri_wrap}} function will be used if \code{stringi} @@ -40,7 +41,8 @@ #' @export #' #' @seealso \code{\link{add_side_box}},\code{\link{add_split}}, -#' \code{\link{add_side_box}} \code{\link{textbox}} +#' \code{\link{add_side_box}} \code{\link{textbox}} +#' \code{\link{set_consort_defaults}} #' @example inst/examples/consort-plot-example.R #' @import grid #' @importFrom stats na.omit @@ -53,9 +55,13 @@ consort_plot <- function(data, kickoff_sidebox = TRUE, cex = 0.8, text_width = NULL) { - options(txt_gp = gpar(cex = cex)) - on.exit(options(txt_gp = gpar())) - + + if(!is.null(cex)){ + message("Please define this via `set_consort_defaults(txt_gp = gpar(cex = ...))` instead.") + old <- set_consort_defaults(txt_gp = gpar(cex = cex)) + on.exit(consort_global$defaults$txt_gp <- old$txt_gp, add = TRUE) + } + data <- as.data.frame(data) if(!is.list(orders)){ diff --git a/R/defaults.R b/R/defaults.R new file mode 100644 index 0000000..100c3db --- /dev/null +++ b/R/defaults.R @@ -0,0 +1,183 @@ + +consort_global <- new.env(parent = emptyenv()) + +# Default settings for consort diagram +consort_defaults_settings <- list( + arrow_gp = gpar(col = "black", lwd = 1), + txt_gp = gpar(cex = 1, col = "black"), + box_gp = gpar(fill = "white"), + label_txt_gp = gpar(col = "#4F81BD", cex = 1, fontface = "bold"), + label_box_gp = gpar(fill = "#A9C7FD"), + arrow_length = 0.1, + arrow_type = "closed", + pad_u = 3, + bullet = "\u2022" +) + +consort_global$defaults <- consort_defaults_settings + +# Internal accessor for a single default value +#' @keywords internal +consort_opt <- function(name) { + consort_global$defaults[[name]] +} + +#' Set consort diagram default options +#' +#' Modify the default graphical parameters and other settings for consort diagrams. +#' Any parameter set to \code{NULL} (the default) will remain unchanged. +#' +#' @param arrow_gp A \code{\link[grid]{gpar}} object for the arrow line. +#' @param txt_gp A \code{\link[grid]{gpar}} object for the text inside boxes. +#' @param box_gp A \code{\link[grid]{gpar}} object for the box border and fill. +#' @param label_txt_gp A \code{\link[grid]{gpar}} object for the label text. +#' @param label_box_gp A \code{\link[grid]{gpar}} object for the label box. +#' @param arrow_length Numeric, length of the arrowhead in inches. +#' @param arrow_type Character, arrow type: \code{"closed"} or \code{"open"}. +#' @param pad_u Numeric, padding between nodes. +#' @param bullet Character, bullet character for side box items. +#' +#' @return Invisibly returns the previous defaults (a \code{consort_defaults} object). +#' @export +#' @examples +#' # Change text color and box fill +#' old <- set_consort_defaults( +#' txt_gp = grid::gpar(col = "navy", cex = 0.9), +#' box_gp = grid::gpar(fill = "#F0F0F0") +#' ) +#' +#' # View current defaults +#' get_consort_defaults() +#' +#' # Restore previous defaults +#' set_consort_defaults( +#' txt_gp = old$txt_gp, +#' box_gp = old$box_gp +#' ) +set_consort_defaults <- function( + arrow_gp = NULL, + txt_gp = NULL, + box_gp = NULL, + label_txt_gp = NULL, + label_box_gp = NULL, + arrow_length = NULL, + arrow_type = NULL, + pad_u = NULL, + bullet = NULL +) { + + old <- get_consort_defaults() + + # Map parameter names to option names + args <- list( + arrow_gp = arrow_gp, + txt_gp = txt_gp, + box_gp = box_gp, + label_txt_gp = label_txt_gp, + label_box_gp = label_box_gp, + arrow_length = arrow_length, + arrow_type = arrow_type, + pad_u = pad_u, + bullet = bullet + ) + + # Keep only non-NULL arguments + args <- Filter(Negate(is.null), args) + + if (length(args) == 0) return(invisible(old)) + + # Validate and merge gpar arguments into existing defaults + gpar_params <- c("arrow_gp", "txt_gp", "box_gp", + "label_txt_gp", "label_box_gp") + for (nm in intersect(names(args), gpar_params)) { + if (!inherits(args[[nm]], "gpar")) + stop(sprintf("`%s` must be a gpar() object.", nm)) + args[[nm]] <- do.call(gpar, + utils::modifyList(as.list(consort_global$defaults[[nm]]), + as.list(args[[nm]]))) + } + + if (!is.null(arrow_length)) { + if (!is.numeric(arrow_length) || length(arrow_length) != 1 || is.na(arrow_length)) + stop("`arrow_length` must be a single numeric value.") + } + + if (!is.null(arrow_type)) { + arrow_type <- match.arg(arrow_type, c("closed", "open")) + args$arrow_type <- arrow_type + } + + if (!is.null(pad_u)) { + if (!is.numeric(pad_u) || length(pad_u) != 1 || is.na(pad_u)) + stop("`pad_u` must be a single numeric value.") + } + + if (!is.null(bullet)) { + if (!is.character(bullet) || length(bullet) != 1) + stop("`bullet` must be a single character string.") + } + + # Update stored defaults + consort_global$defaults <- utils::modifyList(consort_global$defaults, args) + + invisible(old) +} + +#' Get consort diagram default options +#' +#' @return A \code{consort_defaults} object containing all current default settings. +#' @export +#' @rdname set_consort_defaults +#' @examples +#' get_consort_defaults() +get_consort_defaults <- function() { + x <- consort_global$defaults + class(x) <- "consort_defaults" + x +} + +#' @rdname set_consort_defaults +#' @export +init_consort_defaults <- function() { + x <- consort_defaults_settings + consort_global$defaults <- x + class(x) <- "consort_defaults" + invisible(x) +} + +#' @param x A \code{consort_defaults} object. +#' @param ... Not used. +#' @rdname set_consort_defaults +#' @export +print.consort_defaults <- function(x, ...) { + cat("Consort diagram default settings:\n\n") + + for (nm in names(x)) { + label <- nm + val <- x[[nm]] + + if (inherits(val, "gpar")) { + parts <- vapply(names(val), function(p) { + v <- val[[p]] + if (is.numeric(v) && !is.null(names(v))) { + # gpar stores fontface as named numeric (e.g., font = c(bold = 2)) + sprintf('%s = "%s"', p, names(v)) + } else if (is.character(v)) { + sprintf('%s = "%s"', p, v) + } else { + sprintf("%s = %s", p, v) + } + }, character(1)) + cat(sprintf(" %-14s: gpar(%s)\n", label, paste(parts, collapse = ", "))) + } else if (is.character(val)) { + cat(sprintf(" %-14s: \"%s\"\n", label, val)) + } else { + cat(sprintf(" %-14s: %s\n", label, val)) + } + } + + invisible(x) +} + + + diff --git a/R/gen_text.R b/R/gen_text.R index 667caa5..76e7092 100644 --- a/R/gen_text.R +++ b/R/gen_text.R @@ -12,7 +12,7 @@ #' @param bullet If shows bullet points. If the value is \code{TRUE}, the bullet points #' will be tabulated, default is \code{FALSE}. #' @param bullet_char A single character used as the bullet symbol. Defaults to -#' \code{getOption("consort_bullet", default = "\u2022")}. Can be any Unicode +#' \code{consort_opt("bullet")}. Can be any Unicode #' character such as \code{"\u2013"} (en-dash), \code{"\u25CB"} (circle), #' \code{"\u25A0"} (square), \code{"-"}, etc. #' @@ -38,11 +38,11 @@ #' # Use a custom bullet character #' gen_text(val$car, label = "Cars in the data", bullet = TRUE, bullet_char = "-") #' -#' # Or set globally via options -#' options(consort_bullet = "\u25CB") +#' # Or set globally via set_consort_defaults +#' set_consort_defaults(bullet = "\u25CB") #' gen_text(val$car, label = "Cars in the data", bullet = TRUE) gen_text <- function(x, label = NULL, bullet = FALSE, - bullet_char = getOption("consort_bullet", default = "\u2022")) { + bullet_char = consort_opt("bullet")) { if(!is.null(label) & length(label)>1) stop("label must be of length 1") @@ -96,7 +96,7 @@ gen_text <- function(x, label = NULL, bullet = FALSE, # Calculate the numbers in the box use the data provided. #' @keywords internal box_data.frame <- function(x, label = NULL, - bullet_char = getOption("consort_bullet", default = "\u2022")){ + bullet_char = consort_opt("bullet")){ if(ncol(x) != 2) stop("only two columns are supported") @@ -120,7 +120,7 @@ box_data.frame <- function(x, label = NULL, # Calculate the numbers in the box #' @keywords internal box_label <- function(x, label, bullet = TRUE, - bullet_char = getOption("consort_bullet", default = "\u2022")) { + bullet_char = consort_opt("bullet")) { # Blank as NA if (is.character(x)) { diff --git a/R/grid_util.R b/R/grid_util.R index 27661b6..7928eb5 100644 --- a/R/grid_util.R +++ b/R/grid_util.R @@ -1,212 +1,243 @@ +# Stack rows top-to-bottom, with extra padding at split/merge transitions +#' @keywords internal +calc_y_coords <- function(consort_plot, nodes_layout, pad_u) { + nd_y <- vector("list", length = length(nodes_layout)) + prev_bt <- 0 + + for (i in seq_along(nodes_layout)) { + heights <- sapply(consort_plot[nodes_layout[[i]]], function(x) + get_coords(x$box)$height + ) + + if (i == 1) { + nd_y[[i]] <- heights / 2 + pad_u / 2 + prev_bt <- max(heights) + } else { + # Extra padding when column count changes (split/merge transition) + extra_pad <- if (length(nd_y[[i]]) != length(nd_y[[i - 1]])) 2 * pad_u else pad_u + nd_y[[i]] <- prev_bt + extra_pad + heights / 2 + prev_bt <- prev_bt + extra_pad + max(heights) + } + names(nd_y[[i]]) <- names(heights) + } + + list(nd_y = nd_y, total_height = prev_bt + pad_u) +} + +# Calculate X positions for multi-column rows, accounting for sidebox extents. +# main_col_widths: max node width per column from non-sidebox rows +# sb_wd_mat: matrix of widths from sidebox rows (rows x columns), or NULL +# sb_sd_mat: matrix of sides ("left"/"right") from sidebox rows, or NULL +#' @keywords internal +calc_column_x <- function(main_col_widths, sb_wd_mat, sb_sd_mat, pad_u) { + n_cols <- length(main_col_widths) + has_sb <- !is.null(sb_sd_mat) + pos <- numeric(n_cols) + + # Left extent of column 1 + left_space <- main_col_widths[1] / 2 + if (has_sb && any(sb_sd_mat[, 1] == "left")) { + sb_wd <- sb_wd_mat[sb_sd_mat[, 1] == "left", 1] + left_space <- max(c(left_space, max(sb_wd))) + } + pos[1] <- left_space + pad_u + + if (n_cols > 1) { + for (j in 2:n_cols) { + # Right extent of previous column + right_space <- main_col_widths[j - 1] / 2 + prev_wide_right <- FALSE + if (has_sb && any(sb_sd_mat[, j - 1] == "right")) { + sb_wd <- sb_wd_mat[sb_sd_mat[, j - 1] == "right", j - 1] + right_space <- max(c(right_space, max(sb_wd))) + prev_wide_right <- right_space > main_col_widths[j - 1] / 2 + } + + # Left extent of current column + left_space <- main_col_widths[j] / 2 + if (has_sb && any(sb_sd_mat[, j] == "left")) { + sb_wd <- sb_wd_mat[sb_sd_mat[, j] == "left", j] + left_space <- max(c(left_space, max(sb_wd))) + } else if (prev_wide_right) { + # Previous column's sidebox already extends past its center, + # so reduce this column's left extent to avoid excessive spacing + left_space <- pad_u / 2 + } + + pos[j] <- pos[j - 1] + right_space + pad_u + left_space + } + } + + pos - mean(pos) +} + +# Offset sidebox nodes from their parent column's X position +#' @keywords internal +place_sideboxes <- function(col_x, sb_widths, sb_sides, pad_u) { + n_cols <- length(col_x) + sb_x <- numeric(n_cols) + for (k in seq_len(n_cols)) { + sb_x[k] <- if (sb_sides[k] == "right") { + col_x[k] + sb_widths[k] / 2 + pad_u / 2 + } else { + col_x[k] - sb_widths[k] / 2 - pad_u / 2 + } + } + sb_x +} + +# Recenter parent nodes at the midpoint of their children after a second split +#' @keywords internal +adjust_multisplit <- function(nd_x, nd_type, nodes_layout, consort_plot) { + n_splits <- sum(nd_type == "splitbox") + if (n_splits <= 1) return(nd_x) + if (n_splits > 2) stop("More than two splits are not supported.") + + split_idx <- which(nd_type == "splitbox")[-1] + prev_nodes <- sapply(unlist(nodes_layout[split_idx]), function(y) { + consort_plot[[y]]$prev_node + }, simplify = FALSE) + prev_nodes <- unlist(prev_nodes) + + for (parent in unique(prev_nodes)) { + children_x <- nd_x[[split_idx]][names(prev_nodes[prev_nodes == parent])] + nd_x[[split_idx - 1]][parent] <- mean(range(children_x)) + } + + nd_x +} + +# Shift all X coordinates so minimum is 0 and compute final bounds +#' @keywords internal +normalize_x <- function(nd_x, nd_wd, nodes_layout) { + nd_minmax <- lapply(seq_along(nodes_layout), function(i) { + n_cols <- length(nodes_layout[[i]]) + if (n_cols > 1) { + wd_mat <- do.call(rbind, nd_wd[i]) + x_mat <- do.call(rbind, nd_x[i]) + c(minx = min(x_mat[, 1] - wd_mat[, 1] / 2), + maxx = max(x_mat[, n_cols] + wd_mat[, n_cols] / 2)) + } else { + c(minx = nd_x[[i]] - nd_wd[[i]] / 2, + maxx = nd_x[[i]] + nd_wd[[i]] / 2) + } + }) + + bounds <- do.call(rbind, Filter(Negate(is.null), nd_minmax)) + min_val <- min(bounds[, 1]) + max_val <- max(bounds[, 2]) + + for (i in seq_along(nodes_layout)) { + nd_x[[i]] <- nd_x[[i]] - min_val + } + + list(nd_x = nd_x, max_width = max_val - min_val) +} + # Calculate coordinates #' @keywords internal #' @importFrom stats setNames -calc_coords <- function(consort_plot){ - - # Get the maximum of height and width of each node +calc_coords <- function(consort_plot) { + nodes_layout <- attr(consort_plot, "nodes.list") - - # Node type of each - nd_type <- sapply(nodes_layout, function(x) + + # Node type per row + nd_type <- sapply(nodes_layout, function(x) unique(sapply(consort_plot[x], "[[", "node_type")) ) - - if(nd_type[length(nd_type)] == "sidebox") + + if (nd_type[length(nd_type)] == "sidebox") stop("The last node can not be a side box.") + pad_u <- consort_opt("pad_u") + if (!is.numeric(pad_u) || length(pad_u) != 1L || is.na(pad_u)) + stop("`pad_u` must be a single, non-NA numeric value.") - # Calculate Y - # Use configurable padding via global option, default 3 for backward compatibility - pad_u <- getOption("consort_pad_u", default = 3) - - nd_y <- vector("list", length = length(nodes_layout)) - for(i in seq_along(nodes_layout)){ - nd <- sapply(consort_plot[nodes_layout[[i]]], function(x) - get_coords(x$box)$height - ) - if(i == 1){ - nd_y[[i]] <- nd/2 + pad_u/2 - prev_bt <- max(nd) - }else{ - - if(length(nd_y[[i]]) != length(nd_y[[i-1]])) - add_padd <- 2*pad_u - else - add_padd <- pad_u - - nd_y[[i]] <- prev_bt + add_padd + nd/2 - prev_bt <- prev_bt + add_padd + max(nd) - } - names(nd_y[[i]]) <- names(nd) - } - - # Calculate X - nd_x <- vector("list", length = length(nodes_layout)) - idx <- sapply(nodes_layout, length) - nd_gp <- gp_consecutive(idx) - - # Nodes width - nd_wd <- lapply(nodes_layout, function(nd){ - sapply(consort_plot[nd], function(x){ - get_coords(x$box)$width - }) + # --- Phase 1: Y coordinates --- + y_result <- calc_y_coords(consort_plot, nodes_layout, pad_u) + nd_y <- y_result$nd_y + + # --- Phase 2: Gather node widths and sides --- + nd_wd <- lapply(nodes_layout, function(nd) { + sapply(consort_plot[nd], function(x) get_coords(x$box)$width) }) - - # Nodes side - nd_sides <- lapply(nodes_layout, function(nd){ - unlist(sapply(consort_plot[nd], function(nd)nd$side)) + + nd_sides <- lapply(nodes_layout, function(nd) { + unlist(sapply(consort_plot[nd], function(x) x$side)) }) - for(i in unique(nd_gp)){ - idx_layout <- which(nd_gp %in% i) - sub_layout <- nodes_layout[idx_layout] - - sub_len <- unique(sapply(sub_layout, length)) - - sb_wd <- do.call(rbind, nd_wd[idx_layout]) - nd_tp <- nd_type[idx_layout] - nd_sd <- nd_sides[idx_layout][nd_tp %in% "sidebox"] - nd_sd <- do.call(rbind, nd_sd) - - if(sub_len == 1){ - for(j in idx_layout){ - if(nd_type[j] != "sidebox"){ - nd_x[[j]] <- 0 - }else{ - nd_x[[j]] <- ifelse(nd_sides[[j]] == "right", - nd_wd[[j]]/2 + pad_u, - -nd_wd[[j]]/2 - pad_u) + # --- Phase 3: X coordinates --- + nd_x <- vector("list", length = length(nodes_layout)) + col_counts <- sapply(nodes_layout, length) + row_groups <- gp_consecutive(col_counts) + + for (gp in unique(row_groups)) { + gp_rows <- which(row_groups == gp) + n_cols <- unique(col_counts[gp_rows]) + gp_types <- nd_type[gp_rows] + + if (n_cols == 1) { + # Single-column: main nodes at center, sideboxes offset + for (r in gp_rows) { + if (nd_type[r] != "sidebox") { + nd_x[[r]] <- 0 + } else { + nd_x[[r]] <- ifelse(nd_sides[[r]] == "right", + nd_wd[[r]] / 2 + pad_u, + -nd_wd[[r]] / 2 - pad_u) } - names(nd_x[[j]]) <- nodes_layout[[j]] + names(nd_x[[r]]) <- nodes_layout[[r]] } - }else{ - - if(any(nd_tp %in% "sidebox")){ - pos_tmp <- apply(sb_wd[!nd_tp %in% "sidebox",], 2, max) - pos_x <- vector("numeric", length = sub_len) - - # Calculate x for splits - for(j in 1:sub_len){ - if(j == 1){ - if(any("left" %in% nd_sd[,1])){ - - # Width of the left - lt_max <- sb_wd[nd_tp %in% "sidebox", 1][nd_sd[,1] %in% "left"] - - pos_x[1] <- max(c(max(lt_max), pos_tmp[1]/2)) - - }else{ - pos_x[1] <- pos_tmp[1]/2 - } - pos_x[1] <- pos_x[1] + pad_u - }else{ - if(any("right" %in% nd_sd[,j-1])){ - rt_max <- sb_wd[nd_tp %in% "sidebox", j - 1][nd_sd[,j-1] %in% "right"] - - rt_max <- max(c(max(rt_max), pos_tmp[j-1]/2)) - prevnd_right <- TRUE - - }else{ - rt_max <- pos_tmp[j-1]/2 - prevnd_right <- FALSE - } - - if(any("left" %in% nd_sd[,j])){ - lt_max <- sb_wd[nd_tp %in% "sidebox", 1][nd_sd[,j] %in% "left"] - - lt_max <- max(c(max(lt_max), pos_tmp[j]/2)) - - }else{ - if(prevnd_right & max(rt_max) > pos_tmp[j-1]/2) - lt_max <- pad_u/2 - else - lt_max <- pos_tmp[j]/2 - } - - pos_x[j] <- pos_x[j-1] + lt_max + rt_max + pad_u - - } - } + } else { + # Multi-column + wd_mat <- do.call(rbind, nd_wd[gp_rows]) + is_sb <- gp_types == "sidebox" + + if (any(is_sb)) { + # Column positions with sidebox-aware spacing + main_col_widths <- apply(wd_mat[!is_sb, , drop = FALSE], 2, max) + sb_wd_mat <- wd_mat[is_sb, , drop = FALSE] + sb_sd_mat <- do.call(rbind, nd_sides[gp_rows][is_sb]) + + col_x <- calc_column_x(main_col_widths, sb_wd_mat, sb_sd_mat, pad_u) - pos_x <- pos_x - mean(pos_x) - - for(j in idx_layout){ - if(nd_type[j] != "sidebox"){ - nd_x[[j]] <- pos_x - }else{ - sd_tmp <- nd_sides[[j]] - for(k in 1:sub_len){ - nd_x[[j]][k] <- ifelse(sd_tmp[k] == "right", - pos_x[k] + nd_wd[[j]][k]/2 + pad_u/2, - pos_x[k] - nd_wd[[j]][k]/2 - pad_u/2) - } + for (r in gp_rows) { + if (nd_type[r] != "sidebox") { + nd_x[[r]] <- col_x + } else { + nd_x[[r]] <- place_sideboxes(col_x, nd_wd[[r]], nd_sides[[r]], pad_u) } - - names(nd_x[[j]]) <- nodes_layout[[j]] + names(nd_x[[r]]) <- nodes_layout[[r]] } - - }else{ - pos_tmp <- apply(sb_wd, 2, max) - pos_x <- pos_tmp/2 + c(0, cumsum(pos_tmp[-length(pos_tmp)] + 4*pad_u)) - # Make sure center is 0 - pos_x <- pos_x - mean(pos_x) - for(j in idx_layout){ - nd_x[[j]] <- pos_x - names(nd_x[[j]]) <- nodes_layout[[j]] + } else { + # No sideboxes: simple equal spacing + col_widths <- apply(wd_mat, 2, max) + col_x <- col_widths / 2 + c(0, cumsum(col_widths[-length(col_widths)] + 4 * pad_u)) + col_x <- col_x - mean(col_x) + + for (r in gp_rows) { + nd_x[[r]] <- col_x + names(nd_x[[r]]) <- nodes_layout[[r]] } } } } - - # For multiple split - if(sum(nd_type == "splitbox") > 1){ - if(sum(nd_type == "splitbox") > 2) - stop("More than two splits are not supported.") - - # Recalculate the x coordinates for multiple split - split_idx <- which(nd_type == "splitbox")[-1] - - prev_node <- sapply(unlist(nodes_layout[split_idx]), function(y){ - consort_plot[[y]]$prev_node - }, simplify = FALSE) - - prev_node <- unlist(prev_node) - for(i in unique(prev_node)){ - # Get x of next nodes - next_nd_x <- nd_x[[split_idx]][names(prev_node[prev_node==i])] - nd_x[[split_idx-1]][i] <- min(next_nd_x) + (max(next_nd_x) - min(next_nd_x))/2 - } - } - - # Adjust coordinates - nd_minmax <- lapply(seq_along(nodes_layout), function(x){ - nd_len <- length(nodes_layout[[x]]) - if(nd_len != 1){ - tmp_wd <- do.call(rbind, nd_wd[x]) - tmp_x <- do.call(rbind, nd_x[x]) - max_x <- max(tmp_x[,nd_len] + tmp_wd[,nd_len]/2) - min_x <- min(tmp_x[, 1] - tmp_wd[,1]/2) - return(c(minx = min_x, maxx = max_x)) - }else{ - return(c(minx = nd_x[[x]] - nd_wd[[x]]/2, maxx = nd_x[[x]] + nd_wd[[x]]/2)) - } - }) - nd_minmax <- Filter(Negate(is.null), nd_minmax) - min_val <- min(do.call(rbind, nd_minmax)[,1]) - max_val <- max(do.call(rbind, nd_minmax)[,2]) - for(i in seq_along(nodes_layout)){ - nd_x[[i]] <- nd_x[[i]] - min_val - } - max_width <- max_val - min_val - - - return(list(x = unlist(nd_x), - y = unlist(nd_y), - nodes_hw = nd_wd, - nd_x = nd_x, - nd_y = nd_y, - max_width = max_width, - max_height = prev_bt + pad_u)) + + # --- Phase 4: Multiple split adjustment --- + nd_x <- adjust_multisplit(nd_x, nd_type, nodes_layout, consort_plot) + + # --- Phase 5: Normalize to positive coordinates --- + x_result <- normalize_x(nd_x, nd_wd, nodes_layout) + + list( + x = unlist(x_result$nd_x), + y = unlist(nd_y), + nodes_hw = nd_wd, + nd_x = x_result$nd_x, + nd_y = nd_y, + max_width = x_result$max_width, + max_height = y_result$total_height + ) } # Calculate coordinates diff --git a/R/grviz_util.R b/R/grviz_util.R index b31ba94..a3413d1 100644 --- a/R/grviz_util.R +++ b/R/grviz_util.R @@ -1,4 +1,116 @@ +# Convert R gpar to Graphviz node attributes +#' @keywords internal +gpar_to_grviz_attrs <- function(txt_gp, box_gp, box_fn = NULL) { + attrs <- character() + style_parts <- character() + + # Rounded corners for roundrectGrob + if (!is.null(box_fn) && identical(box_fn, grid::roundrectGrob)) { + style_parts <- c(style_parts, "rounded") + } + + # Box fill color + if (!is.null(box_gp$fill) && nzchar(box_gp$fill)) { + attrs <- c(attrs, sprintf('fillcolor="%s"', box_gp$fill)) + style_parts <- c(style_parts, "filled") + } + + # Box border color + if (!is.null(box_gp$col)) { + attrs <- c(attrs, sprintf('color="%s"', box_gp$col)) + } + + # Box border width + if (!is.null(box_gp$lwd)) { + attrs <- c(attrs, sprintf('penwidth=%s', box_gp$lwd)) + } + + # Line type + if (!is.null(box_gp$lty)) { + lty_val <- box_gp$lty + lty_map <- c("2" = "dashed", "3" = "dotted", "4" = "dotdash", + "5" = "longdash", "6" = "twodash", + "dashed" = "dashed", "dotted" = "dotted", + "dotdash" = "dotdash", "longdash" = "longdash", + "twodash" = "twodash") + lty_str <- as.character(lty_val) + if (lty_str %in% names(lty_map)) { + style_parts <- c(style_parts, lty_map[lty_str]) + } + } + + # Style attribute + if (length(style_parts) > 0) { + attrs <- c(attrs, sprintf('style="%s"', paste(style_parts, collapse = ","))) + } + + # Text color + if (!is.null(txt_gp$col)) { + attrs <- c(attrs, sprintf('fontcolor="%s"', txt_gp$col)) + } + + # Font size + if (!is.null(txt_gp$fontsize)) { + attrs <- c(attrs, sprintf('fontsize=%s', txt_gp$fontsize)) + } else if (!is.null(txt_gp$cex) && txt_gp$cex != 1) { + attrs <- c(attrs, sprintf('fontsize=%s', round(14 * txt_gp$cex, 1))) + } + + # Font family + if (!is.null(txt_gp$fontfamily) && nzchar(txt_gp$fontfamily)) { + attrs <- c(attrs, sprintf('fontname="%s"', txt_gp$fontfamily)) + } + + if (length(attrs) == 0) return(NULL) + paste(attrs, collapse = " ") +} + +# Convert arrow settings to Graphviz edge attributes +#' @keywords internal +arrow_to_grviz_attrs <- function(arrow_gp, arrow_type, arrow_length) { + attrs <- character() + + # Edge color + if (!is.null(arrow_gp$col)) { + attrs <- c(attrs, sprintf('color="%s"', arrow_gp$col)) + } + + # Edge line width + if (!is.null(arrow_gp$lwd)) { + attrs <- c(attrs, sprintf('penwidth=%s', arrow_gp$lwd)) + } + + # Edge line type — always emit style to reset from label edges' style=invis + edge_style <- "" + if (!is.null(arrow_gp$lty)) { + lty_map <- c("2" = "dashed", "3" = "dotted", "4" = "dotdash", + "5" = "longdash", "6" = "twodash", + "dashed" = "dashed", "dotted" = "dotted", + "dotdash" = "dotdash", "longdash" = "longdash", + "twodash" = "twodash") + lty_str <- as.character(arrow_gp$lty) + if (lty_str %in% names(lty_map)) { + edge_style <- lty_map[lty_str] + } + } + attrs <- c(attrs, sprintf('style="%s"', edge_style)) + + # Arrowhead type: R "closed" -> Graphviz "normal", R "open" -> "vee" + if (!is.null(arrow_type)) { + ah <- if (arrow_type == "open") "vee" else "normal" + attrs <- c(attrs, sprintf('arrowhead="%s"', ah)) + } + + # Arrow size: scale relative to default 0.1 inches + if (!is.null(arrow_length) && is.numeric(arrow_length)) { + attrs <- c(attrs, sprintf('arrowsize=%.2f', arrow_length / 0.1)) + } + + if (length(attrs) == 0) return("") + paste(attrs, collapse = " ") +} + # Make subgraph same rank #' @keywords internal mk_subgraph_rank <- function(x){ @@ -15,25 +127,27 @@ mk_invs_connect <- function(x){ # Make text alignment #' @keywords internal -mk_text_align <- function(text, just, group = NULL){ +mk_text_align <- function(text, just, group = NULL, grviz_style = NULL){ # If empty # if(is_empty(text)) # return("") - - jst <- ifelse(just == "center", "", + + jst <- ifelse(just == "center", "", ifelse(just == "left", "\\l", "\r")) - + if(just %in% c("left", "right")){ text <- unlist(strsplit(text, "\n")) text <- ifelse(just == "left", paste(text, collapse = "\\l"), paste(text, collapse = "\r")) } - - if(is.null(group)) - r <- sprintf("[label = \"%s%s\"]", text, jst) - else - r <- sprintf("[label = \"%s%s\" group=%s]", text, jst, group) + + # Build attributes + attr_parts <- sprintf('label = "%s%s"', text, jst) + if(!is.null(group)) attr_parts <- paste(attr_parts, sprintf("group=%s", group)) + if(!is.null(grviz_style)) attr_parts <- paste(attr_parts, grviz_style) + + r <- sprintf("[%s]", attr_parts) if(is_empty(text)){ r <- gsub('.{1}$', ' shape=none height=0 width=0]', r) diff --git a/R/textbox.R b/R/textbox.R index 98267c0..5b192ff 100644 --- a/R/textbox.R +++ b/R/textbox.R @@ -11,12 +11,14 @@ #' @param just The justification of the text, `"left"`, `"right` and `"center"`. #' See \link[grid]{textGrob} for more details. #' @param txt_gp An object of class \link[grid]{gpar} style to be applied to the -#' text. This will also be read from global options of \code{"txt_gp"}. For -#' example, if one wants to set a font size for all the text inside box, -#' \code{options(txt_gp = gpar(cex = 0.8))} will do the trick. +#' text. Defaults are read from \code{\link{set_consort_defaults}}. For +#' example, to set a font size for all text inside boxes, use +#' \code{set_consort_defaults(txt_gp = gpar(cex = 0.8))}. +#' @param leftrotate If the text box will be rotated 90 degrees counter-clockwise. +#' Default is \code{FALSE}. #' @param box_fn Function to create box for the text. Parameters of `x=0.5`, #' `y=0.5` and `box_gp` will be passed to this function and return a \code{grob} -#' object. This will also be read from global options of \code{"box_gp"}. +#' object. #' @param box_gp An object of class \link[grid]{gpar} style to be applied to the #' box. #' @param name A character identifier. @@ -35,25 +37,29 @@ textbox <- function(text, x = unit(.5, "npc"), y = unit(.5, "npc"), just = c("center", "left", "right"), - txt_gp = getOption("txt_gp", default = gpar( - color = "black", - cex = 1 - )), + txt_gp = consort_opt("txt_gp"), + leftrotate = FALSE, box_fn = roundrectGrob, - box_gp = getOption("box_gp", default = gpar(fill = "white")), + box_gp = consort_opt("box_gp"), name = "textbox") { just <- match.arg(just) + if (!is.logical(leftrotate) || length(leftrotate) != 1 || is.na(leftrotate)) { + stop("`leftrotate` must be a single TRUE/FALSE value.") + } + if (!is.unit(x)) x <- unit(x, units = "npc") if (!is.unit(y)) y <- unit(y, units = "npc") + angle <- if (isTRUE(leftrotate)) 90 else 0 + # class(fg) <- union("box", class(fg)) name <- paste(name, auto_index(), sep = ".") gTree( label = text, x = x, y = y, just = just, txt_gp = txt_gp, box_fn = box_fn, - box_gp = box_gp, + box_gp = box_gp, angle = angle, name = name, cl = "textbox" ) @@ -90,6 +96,7 @@ makeContext.textbox <- function(x) { tbvp <- viewport(x$x, x$y, width = hw$width, height = hw$height, + angle = x$angle, name = paste0(x$name, ".vp") ) diff --git a/R/utils.R b/R/utils.R index 7ba0da4..16bc833 100644 --- a/R/utils.R +++ b/R/utils.R @@ -41,10 +41,11 @@ is_empty <- function(x){ is.null(x) | x == "" | is.na(x) } + # Wrap text #' @keywords internal #' -text_wrap <- function(txt, width = 0.9 * getOption("width")) { +text_wrap <- function(txt, width = 0.9) { if (length(txt) > 1) { stop("Vector does not supported!") } diff --git a/data/adsl.rda b/data/adsl.rda deleted file mode 100644 index e579388..0000000 Binary files a/data/adsl.rda and /dev/null differ diff --git a/man/add_label_box.Rd b/man/add_label_box.Rd index b31ba9b..8712ea3 100644 --- a/man/add_label_box.Rd +++ b/man/add_label_box.Rd @@ -9,6 +9,7 @@ add_label_box( txt, only_terminal = TRUE, just = c("center", "left", "right"), + box_fn = roundrectGrob, ... ) } @@ -25,7 +26,9 @@ also be accounted for.} \item{just}{The justification for the text: center (default), left or right.} -\item{...}{Other parameters pass to \link{textbox},} +\item{box_fn}{The function to create the box grob. Default is \code{\link[grid]{roundrectGrob}}.} + +\item{...}{Other parameters pass to \link{textbox}.} } \value{ A \code{consort} object. diff --git a/man/connect_box.Rd b/man/connect_box.Rd index f8f0958..5c0dceb 100644 --- a/man/connect_box.Rd +++ b/man/connect_box.Rd @@ -4,7 +4,16 @@ \alias{connect_box} \title{Connect grob box with arrow.} \usage{ -connect_box(start, end, connect, type = c("s", "p"), name = NULL) +connect_box( + start, + end, + connect, + type = c("s", "p"), + name = NULL, + arrow_gp = consort_opt("arrow_gp"), + arrow_length = consort_opt("arrow_length"), + arrow_type = consort_opt("arrow_type") +) } \arguments{ \item{start}{Starting point of the arrow.} @@ -22,6 +31,15 @@ will be started in the middle point.} \item{type}{Should be one the \code{"s"} (strait line), or \code{"p"} (polyline).} \item{name}{A character identifier of the line grob, passed to \code{\link[grid]{linesGrob}}.} + +\item{arrow_gp}{Graphical parameters \code{\link[grid]{gpar}} object for the +connector line. Defaults to \code{grid::gpar(col = "black", lwd = 1)}.} + +\item{arrow_length}{Length of the arrowhead as a \code{\link[grid]{unit}} object. +Defaults to \code{unit(0.1, "inches")}.} + +\item{arrow_type}{Arrow type, either \code{"closed"} or \code{"open"}. Defaults to +\code{"closed"}.} } \value{ A lines grob with arrow. @@ -36,4 +54,10 @@ fg2 <- textbox(text = "This is an other test", 0.7, 0.2) grid::grid.draw(fg1) grid::grid.draw(fg2) connect_box(fg1, fg2, connect = "bl", type = "p") + +# Customize line width and color +connect_box(fg1, fg2, connect = "bl", type = "p", arrow_gp = grid::gpar(lwd = 2, col = "red")) + +# Or set globally via set_consort_defaults +set_consort_defaults(arrow_gp = grid::gpar(lwd = 2, col = "blue")) } diff --git a/man/consort-package.Rd b/man/consort.Rd similarity index 61% rename from man/consort-package.Rd rename to man/consort.Rd index 6aa4c90..df4d448 100644 --- a/man/consort-package.Rd +++ b/man/consort.Rd @@ -1,8 +1,9 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/consort.R \docType{package} -\name{consort-package} +\name{consort} \alias{consort-package} +\alias{consort} \title{Create Consort diagram} \description{ To make it easy to create CONSORT diagrams for the transparent reporting @@ -11,3 +12,16 @@ To make it easy to create CONSORT diagrams for the transparent reporting as the source for the creation a standard CONSORT diagram. Human effort by supplying text labels on the node can also be achieved. } +\seealso{ +Useful links: +\itemize{ + \item \url{https://github.com/adayim/consort/} + \item Report bugs at \url{https://github.com/adayim/consort/issues} +} + +} +\author{ +\strong{Maintainer}: Alim Dayim \email{ad938@cam.ac.uk} (\href{https://orcid.org/0000-0001-9998-7463}{ORCID}) + +} +\keyword{internal} diff --git a/man/consort_plot.Rd b/man/consort_plot.Rd index 354a5f6..960f7fe 100644 --- a/man/consort_plot.Rd +++ b/man/consort_plot.Rd @@ -40,7 +40,8 @@ is defined.} \item{kickoff_sidebox}{remove (default) the side box observations from the following counting.} -\item{cex}{Multiplier applied to font size, Default is 0.8} +\item{cex}{Multiplier applied to font size, default is 0.8. Prefer using +\code{\link{set_consort_defaults}(txt_gp = gpar(cex = ...))} instead.} \item{text_width}{a positive integer giving the target column for wrapping lines in the output. String will not be wrapped if not defined (default). @@ -85,5 +86,6 @@ p <- consort_plot(data = df, } \seealso{ \code{\link{add_side_box}},\code{\link{add_split}}, -\code{\link{add_side_box}} \code{\link{textbox}} +\code{\link{add_side_box}} \code{\link{textbox}} +\code{\link{set_consort_defaults}} } diff --git a/man/gen_text.Rd b/man/gen_text.Rd index 62719b1..c685fd3 100644 --- a/man/gen_text.Rd +++ b/man/gen_text.Rd @@ -4,7 +4,7 @@ \alias{gen_text} \title{Generate label and bullet points} \usage{ -gen_text(x, label = NULL, bullet = FALSE) +gen_text(x, label = NULL, bullet = FALSE, bullet_char = consort_opt("bullet")) } \arguments{ \item{x}{A list or a vector to be used. \code{x} can be atomic vector, a @@ -15,8 +15,13 @@ The nested reasons only support two columns and the \code{bullet} will be ignore \item{label}{A character string as a label at the beginning of the text label. The count for each categories will be returned if no label is provided.} -\item{bullet}{If shows bullet points. If the value is `TRUE`, the bullet points -will be tabulated, default is `FALSE`.} +\item{bullet}{If shows bullet points. If the value is \code{TRUE}, the bullet points +will be tabulated, default is \code{FALSE}.} + +\item{bullet_char}{A single character used as the bullet symbol. Defaults to +\code{consort_opt("bullet")}. Can be any Unicode +character such as \code{"\u2013"} (en-dash), \code{"\u25CB"} (circle), +\code{"\u25A0"} (square), \code{"-"}, etc.} } \value{ A character string of vector. @@ -37,4 +42,11 @@ gen_text(split(val$car, val$am), label = "Cars in the data") gen_text(split(val$car, val$am), label = "Cars in the data", bullet = FALSE) gen_text(split(val[,c("vs", "car")], val$am), label = "Cars in the data", bullet = FALSE) gen_text(val[,c("vs", "car")], label = "Cars in the data", bullet = FALSE) + +# Use a custom bullet character +gen_text(val$car, label = "Cars in the data", bullet = TRUE, bullet_char = "-") + +# Or set globally via set_consort_defaults +set_consort_defaults(bullet = "\u25CB") +gen_text(val$car, label = "Cars in the data", bullet = TRUE) } diff --git a/man/set_consort_defaults.Rd b/man/set_consort_defaults.Rd new file mode 100644 index 0000000..25aada6 --- /dev/null +++ b/man/set_consort_defaults.Rd @@ -0,0 +1,76 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/defaults.R +\name{set_consort_defaults} +\alias{set_consort_defaults} +\alias{get_consort_defaults} +\alias{init_consort_defaults} +\alias{print.consort_defaults} +\title{Set consort diagram default options} +\usage{ +set_consort_defaults( + arrow_gp = NULL, + txt_gp = NULL, + box_gp = NULL, + label_txt_gp = NULL, + label_box_gp = NULL, + arrow_length = NULL, + arrow_type = NULL, + pad_u = NULL, + bullet = NULL +) + +get_consort_defaults() + +init_consort_defaults() + +\method{print}{consort_defaults}(x, ...) +} +\arguments{ +\item{arrow_gp}{A \code{\link[grid]{gpar}} object for the arrow line.} + +\item{txt_gp}{A \code{\link[grid]{gpar}} object for the text inside boxes.} + +\item{box_gp}{A \code{\link[grid]{gpar}} object for the box border and fill.} + +\item{label_txt_gp}{A \code{\link[grid]{gpar}} object for the label text.} + +\item{label_box_gp}{A \code{\link[grid]{gpar}} object for the label box.} + +\item{arrow_length}{Numeric, length of the arrowhead in inches.} + +\item{arrow_type}{Character, arrow type: \code{"closed"} or \code{"open"}.} + +\item{pad_u}{Numeric, padding between nodes.} + +\item{bullet}{Character, bullet character for side box items.} + +\item{x}{A \code{consort_defaults} object.} + +\item{...}{Not used.} +} +\value{ +Invisibly returns the previous defaults (a \code{consort_defaults} object). + +A \code{consort_defaults} object containing all current default settings. +} +\description{ +Modify the default graphical parameters and other settings for consort diagrams. +Any parameter set to \code{NULL} (the default) will remain unchanged. +} +\examples{ +# Change text color and box fill +old <- set_consort_defaults( + txt_gp = grid::gpar(col = "navy", cex = 0.9), + box_gp = grid::gpar(fill = "#F0F0F0") +) + +# View current defaults +get_consort_defaults() + +# Restore previous defaults +set_consort_defaults( + txt_gp = old$txt_gp, + box_gp = old$box_gp +) +get_consort_defaults() +} diff --git a/man/textbox.Rd b/man/textbox.Rd index 8c192dc..ef0665d 100644 --- a/man/textbox.Rd +++ b/man/textbox.Rd @@ -10,9 +10,10 @@ textbox( x = unit(0.5, "npc"), y = unit(0.5, "npc"), just = c("center", "left", "right"), - txt_gp = getOption("txt_gp", default = gpar(color = "black", cex = 1)), + txt_gp = consort_opt("txt_gp"), + leftrotate = FALSE, box_fn = roundrectGrob, - box_gp = getOption("box_gp", default = gpar(fill = "white")), + box_gp = consort_opt("box_gp"), name = "textbox" ) @@ -29,13 +30,16 @@ grid.textbox(...) See \link[grid]{textGrob} for more details.} \item{txt_gp}{An object of class \link[grid]{gpar} style to be applied to the -text. This will also be read from global options of \code{"txt_gp"}. For -example, if one wants to set a font size for all the text inside box, -\code{options(txt_gp = gpar(cex = 0.8))} will do the trick.} +text. Defaults are read from \code{\link{set_consort_defaults}}. For +example, to set a font size for all text inside boxes, use +\code{set_consort_defaults(txt_gp = gpar(cex = 0.8))}.} + +\item{leftrotate}{If the text box will be rotated 90 degrees counter-clockwise. +Default is \code{FALSE}.} \item{box_fn}{Function to create box for the text. Parameters of `x=0.5`, `y=0.5` and `box_gp` will be passed to this function and return a \code{grob} -object. This will also be read from global options of \code{"box_gp"}.} +object.} \item{box_gp}{An object of class \link[grid]{gpar} style to be applied to the box.} diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf index d06ad05..14861db 100644 Binary files a/tests/testthat/Rplots.pdf and b/tests/testthat/Rplots.pdf differ diff --git a/tests/testthat/_snaps/auto/auto-grviz.gv b/tests/testthat/_snaps/auto/auto-grviz.gv index cf4cfca..652ed8a 100644 --- a/tests/testthat/_snaps/auto/auto-grviz.gv +++ b/tests/testthat/_snaps/auto/auto-grviz.gv @@ -1,32 +1,31 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] -node [shape = rectangle, style = "rounded,filled", fillcolor = "#A9C7FD" color = "#4F81BD"] -label14 [label = "Screening"] -label15 [label = "Randomization"] -label16 [label = "Final"] +node [shape = rectangle] +label14 [label = "Screening" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] +label15 [label = "Randomization" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] +label16 [label = "Final" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] edge[style=invis]; label14 -> label15 -> label16; # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] -node1 [label = "Population (n=300)" group=A1] -node2 [label = "Excluded (n=15)\l MRI not collected (n=3)\l Other (n=4)\l Sample not collected (n=8)\l"] -node3 [label = "Allocated (n=285)" group=A1] -node4 [label = "Conc (n=158)" group=B1] -node5 [label = "Seq (n=127)" group=B2] -node6 [label = "Lost of Follow-up (n=33)\l Death (n=12)\l Discontinued (n=4)\l Other (n=11)\l Withdraw (n=6)\l"] -node7 [label = "Lost of Follow-up (n=26)\l Death (n=5)\l Discontinued (n=6)\l Other (n=11)\l Withdraw (n=4)\l"] -node8 [label = "Finished Followup (n=125)" group=B1] -node9 [label = "Finished Followup (n=101)" group=B2] -node10 [label = "Not evaluable for the final analysis (n=6)\l Outcome missing (n=4)\l Protocol deviation (n=2)\l"] -node11 [label = "Not evaluable for the final analysis (n=5)\l Outcome missing (n=4)\l Protocol deviation (n=1)\l"] -node12 [label = "Final Analysis (n=119)" group=B1] -node13 [label = "Final Analysis (n=96)" group=B2] +node1 [label = "Population (n=300)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node2 [label = "Excluded (n=15)\l MRI not collected (n=3)\l Other (n=4)\l Sample not collected (n=8)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node3 [label = "Allocated (n=285)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node4 [label = "Conc (n=158)" group=B1 fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node5 [label = "Seq (n=127)" group=B2 fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node6 [label = "Lost of Follow-up (n=33)\l Death (n=12)\l Discontinued (n=4)\l Other (n=11)\l Withdraw (n=6)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node7 [label = "Lost of Follow-up (n=26)\l Death (n=5)\l Discontinued (n=6)\l Other (n=11)\l Withdraw (n=4)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node8 [label = "Finished Followup (n=125)" group=B1 fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node9 [label = "Finished Followup (n=101)" group=B2 fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node10 [label = "Not evaluable for the final analysis (n=6)\l Outcome missing (n=4)\l Protocol deviation (n=2)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node11 [label = "Not evaluable for the final analysis (n=5)\l Outcome missing (n=4)\l Protocol deviation (n=1)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node12 [label = "Final Analysis (n=119)" group=B1 fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] +node13 [label = "Final Analysis (n=96)" group=B2 fillcolor="white" style="filled" fontcolor="black" fontsize=12.6] ## Invisible point node for joints @@ -82,7 +81,7 @@ subgraph { rank = same; rankdir = LR; label15; node3; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; node1 -> P1 [arrowhead = none]; P1 -> node2; diff --git a/tests/testthat/_snaps/auto/auto-last-grviz.gv b/tests/testthat/_snaps/auto/auto-last-grviz.gv index ca4798a..e59a022 100644 --- a/tests/testthat/_snaps/auto/auto-last-grviz.gv +++ b/tests/testthat/_snaps/auto/auto-last-grviz.gv @@ -1,25 +1,24 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] -node [shape = rectangle, style = "rounded,filled", fillcolor = "#A9C7FD" color = "#4F81BD"] -label8 [label = "Screening"] -label9 [label = "Consent"] +node [shape = rectangle] +label8 [label = "Screening" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] +label9 [label = "Consent" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] edge[style=invis]; label8 -> label9; # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] -node1 [label = "Screened (n=1,000)" group=A1] -node2 [label = "Excluded (n=286)\l Hx medication (n=48)\l age < 40 (n=82)\l pain-free (n=156)\l"] -node3 [label = "Qualified for Randomization (n=714)" group=A1] -node4 [label = "Consented (n=634)" group=A1] -node5 [label = "Randomized (n=534)" group=A1] -node6 [label = "Treatment A (n=285)" group=B1] -node7 [label = "Treatment B (n=249)" group=B2] +node1 [label = "Screened (n=1,000)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node2 [label = "Excluded (n=286)\l Hx medication (n=48)\l age < 40 (n=82)\l pain-free (n=156)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node3 [label = "Qualified for Randomization (n=714)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node4 [label = "Consented (n=634)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node5 [label = "Randomized (n=534)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node6 [label = "Treatment A (n=285)" group=B1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node7 [label = "Treatment B (n=249)" group=B2 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] ## Invisible point node for joints @@ -53,7 +52,7 @@ subgraph { rank = same; rankdir = LR; node6; node7; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; node1 -> P1 [arrowhead = none]; P1 -> node2; diff --git a/tests/testthat/_snaps/auto/auto-nolab-grviz.gv b/tests/testthat/_snaps/auto/auto-nolab-grviz.gv index 2671f73..e3120be 100644 --- a/tests/testthat/_snaps/auto/auto-nolab-grviz.gv +++ b/tests/testthat/_snaps/auto/auto-nolab-grviz.gv @@ -1,20 +1,21 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] +node [shape = rectangle] + # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] -node1 [label = "Screened (n=1,000)" group=A1] -node2 [label = "Excluded (n=286)\l Hx medication (n=48)\l age < 40 (n=82)\l pain-free (n=156)\l"] -node3 [label = "Qualified for Randomization (n=714)" group=A1] -node4 [label = "Consented (n=634)" group=A1] -node5 [label = "Randomized (n=534)" group=A1] -node6 [label = "Treatment A (n=285)" group=B1] -node7 [label = "Treatment B (n=249)" group=B2] +node1 [label = "Screened (n=1,000)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node2 [label = "Excluded (n=286)\l Hx medication (n=48)\l age < 40 (n=82)\l pain-free (n=156)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node3 [label = "Qualified for Randomization (n=714)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node4 [label = "Consented (n=634)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node5 [label = "Randomized (n=534)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node6 [label = "Treatment A (n=285)" group=B1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node7 [label = "Treatment B (n=249)" group=B2 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] ## Invisible point node for joints @@ -48,7 +49,7 @@ subgraph { rank = same; rankdir = LR; node6; node7; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; node1 -> P1 [arrowhead = none]; P1 -> node2; diff --git a/tests/testthat/_snaps/auto/autogen-last.png b/tests/testthat/_snaps/auto/autogen-last.png index 0de94b7..4725fdc 100644 Binary files a/tests/testthat/_snaps/auto/autogen-last.png and b/tests/testthat/_snaps/auto/autogen-last.png differ diff --git a/tests/testthat/_snaps/auto/autogen-nolab.png b/tests/testthat/_snaps/auto/autogen-nolab.png index 470d94b..db7778e 100644 Binary files a/tests/testthat/_snaps/auto/autogen-nolab.png and b/tests/testthat/_snaps/auto/autogen-nolab.png differ diff --git a/tests/testthat/_snaps/auto/autogen.png b/tests/testthat/_snaps/auto/autogen.png index df72c93..757a062 100644 Binary files a/tests/testthat/_snaps/auto/autogen.png and b/tests/testthat/_snaps/auto/autogen.png differ diff --git a/tests/testthat/_snaps/build_grviz/build-grviz-withopts.png b/tests/testthat/_snaps/build_grviz/build-grviz-withopts.png new file mode 100644 index 0000000..77e7a05 Binary files /dev/null and b/tests/testthat/_snaps/build_grviz/build-grviz-withopts.png differ diff --git a/tests/testthat/_snaps/build_grviz/build-grviz.png b/tests/testthat/_snaps/build_grviz/build-grviz.png index fcfc60d..586aad7 100644 Binary files a/tests/testthat/_snaps/build_grviz/build-grviz.png and b/tests/testthat/_snaps/build_grviz/build-grviz.png differ diff --git a/tests/testthat/_snaps/build_grviz/empty-middle-grviz.gv b/tests/testthat/_snaps/build_grviz/empty-middle-grviz.gv index 0ee43c2..01ac80a 100644 --- a/tests/testthat/_snaps/build_grviz/empty-middle-grviz.gv +++ b/tests/testthat/_snaps/build_grviz/empty-middle-grviz.gv @@ -1,22 +1,23 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] +node [shape = rectangle] + # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] -node1 [label = "Cohort 1 (n=6)" group=A1] -node2 [label = "Cohort 2 (n=6)" group=A2] -node3 [label = "Cohort 3 (n=6)" group=A3] -node4 [label = "Excluded (n=1)\l"] -node5 [label = "Excluded (n=3)\l"] -node7 [label = "Cohort 1 (n=5)" group=A1] -node8 [label = "Cohort 2 (n=3)" group=A2] -node9 [label = "" group=A3 shape=none height=0 width=0] -node10 [label = "Total (n=14)" group=B1] +node1 [label = "Cohort 1 (n=6)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node2 [label = "Cohort 2 (n=6)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node3 [label = "Cohort 3 (n=6)" group=A3 fillcolor="white" style="filled" fontcolor="black"] +node4 [label = "Excluded (n=1)\l" fillcolor="white" style="filled" fontcolor="black"] +node5 [label = "Excluded (n=3)\l" fillcolor="white" style="filled" fontcolor="black"] +node7 [label = "Cohort 1 (n=5)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node8 [label = "Cohort 2 (n=3)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node9 [label = "" group=A3 fillcolor="white" style="filled" fontcolor="black" shape=none height=0 width=0] +node10 [label = "Total (n=14)" group=B1 fillcolor="white" style="filled" fontcolor="black"] ## Invisible point node for joints @@ -51,7 +52,7 @@ subgraph { rank = same; rankdir = LR; P3; P4; P5; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; node1 -> P1 [arrowhead = none]; P1 -> node4; diff --git a/tests/testthat/_snaps/build_grviz/end-miss-grviz.gv b/tests/testthat/_snaps/build_grviz/end-miss-grviz.gv index 7ca0530..d918d68 100644 --- a/tests/testthat/_snaps/build_grviz/end-miss-grviz.gv +++ b/tests/testthat/_snaps/build_grviz/end-miss-grviz.gv @@ -1,23 +1,24 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] +node [shape = rectangle] + # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] -node1 [label = "Study 1 (n=8)" group=A1] -node2 [label = "Study 2 (n=12)" group=A2] -node3 [label = "Included All (n=20)" group=B1] -node4 [label = "Excluded (n=7):\l MRI not collected (n=3)\l"] -node5 [label = "Randomised" group=B1] -node6 [label = "Arm A (n=143)" group=A1] -node7 [label = "Arm B (n=142)" group=A2] -node9 [label = "Exclude (n=3\l"] -node10 [label = "" group=A1 shape=none height=0 width=0] -node11 [label = "From Arm B" group=A2] +node1 [label = "Study 1 (n=8)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node2 [label = "Study 2 (n=12)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node3 [label = "Included All (n=20)" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node4 [label = "Excluded (n=7):\l MRI not collected (n=3)\l" fillcolor="white" style="filled" fontcolor="black"] +node5 [label = "Randomised" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node6 [label = "Arm A (n=143)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node7 [label = "Arm B (n=142)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node9 [label = "Exclude (n=3\l" fillcolor="white" style="filled" fontcolor="black"] +node10 [label = "" group=A1 fillcolor="white" style="filled" fontcolor="black" shape=none height=0 width=0] +node11 [label = "From Arm B" group=A2 fillcolor="white" style="filled" fontcolor="black"] ## Invisible point node for joints @@ -58,7 +59,7 @@ subgraph { rank = same; rankdir = LR; P8; node9; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; P2 -> node3; node1 -> P1 [arrowhead = none]; diff --git a/tests/testthat/_snaps/build_grviz/end-miss-grviz.png b/tests/testthat/_snaps/build_grviz/end-miss-grviz.png index f227f5d..09d2ec2 100644 Binary files a/tests/testthat/_snaps/build_grviz/end-miss-grviz.png and b/tests/testthat/_snaps/build_grviz/end-miss-grviz.png differ diff --git a/tests/testthat/_snaps/build_grviz/grviz-withopts.gv b/tests/testthat/_snaps/build_grviz/grviz-withopts.gv new file mode 100644 index 0000000..1210234 --- /dev/null +++ b/tests/testthat/_snaps/build_grviz/grviz-withopts.gv @@ -0,0 +1,105 @@ +digraph consort_diagram { + graph [layout = dot, splines=ortho] + +node [shape = rectangle] + +label14 [label = "Screening" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="red"] +label15 [label = "Randomized" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="red"] +label16 [label = "Final analysis" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="red"] + +edge[style=invis]; +label14 -> label15 -> label16; + + +# node definitions with substituted label text + +node1 [label = "Study 1 (n=8)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node2 [label = "Study 2 (n=12)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node3 [label = "Included All (n=20)" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node4 [label = "Excluded (n=7):\l MRI not collected (n=3)\l" fillcolor="white" style="filled" fontcolor="black"] +node5 [label = "Randomised" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node6 [label = "Arm A (n=143)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node7 [label = "Arm B (n=142)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node8 [label = "Follow-up (n=20)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node9 [label = "Follow-up (n=7)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node10 [label = "Excluded (n=15):\l MRI not collected (n=3)\l Tissues not collected (n=4)\l Other (n=8)\l" fillcolor="white" style="filled" fontcolor="black"] +node11 [label = "Excluded (n=7):\l MRI not collected (n=3)\l Tissues not collected (n=4)\l" fillcolor="white" style="filled" fontcolor="black"] +node12 [label = "Final analysis (n=128)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node13 [label = "Final analysis (n=135)" group=A2 fillcolor="white" style="filled" fontcolor="black"] + + +## Invisible point node for joints + +node [shape = point, width = 0, style=invis] + +P1 [group=A1] +P2 [group=B1] +P3 [group=A2] +P4 [group=B1] +P5 [group=A1] +P6 [group=B1] +P7 [group=A2] +P8 [group=A1] +P9 [group=A2] + +subgraph { + rank = same; rankdir = LR; P1; P2; P3; + } +subgraph { + rank = same; rankdir = LR; label14; node1; node2; + } +subgraph { + rank = same; rankdir = LR; node3; + } +subgraph { + rank = same; rankdir = LR; P4; node4; + } +subgraph { + rank = same; rankdir = LR; P5; P6; P7; + } +subgraph { + rank = same; rankdir = LR; node6; node7; + } +subgraph { + rank = same; rankdir = LR; node8; node9; + } +subgraph { + rank = same; rankdir = LR; P8; node10; + } +subgraph { + rank = same; rankdir = LR; label16; node12; node13; + } +subgraph { + rank = same; rankdir = LR; node10; node11; + } +subgraph { + rank = same; rankdir = LR; P9; node11; + } +subgraph { + rank = same; rankdir = LR; label15; node5; + } + +edge[color="green" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; + +P2 -> node3; +node1 -> P1 [arrowhead = none]; +node2 -> P3 [arrowhead = none]; +P1 -> P2 -> P3 [arrowhead = none]; +node3 -> P4 [arrowhead = none]; +P4 -> node4; +P4 -> node5; +node5 -> P6 [arrowhead = none]; +P5 -> node6; +P7 -> node7; +P5 -> P6 -> P7 [arrowhead = none]; +node6 -> node8; +node7 -> node9; +node8 -> P8 [arrowhead = none]; +P8 -> node10; +P8 -> node12; +node9 -> P9 [arrowhead = none]; +P9 -> node11; +P9 -> node13; + + +} diff --git a/tests/testthat/_snaps/build_grviz/grviz.gv b/tests/testthat/_snaps/build_grviz/grviz.gv index bc9d623..c7d17ef 100644 --- a/tests/testthat/_snaps/build_grviz/grviz.gv +++ b/tests/testthat/_snaps/build_grviz/grviz.gv @@ -1,32 +1,31 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] -node [shape = rectangle, style = "rounded,filled", fillcolor = "#A9C7FD" color = "#4F81BD"] -label14 [label = "Screening"] -label15 [label = "Randomized"] -label16 [label = "Final analysis"] +node [shape = rectangle] +label14 [label = "Screening" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] +label15 [label = "Randomized" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] +label16 [label = "Final analysis" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] edge[style=invis]; label14 -> label15 -> label16; # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] -node1 [label = "Study 1 (n=8)" group=A1] -node2 [label = "Study 2 (n=12)" group=A2] -node3 [label = "Included All (n=20)" group=B1] -node4 [label = "Excluded (n=7):\l MRI not collected (n=3)\l"] -node5 [label = "Randomised" group=B1] -node6 [label = "Arm A (n=143)" group=A1] -node7 [label = "Arm B (n=142)" group=A2] -node8 [label = "Follow-up (n=20)" group=A1] -node9 [label = "Follow-up (n=7)" group=A2] -node10 [label = "Excluded (n=15):\l MRI not collected (n=3)\l Tissues not collected (n=4)\l Other (n=8)\l"] -node11 [label = "Excluded (n=7):\l MRI not collected (n=3)\l Tissues not collected (n=4)\l"] -node12 [label = "Final analysis (n=128)" group=A1] -node13 [label = "Final analysis (n=135)" group=A2] +node1 [label = "Study 1 (n=8)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node2 [label = "Study 2 (n=12)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node3 [label = "Included All (n=20)" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node4 [label = "Excluded (n=7):\l MRI not collected (n=3)\l" fillcolor="white" style="filled" fontcolor="black"] +node5 [label = "Randomised" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node6 [label = "Arm A (n=143)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node7 [label = "Arm B (n=142)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node8 [label = "Follow-up (n=20)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node9 [label = "Follow-up (n=7)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node10 [label = "Excluded (n=15):\l MRI not collected (n=3)\l Tissues not collected (n=4)\l Other (n=8)\l" fillcolor="white" style="filled" fontcolor="black"] +node11 [label = "Excluded (n=7):\l MRI not collected (n=3)\l Tissues not collected (n=4)\l" fillcolor="white" style="filled" fontcolor="black"] +node12 [label = "Final analysis (n=128)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node13 [label = "Final analysis (n=135)" group=A2 fillcolor="white" style="filled" fontcolor="black"] ## Invisible point node for joints @@ -80,7 +79,7 @@ subgraph { rank = same; rankdir = LR; label15; node5; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; P2 -> node3; node1 -> P1 [arrowhead = none]; diff --git a/tests/testthat/_snaps/build_grviz/multi-miss-grviz.gv b/tests/testthat/_snaps/build_grviz/multi-miss-grviz.gv index 47c3660..0ac5439 100644 --- a/tests/testthat/_snaps/build_grviz/multi-miss-grviz.gv +++ b/tests/testthat/_snaps/build_grviz/multi-miss-grviz.gv @@ -1,25 +1,26 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] +node [shape = rectangle] + # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] - -node1 [label = "Study 1 (n=8)" group=A1] -node2 [label = "Study 2 (n=12)" group=A2] -node3 [label = "Included All (n=20)" group=B1] -node4 [label = "Excluded (n=7):\l MRI not collected (n=3)\l"] -node5 [label = "Randomised" group=B1] -node6 [label = "Arm A (n=143)" group=A1] -node7 [label = "Arm B (n=142)" group=A2] -node9 [label = "Exclude (n=3\l"] -node10 [label = "" group=A1 shape=none height=0 width=0] -node11 [label = "From Arm B" group=A2] -node12 [label = "This is it" group=A1] -node13 [label = "From Arm B" group=A2] + +node1 [label = "Study 1 (n=8)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node2 [label = "Study 2 (n=12)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node3 [label = "Included All (n=20)" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node4 [label = "Excluded (n=7):\l MRI not collected (n=3)\l" fillcolor="white" style="filled" fontcolor="black"] +node5 [label = "Randomised" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node6 [label = "Arm A (n=143)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node7 [label = "Arm B (n=142)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node9 [label = "Exclude (n=3\l" fillcolor="white" style="filled" fontcolor="black"] +node10 [label = "" group=A1 fillcolor="white" style="filled" fontcolor="black" shape=none height=0 width=0] +node11 [label = "From Arm B" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node12 [label = "This is it" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node13 [label = "From Arm B" group=A2 fillcolor="white" style="filled" fontcolor="black"] ## Invisible point node for joints @@ -63,7 +64,7 @@ subgraph { rank = same; rankdir = LR; node12; node13; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; P2 -> node3; node1 -> P1 [arrowhead = none]; diff --git a/tests/testthat/_snaps/build_grviz/multi-miss-grviz.png b/tests/testthat/_snaps/build_grviz/multi-miss-grviz.png index 3ea4e2d..6367d19 100644 Binary files a/tests/testthat/_snaps/build_grviz/multi-miss-grviz.png and b/tests/testthat/_snaps/build_grviz/multi-miss-grviz.png differ diff --git a/tests/testthat/_snaps/build_grviz/split-comb-grviz.gv b/tests/testthat/_snaps/build_grviz/split-comb-grviz.gv index fc7a060..44a6233 100644 --- a/tests/testthat/_snaps/build_grviz/split-comb-grviz.gv +++ b/tests/testthat/_snaps/build_grviz/split-comb-grviz.gv @@ -1,29 +1,30 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] +node [shape = rectangle] + # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] - -node1 [label = "Study 1 (n=8)" group=A1] -node2 [label = "Study 2 And this is long (n=12)" group=A2] -node3 [label = "Study 3 (n=12)" group=A3] -node4 [label = "Study 3 (n=12)" group=A4] -node5 [label = "Study 3 (n=12)" group=A5] -node6 [label = "Included All (n=20)" group=B1] -node7 [label = "Excluded (n=7):\l MRI not collected (n=3)\l"] -node8 [label = "Randomised" group=B1] -node9 [label = "Arm A (n=143)" group=C1] -node10 [label = "Arm B (n=142)" group=C2] -node11 [label = "" group=C1 shape=none height=0 width=0] -node12 [label = "From Arm B" group=C2] -node13 [label = "Combine all" group=B1] -node14 [label = "Process 1 (n=140)" group=D1] -node15 [label = "Process 2 (n=140)" group=D2] -node16 [label = "Process 3 (n=142)" group=D3] + +node1 [label = "Study 1 (n=8)" group=A1 fillcolor="white" style="filled" fontcolor="black"] +node2 [label = "Study 2 And this is long (n=12)" group=A2 fillcolor="white" style="filled" fontcolor="black"] +node3 [label = "Study 3 (n=12)" group=A3 fillcolor="white" style="filled" fontcolor="black"] +node4 [label = "Study 3 (n=12)" group=A4 fillcolor="white" style="filled" fontcolor="black"] +node5 [label = "Study 3 (n=12)" group=A5 fillcolor="white" style="filled" fontcolor="black"] +node6 [label = "Included All (n=20)" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node7 [label = "Excluded (n=7):\l MRI not collected (n=3)\l" fillcolor="white" style="filled" fontcolor="black"] +node8 [label = "Randomised" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node9 [label = "Arm A (n=143)" group=C1 fillcolor="white" style="filled" fontcolor="black"] +node10 [label = "Arm B (n=142)" group=C2 fillcolor="white" style="filled" fontcolor="black"] +node11 [label = "" group=C1 fillcolor="white" style="filled" fontcolor="black" shape=none height=0 width=0] +node12 [label = "From Arm B" group=C2 fillcolor="white" style="filled" fontcolor="black"] +node13 [label = "Combine all" group=B1 fillcolor="white" style="filled" fontcolor="black"] +node14 [label = "Process 1 (n=140)" group=D1 fillcolor="white" style="filled" fontcolor="black"] +node15 [label = "Process 2 (n=140)" group=D2 fillcolor="white" style="filled" fontcolor="black"] +node16 [label = "Process 3 (n=142)" group=D3 fillcolor="white" style="filled" fontcolor="black"] ## Invisible point node for joints @@ -77,7 +78,7 @@ subgraph { rank = same; rankdir = LR; node14; node15; node16; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; P3 -> node6; node1 -> P1 [arrowhead = none]; diff --git a/tests/testthat/_snaps/manually/manually-gen.png b/tests/testthat/_snaps/manually/manually-gen.png index 1eea236..4937434 100644 Binary files a/tests/testthat/_snaps/manually/manually-gen.png and b/tests/testthat/_snaps/manually/manually-gen.png differ diff --git a/tests/testthat/_snaps/multiple-split/multiple-split-nokick.gv b/tests/testthat/_snaps/multiple-split/multiple-split-nokick.gv index ee1df7d..62539ca 100644 --- a/tests/testthat/_snaps/multiple-split/multiple-split-nokick.gv +++ b/tests/testthat/_snaps/multiple-split/multiple-split-nokick.gv @@ -1,45 +1,44 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] -node [shape = rectangle, style = "rounded,filled", fillcolor = "#A9C7FD" color = "#4F81BD"] -label26 [label = "Screening"] -label27 [label = "Randomization"] -label28 [label = "Follow-up"] -label29 [label = "Final analysis"] +node [shape = rectangle] +label26 [label = "Screening" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] +label27 [label = "Randomization" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] +label28 [label = "Follow-up" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] +label29 [label = "Final analysis" fillcolor="#A9C7FD" style="rounded,filled" fontcolor="#4F81BD"] edge[style=invis]; label26 -> label27 -> label28 -> label29; # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] -node1 [label = "Population (n=202)" group=A1] -node2 [label = "Excluded (n=33)\l Dead (n=4)\l MRI not collected (n=3)\l Other (n=8)\l Sample not collected (n=18)\l"] -node3 [label = "Randomized patient (n=169)" group=A1] -node4 [label = "Conc (n=86)" group=B1] -node5 [label = "Seq (n=83)" group=B2] -node6 [label = "Trt A (n=42)\lParticipants not treated (n=2)\l Never dosed (n=2)\l" group=C1] -node7 [label = "Trt B (n=44)\lParticipants not treated (n=1)\l Never dosed (n=1)\l" group=C2] -node8 [label = "Trt A (n=44)\lParticipants not treated (n=7)\l Declined (n=2)\l Never dosed (n=3)\l Ranomised by error (n=2)\l" group=C3] -node9 [label = "Trt B (n=39)\lParticipants not treated (n=2)\l Never dosed (n=1)\l Ranomised by error (n=1)\l" group=C4] -node10 [label = "Pariticpants planned for follow-up (n=40)\lReason for tot followed (n=12)\l Death (n=2)\l Discontinued (n=4)\l Other (n=3)\l Withdraw (n=3)\l" group=C1] -node11 [label = "Pariticpants planned for follow-up (n=43)\lReason for tot followed (n=5)\l Discontinued (n=2)\l Other (n=2)\l Withdraw (n=1)\l" group=C2] -node12 [label = "Pariticpants planned for follow-up (n=37)\lReason for tot followed (n=13)\l Death (n=2)\l Discontinued (n=1)\l Other (n=4)\l Withdraw (n=6)\l" group=C3] -node13 [label = "Pariticpants planned for follow-up (n=37)\lReason for tot followed (n=9)\l Death (n=2)\l Discontinued (n=4)\l Withdraw (n=3)\l" group=C4] -node14 [label = "Assessed for final outcome (n=28)" group=C1] -node15 [label = "Assessed for final outcome (n=38)" group=C2] -node16 [label = "Assessed for final outcome (n=25)" group=C3] -node17 [label = "Assessed for final outcome (n=30)" group=C4] -node18 [label = "Reason for not assessed (n=3)\l Outcome missing (n=2)\l Protocol deviation (n=1)\l"] -node19 [label = "Reason for not assessed (n=4)\l Outcome missing (n=1)\l Protocol deviation (n=3)\l"] -node20 [label = "Reason for not assessed (n=2)\l Outcome missing (n=2)\l"] -node21 [label = "Reason for not assessed (n=3)\l Outcome missing (n=2)\l Protocol deviation (n=1)\l"] -node22 [label = "Included in the mITT analysis (n=40)" group=C1] -node23 [label = "Included in the mITT analysis (n=43)" group=C2] -node24 [label = "Included in the mITT analysis (n=37)" group=C3] -node25 [label = "Included in the mITT analysis (n=37)" group=C4] +node1 [label = "Population (n=202)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node2 [label = "Excluded (n=33)\l Dead (n=4)\l MRI not collected (n=3)\l Other (n=8)\l Sample not collected (n=18)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node3 [label = "Randomized patient (n=169)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node4 [label = "Conc (n=86)" group=B1 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node5 [label = "Seq (n=83)" group=B2 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node6 [label = "Trt A (n=42)\lParticipants not treated (n=2)\l Never dosed (n=2)\l" group=C1 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node7 [label = "Trt B (n=44)\lParticipants not treated (n=1)\l Never dosed (n=1)\l" group=C2 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node8 [label = "Trt A (n=44)\lParticipants not treated (n=7)\l Declined (n=2)\l Never dosed (n=3)\l Ranomised by error (n=2)\l" group=C3 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node9 [label = "Trt B (n=39)\lParticipants not treated (n=2)\l Never dosed (n=1)\l Ranomised by error (n=1)\l" group=C4 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node10 [label = "Pariticpants planned for follow-up (n=40)\lReason for tot followed (n=12)\l Death (n=2)\l Discontinued (n=4)\l Other (n=3)\l Withdraw (n=3)\l" group=C1 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node11 [label = "Pariticpants planned for follow-up (n=43)\lReason for tot followed (n=5)\l Discontinued (n=2)\l Other (n=2)\l Withdraw (n=1)\l" group=C2 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node12 [label = "Pariticpants planned for follow-up (n=37)\lReason for tot followed (n=13)\l Death (n=2)\l Discontinued (n=1)\l Other (n=4)\l Withdraw (n=6)\l" group=C3 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node13 [label = "Pariticpants planned for follow-up (n=37)\lReason for tot followed (n=9)\l Death (n=2)\l Discontinued (n=4)\l Withdraw (n=3)\l" group=C4 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node14 [label = "Assessed for final outcome (n=28)" group=C1 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node15 [label = "Assessed for final outcome (n=38)" group=C2 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node16 [label = "Assessed for final outcome (n=25)" group=C3 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node17 [label = "Assessed for final outcome (n=30)" group=C4 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node18 [label = "Reason for not assessed (n=3)\l Outcome missing (n=2)\l Protocol deviation (n=1)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node19 [label = "Reason for not assessed (n=4)\l Outcome missing (n=1)\l Protocol deviation (n=3)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node20 [label = "Reason for not assessed (n=2)\l Outcome missing (n=2)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node21 [label = "Reason for not assessed (n=3)\l Outcome missing (n=2)\l Protocol deviation (n=1)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node22 [label = "Included in the mITT analysis (n=40)" group=C1 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node23 [label = "Included in the mITT analysis (n=43)" group=C2 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node24 [label = "Included in the mITT analysis (n=37)" group=C3 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] +node25 [label = "Included in the mITT analysis (n=37)" group=C4 fillcolor="white" style="filled" fontcolor="black" fontsize=9.8] ## Invisible point node for joints @@ -107,7 +106,7 @@ subgraph { rank = same; rankdir = LR; label27; node3; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; node1 -> P1 [arrowhead = none]; P1 -> node2; diff --git a/tests/testthat/_snaps/multiple-split/multiple-split-nokick.png b/tests/testthat/_snaps/multiple-split/multiple-split-nokick.png index 062955a..c24e4f7 100644 Binary files a/tests/testthat/_snaps/multiple-split/multiple-split-nokick.png and b/tests/testthat/_snaps/multiple-split/multiple-split-nokick.png differ diff --git a/tests/testthat/_snaps/multiple-split/multiple-split.gv b/tests/testthat/_snaps/multiple-split/multiple-split.gv index 86ef0a7..944deeb 100644 --- a/tests/testthat/_snaps/multiple-split/multiple-split.gv +++ b/tests/testthat/_snaps/multiple-split/multiple-split.gv @@ -1,48 +1,49 @@ digraph consort_diagram { graph [layout = dot, splines=ortho] +node [shape = rectangle] + # node definitions with substituted label text - node [shape = rectangle, fillcolor = Biege, style="", fillcolor = "", color = ""] - -node1 [label = "Population (n=300)" group=A1] -node2 [label = "Excluded (n=33)\l Dead (n=4)\l MRI not collected (n=3)\l Other (n=8)\l Sample not collected (n=18)\l"] -node3 [label = "Randomized patient (n=267)" group=A1] -node4 [label = "Conc (n=142)" group=B1] -node5 [label = "Seq (n=125)" group=B2] -node6 [label = "Trt A (n=42)" group=C1] -node7 [label = "Trt B (n=44)" group=C2] -node8 [label = "Trt C (n=56)" group=C3] -node9 [label = "Trt A (n=44)" group=C4] -node10 [label = "Trt B (n=39)" group=C5] -node11 [label = "Trt C (n=42)" group=C6] -node12 [label = "Lost of Follow-up (n=2)\l Never dosed (n=2)\l"] -node13 [label = "Lost of Follow-up (n=1)\l Never dosed (n=1)\l"] -node14 [label = "Lost of Follow-up (n=2)\l Never dosed (n=2)\l"] -node15 [label = "Lost of Follow-up (n=7)\l Declined (n=2)\l Never dosed (n=3)\l Ranomised by error (n=2)\l"] -node16 [label = "Lost of Follow-up (n=2)\l Never dosed (n=1)\l Ranomised by error (n=1)\l"] -node17 [label = "Lost of Follow-up (n=4)\l Never dosed (n=3)\l Ranomised by error (n=1)\l"] -node18 [label = "Followup-up (n=40)" group=C1] -node19 [label = "Followup-up (n=43)" group=C2] -node20 [label = "Followup-up (n=54)" group=C3] -node21 [label = "Followup-up (n=37)" group=C4] -node22 [label = "Followup-up (n=37)" group=C5] -node23 [label = "Followup-up (n=38)" group=C6] -node24 [label = "Lost to follow-up (n=12)\l Death (n=2)\l Discontinued (n=4)\l Other (n=3)\l Withdraw (n=3)\l"] -node25 [label = "Lost to follow-up (n=5)\l Discontinued (n=2)\l Other (n=2)\l Withdraw (n=1)\l"] -node26 [label = "Lost to follow-up (n=9)\l Death (n=1)\l Discontinued (n=5)\l Other (n=2)\l Withdraw (n=1)\l"] -node27 [label = "Lost to follow-up (n=12)\l Death (n=2)\l Other (n=4)\l Withdraw (n=6)\l"] -node28 [label = "Lost to follow-up (n=7)\l Death (n=1)\l Discontinued (n=4)\l Withdraw (n=2)\l"] -node29 [label = "Lost to follow-up (n=7)\l Death (n=2)\l Discontinued (n=2)\l Other (n=1)\l Withdraw (n=2)\l"] -node30 [label = "Final Analysis (n=28)" group=C1] -node31 [label = "Final Analysis (n=38)" group=C2] -node32 [label = "Final Analysis (n=45)" group=C3] -node33 [label = "Final Analysis (n=25)" group=C4] -node34 [label = "Final Analysis (n=30)" group=C5] -node35 [label = "Final Analysis (n=31)" group=C6] + +node1 [label = "Population (n=300)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node2 [label = "Excluded (n=33)\l Dead (n=4)\l MRI not collected (n=3)\l Other (n=8)\l Sample not collected (n=18)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node3 [label = "Randomized patient (n=267)" group=A1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node4 [label = "Conc (n=142)" group=B1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node5 [label = "Seq (n=125)" group=B2 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node6 [label = "Trt A (n=42)" group=C1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node7 [label = "Trt B (n=44)" group=C2 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node8 [label = "Trt C (n=56)" group=C3 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node9 [label = "Trt A (n=44)" group=C4 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node10 [label = "Trt B (n=39)" group=C5 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node11 [label = "Trt C (n=42)" group=C6 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node12 [label = "Lost of Follow-up (n=2)\l Never dosed (n=2)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node13 [label = "Lost of Follow-up (n=1)\l Never dosed (n=1)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node14 [label = "Lost of Follow-up (n=2)\l Never dosed (n=2)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node15 [label = "Lost of Follow-up (n=7)\l Declined (n=2)\l Never dosed (n=3)\l Ranomised by error (n=2)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node16 [label = "Lost of Follow-up (n=2)\l Never dosed (n=1)\l Ranomised by error (n=1)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node17 [label = "Lost of Follow-up (n=4)\l Never dosed (n=3)\l Ranomised by error (n=1)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node18 [label = "Followup-up (n=40)" group=C1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node19 [label = "Followup-up (n=43)" group=C2 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node20 [label = "Followup-up (n=54)" group=C3 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node21 [label = "Followup-up (n=37)" group=C4 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node22 [label = "Followup-up (n=37)" group=C5 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node23 [label = "Followup-up (n=38)" group=C6 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node24 [label = "Lost to follow-up (n=12)\l Death (n=2)\l Discontinued (n=4)\l Other (n=3)\l Withdraw (n=3)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node25 [label = "Lost to follow-up (n=5)\l Discontinued (n=2)\l Other (n=2)\l Withdraw (n=1)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node26 [label = "Lost to follow-up (n=9)\l Death (n=1)\l Discontinued (n=5)\l Other (n=2)\l Withdraw (n=1)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node27 [label = "Lost to follow-up (n=12)\l Death (n=2)\l Other (n=4)\l Withdraw (n=6)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node28 [label = "Lost to follow-up (n=7)\l Death (n=1)\l Discontinued (n=4)\l Withdraw (n=2)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node29 [label = "Lost to follow-up (n=7)\l Death (n=2)\l Discontinued (n=2)\l Other (n=1)\l Withdraw (n=2)\l" fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node30 [label = "Final Analysis (n=28)" group=C1 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node31 [label = "Final Analysis (n=38)" group=C2 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node32 [label = "Final Analysis (n=45)" group=C3 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node33 [label = "Final Analysis (n=25)" group=C4 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node34 [label = "Final Analysis (n=30)" group=C5 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] +node35 [label = "Final Analysis (n=31)" group=C6 fillcolor="white" style="filled" fontcolor="black" fontsize=11.2] ## Invisible point node for joints @@ -139,7 +140,7 @@ subgraph { rank = same; rankdir = LR; P22; node29; } -edge[style=""]; +edge[color="black" penwidth=1 style="" arrowhead="normal" arrowsize=1.00]; node1 -> P1 [arrowhead = none]; P1 -> node2; diff --git a/tests/testthat/_snaps/multiple-split/multiple-split.png b/tests/testthat/_snaps/multiple-split/multiple-split.png index e968fd9..752981d 100644 Binary files a/tests/testthat/_snaps/multiple-split/multiple-split.png and b/tests/testthat/_snaps/multiple-split/multiple-split.png differ diff --git a/tests/testthat/test-build_grviz.R b/tests/testthat/test-build_grviz.R index f1a693b..7f01e3a 100644 --- a/tests/testthat/test-build_grviz.R +++ b/tests/testthat/test-build_grviz.R @@ -41,6 +41,39 @@ test_that("Check plot creation", { }) +test_that("New options", { + + set_consort_defaults( + arrow_gp = gpar(col = "green"), + label_txt_gp = gpar(col = "red") + ) + g <- add_box(txt = c("Study 1 (n=8)", "Study 2 (n=12)")) + g <- add_box(g, txt = "Included All (n=20)") + g <- add_side_box(g, txt = "Excluded (n=7):\n\u2022 MRI not collected (n=3)") + g <- add_box(g, txt = "Randomised") + g <- add_split(g, txt = c("Arm A (n=143)", "Arm B (n=142)")) + g <- add_box(g, txt = c("Follow-up (n=20)", + "Follow-up (n=7)")) + + g <- add_side_box(g, txt = c("Excluded (n=15):\n\u2022 MRI not collected (n=3)\n\u2022 Tissues not collected (n=4)\n\u2022 Other (n=8)", + "Excluded (n=7):\n\u2022 MRI not collected (n=3)\n\u2022 Tissues not collected (n=4)")) + + g <- add_box(g, txt = c("Final analysis (n=128)", "Final analysis (n=135)")) + + g <- add_label_box(g, + txt = c("1" = "Screening", "3" = "Randomized", "6" = "Final analysis")) + + txt <- build_grviz(g) + expect_snapshot_file(to_grviz(txt), "grviz-withopts.gv") + + # skip_if_not(tolower(.Platform$OS.type) == "windows") + skip_on_ci() + expect_snapshot_file(save_png(g), "build-grviz-withopts.png") + +}) + +init_consort_defaults() + test_that("Missing in some nodes", { g <- add_box(txt = c("Study 1 (n=8)", "Study 2 (n=12)")) g <- add_box(g, txt = "Included All (n=20)") diff --git a/tests/testthat/test-textbox.R b/tests/testthat/test-textbox.R index 8fcc2c4..76204b0 100644 --- a/tests/testthat/test-textbox.R +++ b/tests/testthat/test-textbox.R @@ -17,21 +17,21 @@ test_that("Box width and height", { }) test_that("Box options", { - options( + old <- set_consort_defaults( txt_gp = gpar(cex = 0.5), box_gp = gpar(fill = "red") ) + on.exit(set_consort_defaults( + txt_gp = old$txt_gp, + box_gp = old$box_gp + ), add = TRUE) + bx1 <- textbox(text = "This is a test") # Text size expect_equal(bx1$txt_gp$cex, 0.5) # Box fill expect_equal(bx1$box_gp$fill, "red") - - options( - txt_gp = gpar(), - box_gp = gpar() - ) }) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R new file mode 100644 index 0000000..ce6d97b --- /dev/null +++ b/tests/testthat/test-utils.R @@ -0,0 +1,20 @@ + +test_that("set_consort_defaults merges gpar values", { + + old <- set_consort_defaults(txt_gp = gpar(lwd = 1)) + on.exit(consort_global$defaults <- old, add = TRUE) + + new_txt <- consort_opt("txt_gp") + # lwd was added + expect_equal(new_txt$lwd, 1) + # existing col was preserved + expect_equal(new_txt$col, "black") + + set_consort_defaults(arrow_gp = gpar(col = "red")) + new_arrow <- consort_opt("arrow_gp") + # col was updated + expect_equal(new_arrow$col, "red") + # existing lwd was preserved + expect_equal(new_arrow$lwd, 1) + +}) diff --git a/vignettes/consort_diagram.Rmd b/vignettes/consort_diagram.Rmd index 9a3856a..e4fd87c 100644 --- a/vignettes/consort_diagram.Rmd +++ b/vignettes/consort_diagram.Rmd @@ -110,6 +110,13 @@ Although many efforts have been made to draw the plot as easy as possible, you m cat(build_grviz(g), file = "consort.gv") ``` +# Some default settings +The package has some default settings for the line width, color, arrow type and length. These can be easily changed with `set_consort_defaults()`. For example, you can set `set_consort_defaults(arrow_gp = gpar(col = "red", lwd = 2))` to change the arrow graphical parameters. Use `get_consort_defaults()` to view current settings. The default parameters are below: +```{r defualts} +get_consort_defaults() +``` + + # Working example (self generation) ## Single arm @@ -205,7 +212,7 @@ The previous is to easily generate a consort diagram based on a disposition data ```{r providetext1, fig.width = 7, fig.height = 4} library(grid) # Might want to change some settings -options(txt_gp = gpar(cex = 0.8)) +set_consort_defaults(txt_gp = gpar(cex = 0.8)) txt0 <- c("Study 1 (n=160)", "Study 2 (n=140)") txt1 <- "Population (n=300)" @@ -275,7 +282,7 @@ plot(g) ## Using disposition table ```{r disposition-data, fig.width = 7, fig.height = 6.5} -options(txt_gp = gpar(cex = 0.8)) +set_consort_defaults(txt_gp = gpar(cex = 0.8)) dispos.data$arm <- factor(dispos.data$arm) diff --git a/vignettes/customisation.Rmd b/vignettes/customisation.Rmd deleted file mode 100644 index 3f348df..0000000 --- a/vignettes/customisation.Rmd +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: "Customising CONSORT Diagrams" -output: - rmarkdown::html_vignette: - toc: yes -vignette: > - %\VignetteIndexEntry{Customising CONSORT Diagrams} - %\VignetteEngine{knitr::rmarkdown} - %\VignetteEncoding{UTF-8} ---- - -```{r, include = FALSE} -knitr::opts_chunk$set( - collapse = TRUE, - comment = "#>" -) -``` - -```{r setup} -library(consort) -library(grid) -``` - -This vignette covers customisation options for CONSORT diagrams created with the `consort` package. For the basics of building diagrams, see `vignette("consort_diagram")`. - -````{r example} -adsl <- data(adsl, package = "consort") - -library(consort) -g <- consort::consort_plot(data=adsl, - orders=list( - c(USUBJID = "Population"), - c(TRT01P = "Treatment "), - c( - COMP24FL = "Pariticpants planned for follow-up", - DCREASCD = "Reason for tot followed" - ) - ), - side_box=NULL, - allocation = c("TRT01P"), - labels = NULL, - cex = 0.8, - text_width = NULL - -) - -# Default behavior — identical to current package (no changes needed) -plot(g) - -# Customize line width and color -options(consort_line_lwd = 2, consort_line_col = "navy") -plot(g) - -# Customize arrow style - -options(consort_arrow_length = unit(0.15, "in"), consort_arrow_type = "open") -plot(g) -# Increase box spacing -options(consort_line_lwd = 1, consort_pad_u = 0.1) -plot(g) - -# Reset all to defaults - -options( - consort_line_lwd = NULL, - consort_line_col = NULL, - consort_arrow_length = NULL, - consort_arrow_type = NULL, - consort_pad_u = NULL -) -# uninstall -# remove.packages("consort") -# devtools::clean_dll() -# unloadNamespace("consort") - -``` \ No newline at end of file