#' Make dumbbell chart. #' #' @param df A data frame. #' @param col A numeric column. #' @param group_x The grouping column on the x-axis; only two groups. #' @param group_y The grouping column on the y-axis. #' @param point_size Point size. #' @param point_alpha Point alpha. #' @param segment_size Segment size. #' @param segment_color Segment color. #' @param group_x_title X-group and legend title. #' @param group_y_title Y-axis and group title. #' @param x_title X-axis title. #' @param title Title. #' @param subtitle Subtitle. #' @param caption Caption. #' @param line_to_y_axis TRUE or FALSE; add a line connected points and Y-axis. #' @param line_to_y_axis_type Line to Y-axis type. #' @param line_to_y_axis_width Line to Y-axis width. #' @param line_to_y_axis_color Line to Y-axis color. #' @param add_text TRUE or FALSE; add text at the points. #' @param add_text_vjust Vertical adjustment. #' @param add_text_size Text size. #' @param add_text_color Text color. #' @param theme_fun A ggplot2 theme, default to `theme_dumbbell()` #' @param scale_fill_fun A ggplot2 scale_fill function, default to `scale_fill_visualizer_discrete()` #' @param scale_color_fun A ggplot2 scale_color function, default to `scale_color_visualizer_discrete()` #' #' @return A dumbbell chart. #' @export #' dumbbell <- function( df, col, group_x, group_y, point_size = 5, point_alpha = 1, segment_size = 2.5, segment_color = color("light_blue_grey"), group_x_title = NULL, group_y_title = NULL, x_title = NULL, title = NULL, subtitle = NULL, caption = NULL, line_to_y_axis = FALSE, line_to_y_axis_type = 3, line_to_y_axis_width = 0.5, line_to_y_axis_color = color("dark_grey"), add_text = FALSE, add_text_vjust = 2, add_text_size = 3.5, add_text_color = color("dark_grey"), theme_fun = theme_dumbbell(), scale_fill_fun = scale_fill_visualizer_discrete(), scale_color_fun = scale_color_visualizer_discrete() ) { #------ Checks # df is a data frame checkmate::assert_data_frame(df) # col, group_x, group_y are character checkmate::assert_character(col, len = 1) checkmate::assert_character(group_x, len = 1) checkmate::assert_character(group_y, len = 1) # col, group_x, group_y are columns in df checkmate::assert_choice(col, colnames(df)) checkmate::assert_choice(group_x, colnames(df)) checkmate::assert_choice(group_y, colnames(df)) # Check numeric/logical values checkmate::assert_numeric(point_size, len = 1) checkmate::assert_numeric(point_alpha, lower = 0, upper = 1, len = 1) checkmate::assert_numeric(segment_size, len = 1) checkmate::assert_logical(line_to_y_axis, len = 1) checkmate::assert_numeric(line_to_y_axis_type, len = 1) checkmate::assert_numeric(line_to_y_axis_width, len = 1) checkmate::assert_logical(add_text, len = 1) checkmate::assert_numeric(add_text_vjust, len = 1) checkmate::assert_numeric(add_text_size, len = 1) # Get group keys group_x_keys <- df |> dplyr::group_by(!!rlang::sym(group_x)) |> dplyr::group_keys() |> dplyr::pull() # Check if only two groups if (length(group_x_keys) > 2) { rlang::abort( "Cannot draw a dumbbell plot for `group_x` with more than 2 groups" ) } # Pivot long data df_pivot <- df |> tidyr::pivot_wider( id_cols = c(!!rlang::sym(group_y)), values_from = !!rlang::sym(col), names_from = !!rlang::sym(group_x) ) df_pivot <- df_pivot |> dplyr::rowwise() |> dplyr::mutate( min = min( !!rlang::sym(group_x_keys[[1]]), !!rlang::sym(group_x_keys[[2]]), na.rm = T ), max = max( !!rlang::sym(group_x_keys[[1]]), !!rlang::sym(group_x_keys[[2]]), na.rm = T ) ) |> dplyr::ungroup() |> dplyr::mutate(diff = max - min) g <- ggplot2::ggplot(df_pivot) # Add line if (line_to_y_axis) { xend <- min(dplyr::pull(df, !!rlang::sym(col))) g <- g + ggplot2::geom_segment( ggplot2::aes( x = min, y = !!rlang::sym(group_y), yend = !!rlang::sym(group_y) ), xend = xend, linetype = line_to_y_axis_type, linewidth = line_to_y_axis_width, color = line_to_y_axis_color ) } # Add segment g <- g + ggplot2::geom_segment( ggplot2::aes( x = !!rlang::sym(group_x_keys[[1]]), y = !!rlang::sym(group_y), xend = !!rlang::sym(group_x_keys[[2]]), yend = !!rlang::sym(group_y) ), linewidth = segment_size, color = segment_color ) # Add points g <- g + ggplot2::geom_point( data = df, ggplot2::aes( x = !!rlang::sym(col), y = !!rlang::sym(group_y), color = !!rlang::sym(group_x), fill = !!rlang::sym(group_x) ), size = point_size, alpha = point_alpha ) # Add title, subtitle, caption, x_title, y_title g <- g + ggplot2::labs( title = title, subtitle = subtitle, caption = caption, x = x_title, y = group_y_title, color = group_x_title, fill = group_x_title ) # Add stat labels to points if (add_text) { g <- g + ggrepel::geom_text_repel( data = df, ggplot2::aes( x = !!rlang::sym(col), y = !!rlang::sym(group_y), label = !!rlang::sym(col) ), vjust = add_text_vjust, size = add_text_size, color = add_text_color ) } # Add theme g <- g + theme_fun # Add scale fun if (!is.null(scale_fill_fun)) { g <- g + scale_fill_fun } if (!is.null(scale_color_fun)) { g <- g + scale_color_fun } return(g) }