2929# ' - `qi_width`: Width of the quantile interval (confidence interval) for
3030# ' the observed probability summary. Only relevant for binary models.
3131# ' Default is `0.95`.
32+ # ' - `return_components`: Logical, whether to return plot components as a
33+ # ' list instead of a combined plot. When `TRUE`, returns a list with
34+ # ' `$main` (main plot), `$boxplot` (boxplot if applicable), `$caption`
35+ # ' (caption text if applicable), and `$metadata`. Default is `FALSE`.
3236# ' @param options_coef_exp List of options for configuring how the exposure
3337# ' coefficient credible interval is displayed. Possible options include:
3438# ' - `qi_width`: Width of the quantile interval (credible interval) for
@@ -99,7 +103,8 @@ plot_er.ersim_med_qi <- function(
99103 list (
100104 add_boxplot = FALSE , boxplot_height = 0.15 ,
101105 show_boxplot_y_title = TRUE , var_group = NULL ,
102- n_bins = 4 , bin_breaks = NULL , qi_width = 0.95
106+ n_bins = 4 , bin_breaks = NULL , qi_width = 0.95 ,
107+ return_components = FALSE
103108 ),
104109 options_orig_data
105110 )
@@ -122,6 +127,7 @@ plot_er.ersim_med_qi <- function(
122127 var_exposure <- extract_var_exposure(x )
123128 add_boxplot <- options_orig_data $ add_boxplot
124129 boxplot_height <- options_orig_data $ boxplot_height
130+ return_components <- options_orig_data $ return_components
125131
126132 # Check input
127133 check_plot_er_input(x , show_orig_data )
@@ -176,6 +182,94 @@ plot_er.ersim_med_qi <- function(
176182 )
177183 }
178184
185+ # Return components if requested --------------------------------------------
186+ if (return_components ) {
187+ # Build boxplot if needed
188+ gg_boxplot <- NULL
189+ if (add_boxplot ) {
190+ var_group <- options_orig_data $ var_group
191+
192+ if (is.null(var_group )) {
193+ gg_boxplot <-
194+ ggplot2 :: ggplot(data = origdata , ggplot2 :: aes(
195+ x = .data [[var_exposure ]],
196+ y = 0
197+ )) +
198+ ggplot2 :: geom_boxplot(alpha = 0.5 ) +
199+ ggplot2 :: scale_y_continuous(expand = ggplot2 :: expansion(mult = 0.2 )) +
200+ ggplot2 :: theme(
201+ panel.grid.major.y = ggplot2 :: element_blank(),
202+ panel.grid.minor.y = ggplot2 :: element_blank(),
203+ axis.ticks.y = ggplot2 :: element_blank(),
204+ axis.title.y = ggplot2 :: element_blank(),
205+ axis.text.y = ggplot2 :: element_blank()
206+ )
207+ } else {
208+ # Define the numeric variable name with a leading dot
209+ var_group_num <- paste0(" ." , var_group , " _num" )
210+
211+ # Convert the categorical variable to a numeric index
212+ origdata [[var_group_num ]] <- as.numeric(factor (origdata [[var_group ]]))
213+
214+ gg_boxplot <-
215+ ggplot2 :: ggplot(data = origdata , ggplot2 :: aes(
216+ x = .data [[var_exposure ]],
217+ y = .data [[var_group_num ]]
218+ )) +
219+ ggplot2 :: geom_boxplot(
220+ ggplot2 :: aes(
221+ fill = .data [[var_group ]],
222+ color = .data [[var_group ]]
223+ ),
224+ alpha = 0.5
225+ ) +
226+ ggplot2 :: scale_y_continuous(
227+ name = var_group ,
228+ breaks = unique(origdata [[var_group_num ]]),
229+ labels = levels(factor (origdata [[var_group ]])),
230+ expand = ggplot2 :: expansion(mult = 0.2 )
231+ ) +
232+ ggplot2 :: theme(
233+ panel.grid.minor.y = ggplot2 :: element_blank()
234+ )
235+ if (! options_orig_data $ show_boxplot_y_title ) {
236+ gg_boxplot <- gg_boxplot +
237+ ggplot2 :: theme(axis.title.y = ggplot2 :: element_blank())
238+ }
239+ }
240+
241+ gg_boxplot <- gg_boxplot +
242+ ggplot2 :: theme(
243+ legend.position = " none" ,
244+ plot.margin = ggplot2 :: margin(t = 0 )
245+ )
246+ }
247+
248+ # Build caption if needed
249+ caption <- NULL
250+ if (show_caption ) {
251+ caption <- .build_caption(
252+ x , show_orig_data , show_coef_exp ,
253+ options_orig_data , options_coef_exp , options_caption
254+ )
255+ }
256+
257+ # Create components list
258+ components <- list (
259+ main = gg ,
260+ boxplot = gg_boxplot ,
261+ caption = caption
262+ )
263+ attr(components , " metadata" ) <- list (
264+ boxplot_height = boxplot_height ,
265+ var_exposure = var_exposure ,
266+ var_resp = extract_var_resp(x ),
267+ endpoint_type = attr(x , " endpoint_type" )
268+ )
269+ class(components ) <- c(" er_plot_components" , " list" )
270+ return (components )
271+ }
272+
179273 # Add boxplot ---------------------------------------------------------------
180274 if (add_boxplot ) {
181275 rlang :: check_installed(" patchwork" )
@@ -587,6 +681,12 @@ plot_er.ermod <- function(
587681# ' draws. Default is `0.95`.
588682# ' @param show_caption Logical, whether to show the caption note for the
589683# ' plot. Default is `TRUE`.
684+ # ' @param return_components Logical, whether to return plot components as a
685+ # ' list instead of a combined plot. When `TRUE`, returns a list with
686+ # ' `$main` (main plot), `$boxplot` (boxplot if applicable), `$caption`
687+ # ' (caption text if applicable), and `$metadata`. This allows users to
688+ # ' customize individual plot components before combining them. Default is
689+ # ' `FALSE`.
590690# '
591691# ' @details
592692# ' The following code will generate the same plot:
@@ -613,6 +713,10 @@ plot_er.ermod <- function(
613713# ' )
614714# ' }
615715# '
716+ # ' To customize plot elements (titles, shapes, themes), use
717+ # ' `return_components = TRUE` to get individual plot components, modify them,
718+ # ' then recombine with `combine_er_components()`.
719+ # '
616720# ' @return A ggplot object
617721# ' @examplesIf BayesERtools:::.if_run_ex_plot_er()
618722# ' \donttest{
@@ -627,6 +731,13 @@ plot_er.ermod <- function(
627731# ' plot_er_gof(ermod_bin, var_group = "Dose_mg", show_coef_exp = TRUE) *
628732# ' # Use log10 scale for exposure, need to use `*` instead of `+`
629733# ' xgxr::xgx_scale_x_log10(guide = ggplot2::guide_axis(minor.ticks = TRUE))
734+ # '
735+ # ' # Customize plot components
736+ # ' comps <- plot_er_gof(ermod_bin, var_group = "Dose_mg", return_components = TRUE)
737+ # ' comps$main <- comps$main +
738+ # ' ggplot2::labs(title = "Exposure-Response Analysis", x = "AUC (ng*h/mL)") +
739+ # ' ggplot2::theme_bw()
740+ # ' combine_er_components(comps)
630741# ' }
631742# '
632743plot_er_gof <- function (
@@ -643,7 +754,8 @@ plot_er_gof <- function(
643754 coef_size = 4 ,
644755 qi_width_coef = 0.95 ,
645756 qi_width_sim = 0.95 ,
646- show_caption = TRUE ) {
757+ show_caption = TRUE ,
758+ return_components = FALSE ) {
647759 plot_er(
648760 x ,
649761 show_orig_data = TRUE ,
@@ -656,7 +768,8 @@ plot_er_gof <- function(
656768 var_group = var_group ,
657769 n_bins = n_bins ,
658770 bin_breaks = bin_breaks ,
659- qi_width = qi_width_obs
771+ qi_width = qi_width_obs ,
772+ return_components = return_components
660773 ),
661774 options_coef_exp = list (
662775 qi_width = qi_width_coef ,
@@ -762,3 +875,96 @@ set_pos_ci_annot <- function(x, pos_x, pos_y, var_exposure, var_resp) {
762875.if_run_ex_plot_er <- function () {
763876 requireNamespace(" xgxr" , quietly = TRUE )
764877}
878+
879+ # ' Combine ER plot components
880+ # '
881+ # ' @param components An object of class `er_plot_components` returned by
882+ # ' `plot_er()` or `plot_er_gof()` with `return_components = TRUE` or
883+ # ' `options_orig_data = list(return_components = TRUE)`.
884+ # ' @param heights Numeric vector of length 2 specifying the relative heights
885+ # ' of the main plot and boxplot. If `NULL` (default), uses the height from
886+ # ' the metadata (based on `boxplot_height` parameter).
887+ # ' @param add_caption Logical, whether to add the caption to the combined plot.
888+ # ' Default is `TRUE` if caption is available.
889+ # ' @param ... Additional arguments passed to `patchwork::wrap_plots()`.
890+ # '
891+ # ' @return A combined ggplot object.
892+ # ' @export
893+ # '
894+ # ' @examples
895+ # ' \dontrun{
896+ # ' # Get components
897+ # ' comps <- plot_er_gof(ermod_bin, return_components = TRUE)
898+ # '
899+ # ' # Modify components
900+ # ' comps$main <- comps$main + labs(title = "Custom Title")
901+ # '
902+ # ' # Recombine
903+ # ' combine_er_components(comps)
904+ # ' }
905+ combine_er_components <- function (components , heights = NULL ,
906+ add_caption = ! is.null(components $ caption ),
907+ ... ) {
908+ if (! inherits(components , " er_plot_components" )) {
909+ stop(
910+ " Input must be an object of class 'er_plot_components' " ,
911+ " returned by plot_er() or plot_er_gof() with return_components = TRUE."
912+ )
913+ }
914+
915+ gg <- components $ main
916+
917+ # Add boxplot if present
918+ if (! is.null(components $ boxplot )) {
919+ rlang :: check_installed(" patchwork" )
920+
921+ # Set default heights if not provided
922+ if (is.null(heights )) {
923+ boxplot_height <- attr(components , " metadata" )$ boxplot_height
924+ heights <- c(1 - boxplot_height , boxplot_height )
925+ }
926+
927+ # Adjust margins for combination
928+ gg <- gg +
929+ ggplot2 :: theme(plot.margin = ggplot2 :: margin(b = 0 ))
930+
931+ gg <-
932+ patchwork :: wrap_plots(
933+ list (gg , components $ boxplot ),
934+ nrow = 2 , heights = heights , ...
935+ ) +
936+ patchwork :: plot_layout(axes = " collect" , guides = " collect" )
937+ }
938+
939+ # Add caption if requested
940+ if (add_caption && ! is.null(components $ caption )) {
941+ gg <- gg +
942+ ggplot2 :: labs(caption = components $ caption ) +
943+ ggplot2 :: theme(
944+ plot.caption = ggplot2 :: element_text(family = " mono" , hjust = 0 )
945+ )
946+ }
947+
948+ return (gg )
949+ }
950+
951+ # ' @export
952+ print.er_plot_components <- function (x , ... ) {
953+ cat(" ER plot components (class: er_plot_components)\n " )
954+ cat(" Components:\n " )
955+ cat(" $main : Main ER plot\n " )
956+ if (! is.null(x $ boxplot )) {
957+ cat(" $boxplot : Exposure boxplot\n " )
958+ }
959+ if (! is.null(x $ caption )) {
960+ cat(" $caption : Caption text\n " )
961+ }
962+ cat(" \n Metadata:\n " )
963+ metadata <- attr(x , " metadata" )
964+ for (name in names(metadata )) {
965+ cat(sprintf(" %s: %s\n " , name , paste(metadata [[name ]], collapse = " , " )))
966+ }
967+ cat(" \n Use combine_er_components() to recombine after customization.\n " )
968+ cat(" Or access individual components for manual combination.\n " )
969+ invisible (x )
970+ }
0 commit comments