Custom `ggplot2` arguments module
Maciej Nasiński
2021-12-01
custom-ggplot2-arguments.Rmd
This vignette will guide you through implementation of custom
ggplot2::labs
and ggplot2::theme
for
ggplot2
graphics based modules. We will enable 2 ways of
updating ggplot2::labs
and ggplot2::theme
by
the end users. The ggplot2
specification could be updated
with the teal.ggplot2_args
options
variable or
a ggplot2_args
argument in a tm_g_*
module. We
still take into account default specification set up by the module
creator in the server function, which has the lowest priority.
The implementation should consist of 5 steps:
- Adding a
ggplot2_args
argument to thetm_g_*
function and then its server function. The default should be set to theggplot2_args(labs = list(), theme = list())
function for single plot. andlist(default = ggplot2_args(labs = list(), theme = list()))
multi-plot modules. - Adding a validation (e.g.
stopifnot
orcheckmate
) of theggplot2_args
argument to thetm_*
function. The validation is more complex for multi-plot modules, where theggplot2_args
could be alist
. The module creator has to provide a list of plots names, which should be validated at this step and added to theparam
field inroxygen2
. For multi-plot modules the stepif (is_ggplot2_args) ggplot2_args <- list(default = ggplot2_args)
is recommended. - Aggregating and reducing all
ggplot2_args
sources withresolve_ggplot2_args()
. - Usage of the
parse_ggplot2_args()
function which will parse inputs to list of expressions. - Adding the created expression to the last chunk of a plot.
Reduce(function(x, y) call("+", x, y), list(...)
function could be helpful at this step.
The resolve_ggplot2_args()
function picks the first non
NULL value for each argument, checking in order:
-
ggplot2_args
argument provided by the end user. For multi-plot case, per plot (user_plot
) and then default (user_default
) setup. - Global R variable (
options
),teal.ggplot2_args
. -
module_plot
which is a developer setup.
Additional topics
When a more complex ggplot2
object has to be used inside
the teal.widgets::ggplot2_args
function, then a
base::quote
function would prevent an object expansion in
Show R Code. For example the ggplot2::element_text
function
returns a complex object, then we should use code like
teal.widgets::ggplot2_args(theme = list(plot.title = quote(ggplot2::element_text(size = 20))))
to prevent Show R Code expansion.
If you get a
promise already under evaluation: recursive default argument reference or earlier problems?
error, then probably your function argument has the same name as a
function which is an input for it. To solve the problem please use
::
to prefix it directly to a specific package, like
new_fun <- function(ggplot2_args = teal.widgets::ggplot2_args())
.
Loading libraries and data
library(shiny)
library(ggplot2)
library(teal.widgets)
options("teal.ggplot2_args" = teal.widgets::ggplot2_args(labs = list(caption = "Caption from options")))
user_ggplot2_args <- list(
default = ggplot2_args(
labs = list(title = "User default title"),
theme = list(legend.position = "right", legend.direction = "vertical")
),
plot1 = ggplot2_args(
labs = list(title = "User title"),
theme = list(legend.position = "right", legend.direction = "vertical")
)
)
app <- shinyApp(
ui = fluidPage(
shinyjs::useShinyjs(),
div(plotOutput("plot1"))
),
server = function(input, output, session) {
dev_ggplot2_args <- ggplot2_args(
labs = list(subtitle = "Dev substitle"),
theme = list(legend.position = "none")
)
f_ggplot2_expr <- parse_ggplot2_args(
resolve_ggplot2_args(
user_plot = user_ggplot2_args$plot1,
user_default = user_ggplot2_args$default,
module_plot = dev_ggplot2_args
)
)
plot_expr <- substitute(
expr = {
gg <- ggplot(iris, aes(x = Sepal.Length, y = Petal.Length, color = Species)) +
geom_point() +
ggplot_expr_labs +
ggplot_expr_theme
print(gg)
},
env = list(ggplot_expr_labs = f_ggplot2_expr$labs, ggplot_expr_theme = f_ggplot2_expr$theme)
)
print(plot_expr)
output$plot1 <- renderPlot(eval(plot_expr))
}
)
runApp(app)