5 Documentation
This chapter covers function documentation in an app-package by introducing the basic fundamentals of the roxygen2
syntax (i.e., the ‘bare minimum’ to include for each function). I’ll also touch on a few helpful roxygen2
tags specific to Shiny modules and standalone app functions.
The current structure of our sap
package is displayed in the folder tree below:
sap/
├── DESCRIPTION
├── NAMESPACE
├── R
│ ├── mod_scatter_display.R
│ ├── mod_var_input.R
│ └── utils.R
├── README.md
├── app.R
├── man
├── movies.RData
├── sap.Rproj
└── www
└── shiny.png
4 directories, 10 files
(the rsconnect/
folder from deploying sap
has been removed)
If you recall, the output from the previous call to install()
returned the following message regarding the documentation:
No man pages found in package ‘sap’
Documenting the functions in the R/
folder will address this message, and I strongly encourage checking out the roxygen2
vignettes and the chapter in R Packages, 2ed for more information on documenting your app-package.
5.1 roxygen2
intro
See the 04_devtools
branch of sap
.
roxygen2
connects the package code (i.e., the .R
files in the R/
folder) to its documentation files (i.e., the .Rd
files in the man/
folder):
Two pieces of roxygen2
syntax to know are comment blocks and tags:
Comment blocks are any lines beginning with
#'
#' #' #'
Tags begin with
@
#' #' @tag #'
roxygen2
tags and comment blocks are placed above the functions stored in R/
to create help documentation:
#'
#' @tag
#'
<- function() {
my_fun
}
In the following sections, we’ll cover some roxygen2
basics using the scatter_plot()
function in the R/utils.R
file.
5.1.1 markdown = TRUE
When we created our app-package with usethis::create_package()
, support for markdown formatting in package help files was automatically included by adding Roxygen: list(markdown = TRUE)
to the DESCRIPTION
file:
Package: sap
Version: 0.0.0.9000
Type: Package
Title: movies app
Description: A movies data Shiny application.
Author: John Smith [aut, cre]
Maintainer: John Smith <John.Smith@email.io>
License: GPL-3
DisplayMode: Showcase
RoxygenNote: 7.2.3
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
# Always leave at least one empty final line in the DESCRIPTION file
5.1.2 @title
& @description
The first two sections of roxygen2
documentation are the title and description. These sections don’t require tags–roxygen2
will detect each section as long as there is at least one #'
line separating them (and their contents don’t extend past the length indicated in parentheses below):
#' function title (one line)
#'
#' A short description... (one paragraph)
#'
The @title
and @description
for the scatter_plot()
function stored in R/utils.R
might look like this:
#' Create scatter plot
#'
#' Custom [`ggplot2`](https://ggplot2.tidyverse.org/) function for building scatter plots in `sap`.
#'
5.1.3 @param
& @return
Document function arguments and outputs with @param
and @return
:
@param
: should include the name and description of each function input (i.e., their type and what they do)#' @param name description of its action
- Read more about
@param
in the arguments chapter of R Packages, 2ed.
- Read more about
@return
: these describe the type (or class) and structure of the function output#' @return type/structure of the output
- Read more about
@return
in the return value chapter of R Packages, 2ed.
- Read more about
Below are examples for @param
and @return
in the scatter_plot()
function stored in R/utils.R
:
#'
#' @param df `data.frame` or `tibble`
#' @param x_var string variable mapped to `x` axis
#' @param y_var string variable mapped to `y` axis
#' @param col_var string variable mapped to `color`
#' @param alpha_var number for point `alpha`
#' @param size_var number for point `size`
#'
#' @return A `ggplot2` plot object
To view how the roxygen2
syntax will appear in the .Rd
file, I’ll document sap
:
To reduce the amount of repetitive code, I’ll just be showing the keyboard shortcuts for each devtools
function from this point forward
Ctrl/Cmd + Shift + D
I can see the scatter_plot.Rd
file is written to the man/
folder:
ℹ Updating sap documentation
ℹ Loading sap
Writing scatter_plot.Rd
Documentation completed
File name alignment
Shiny apps (and most R projects) often contain utility functions in helper.R
or utils.R
files. Placing non-Shiny code in these files isn’t a bad practice–it’s is even encouraged in Mastering Shiny:
“You might want to collect smaller, simpler, functions into one place. I often use
R/utils.R
for this…”
However, because we’ll want to develop tests for scatter_plot()
, we should follow the advice in R Packages,1 and rename R/utils.R
to R/scatter_plot.R
as we transition to an app-package.2
R documentation (.Rd
) files have a formatting style similar to (La)TeX, but roxygen2
saves us from having to learn this syntax by automatically generating the .Rd
files. When we open man/scatter_plot.Rd
, we see it already contains the following:
To view the help file, I can enter ?scatter_plot
in the console:
?scatter_plot
And an informative message tells me that the development version scatter_plot.Rd
is being rendered:
ℹ Rendering development documentation for "scatter_plot"
Previewing the development versions of our documentation is a great way to verify the content in each .Rd
file meets our expectations.
5.1.4 @examples
@examples
are unique because they include executable code that demonstrates how a particular function works. In the Posit Workbench IDE, @examples
are especially helpful because they come with a clickable hyperlink (the @examples
from ggplot2::aes()
are below):
Below is an example demonstrating how the scatter_plot()
utility function works:
#' @examples
#' scatter_plot(mtcars,
#' x_var = "mpg",
#' y_var = "disp",
#' col_var = "cyl",
#' alpha_var = 0.5,
#' size_var = 3)
To preview the @examples
in the help file, I’ll document()
and open the development .Rd
file:
Ctrl/Cmd + Shift + D
?scatter_plot
ℹ Rendering development documentation for "scatter_plot"
The Run examples hyperlink won’t be active in the .Rd
file preview, but reviewing the code in @examples
allows me to correct any errors or typos early.
The scatter_plot()
function now has a Title, Description, Usage, Arguments, Value, and Examples documented. I consider these tags the minimum documentation to include for functions I’m making available to other users.
5.2 Documenting app functions
Your app-package will likely contain at least two functions specific to Shiny: modules and a standalone app function. roxygen2
has tags that can make the documentation for these functions more informative for readers.
Below are a few things to consider when documenting module functions:
Modules will typically include two functions in a single
.R
file: one for the UI and a counterpart in the server.Every module function will include at least one
@param
for the sharedid
.Returned objects are critical in Shiny’s reactive programming model, so I recommend extending the
@description
to include whether or not the returned output is reactive.If the returned output is an input (i.e.
@param
) for another function, link to that documentation functions with@seealso
or@family
.
5.2.1 @seealso
When documenting Shiny modules, I tend to think of the audience as anyone looking to understand the execution path through an application. For example, in our sap
application, the inputs are collected in the UI with the var_input
module, then passed to the scatter_display
module to render in the UI:
%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'monospace', 'primaryColor': '#33a02c', 'edgeLabelBackground':'#FFFFFF'}}}%% flowchart input(input module) --> seealso[<code>@seealso</code>] --> display(display module) style input fill:#8dd38d,stroke:none,rx:10,ry:10; style display fill:#8dd38d,stroke:none,rx:10,ry:10; style seealso fill:#89D6FB,stroke:#000000,rx:1,ry:1;
seealso
connecting documentationThe @seealso
tag can be used to connect mod_var_input_ui()
to it’s server function counterpart. The hyperlink to mod_var_input_server()
from mod_var_input_ui()
is created by placing the destination function in square brackets [fun()]
:
show/hide mod_var_input_ui() roxygen2
#' Variable input module (UI)
#'
#' @description
#' `var_input` collects the following graph inputs:
#' * `input$x`
#' * `input$y`
#' * `input$z`
#' * `input$alpha`
#' * `input$size`
#' * `input$plot_title`
#'
#' @param id UI module id
#'
#' @return module UI (HTML)
#'
#' @seealso [mod_var_input_server()]
#'
Ctrl/Cmd + Shift + D
@seealso
creates a hyperlink readers can use to open the next module in the execution path.
In mod_var_input_server()
, a link can be made to mod_scatter_display_server()
(the module function collecting the returned values) using the square brackets in the @return
section (without @seealso
):
show/hide mod_var_input_server() roxygen2
#' Variable input module (server)
#'
#' @param id server module id
#'
#' @seealso [mod_var_input_ui()]
#'
#' @return reactive inputs are returned in a `list()`:
#' * `"y" = input$y`
#' * `"x" = input$x`
#' * `"z" = input$z`
#' * `"alpha" = input$alpha`
#' * `"size" = input$size`
#' * `"plot_title" = input$plot_title`
#'
#' These become in the `var_inputs()` argument in [mod_scatter_display_server()]
#'
Ctrl/Cmd + Shift + D
5.2.2 @family
Module functions can also be grouped into families using @family {"description"}
. Below is an example of grouping the “scatter plot module functions” into a family:
show/hide mod_scatter_display_ui() roxygen2
#' Scatter plot display module (UI)
#'
#' @param id UI module id
#'
#'
#' @return module UI (HTML)
#'
#' @family {"scatter plot module functions"}
#'
5.2.3 @section
The @section
tag will create a level two header (##
/<h2></h2>
) section, which can help organize topics (like how to use an input or argument).
@seealso
can also be used to link back to the scatter_plot()
utility function:
show/hide mod_scatter_display_server() roxygen2
#' Scatter plot display module (server)
#'
#'
#' @param id server module id
#' @param var_inputs returned reactive list from [mod_var_input_server()].
#'
#' @section Referring to `var_inputs`:
#' Refer to the reactive returned values from `mod_var_input_server()` as:
#' * `var_inputs()$x`
#' * `var_inputs()$y`
#' * `var_inputs()$z`
#' * `var_inputs()$alpha`
#' * `var_inputs()$size`
#' * `var_inputs()$plot_title`
#'
#'
#' @return rendered plot and title output from [scatter_plot()]
#'
#' @family {"scatter plot module functions"}
#'
Ctrl/Cmd + Shift + D
The figure above shows how the @seealso
links can create a path for the graph parameters through the var_input
and scatter_display
modules. It also demonstrates how @family
groups the scatter_display
UI and server functions.
The scatter_plot()
utility function should also include a @seealso
tag for the module function it’s used in.
show/hide scatter_plot() roxygen2
#' Create scatter plot
#'
#' Custom [`ggplot2`](https://ggplot2.tidyverse.org/) function for building scatter plots in `sap`.
#'
#'
#' @param df `data.frame` or `tibble`
#' @param x_var string variable mapped to `x` axis
#' @param y_var string variable mapped to `y` axis
#' @param col_var string variable mapped to `color`
#' @param alpha_var number for point `alpha`
#' @param size_var number for point `size`
#'
#' @return A `ggplot2` plot object
#'
#'
#' @examples
#' scatter_plot(mtcars,
#' x_var = "mpg",
#' y_var = "disp",
#' col_var = "cyl",
#' alpha_var = 0.5,
#' size_var = 3)
#'
#' @seealso [mod_scatter_display_server()]
#'
Ctrl/Cmd + Shift + D
The goal when cross-referencing functions in your app-package is for anyone reading your documentation to follow the links and better understand any modules, their inputs, reactive values, and outputs.
In this case, linking to the scatter_plot()
function gives readers an interactive example to preview the output.
5.3 UI & Server functions
Splitting the standalone app function in app.R
into separate UI and server functions has multiple benefits:
Developing and loading each function will be easier when it’s stored in the
R/
folder.Having dedicated UI, server, and app function means we can develop them independently.
Standalone app functions means we can have multiple applications in the same app-package.
I’ve split the UI and server from launch_app()
into separate movies_ui()
and movies_server()
below.
The movies_ui()
doesn’t have any arguments–the only change is wrapping the shiny::fluidPage()
in shiny::tagList()
:
<- function() {
movies_ui ::tagList(
shiny::fluidPage(
shinytheme = shinythemes::shinytheme("spacelab"),
::titlePanel(
shiny::div(
shiny::img(
shinysrc = "shiny.png",
height = 60,
width = 55,
style = "margin:10px 10px"
), "Movies Reviews"
)
),::sidebarLayout(
shiny::sidebarPanel(
shinymod_var_input_ui("vars")
),::mainPanel(
shinymod_scatter_display_ui("plot")
)
)
)
) }
movies_server()
is written to be passed to the server
argument in shinyApp()
:
<- function(input, output, session) {
movies_server
<- mod_var_input_server("vars")
selected_vars
mod_scatter_display_server("plot", var_inputs = selected_vars)
}
5.3.1 @usage
I’ll explicitly describe the use of movies_ui()
and set @usage
to NULL
(note the use of a code block). I’ll also use @section
to describe each module UI function (and link to the server functions with @seealso
).
show/hide roxygen2 for movies_ui()
#' Movies UI function
#'
#' UI function for standalone app function
#'
#' @usage NULL
#'
#' @details
#' The [launch_app()] function is as a wrapper for `shiny::shinyApp()`:
#'
#' ```
#' shinyApp(ui = movies_ui, server = movies_server)
#' ```
#'
#' In [launch_app()]:
#' * UI is stored in `movies_ui()`
#' * server is stored in [movies_server()]
#'
#' @section `var_input` module:
#' [mod_var_input_ui()] is used to collect the following inputs:
#' * `input$x`
#' * `input$y`
#' * `input$z`
#' * `input$alpha`
#' * `input$size`
#' * `input$plot_title`
#'
#' @seealso [mod_var_input_server()]
#'
#'
#' @section `scatter_display` module:
#' [mod_scatter_display_ui()] displays the graph output using [scatter_plot()]
#'
#' @seealso [mod_scatter_display_server()]
#'
#' @return `ui` argument in `shiny::shinyApp()`
#'
Ctrl/Cmd + Shift + D
The documentation for movies_server()
is very similar to the Ui function–each module server function is documented in it’s own @section
.
Still, I’ll include a @section
for Communication
that describes how values are passed between mod_var_input_server()
and mod_scatter_display_server()
.
show/hide roxygen2 for movies_server()
#' Movies server function
#'
#' Server function for standalone app function
#'
#' @usage NULL
#'
#' @details
#' The [launch_app()] function is as a wrapper for `shiny::shinyApp()`:
#'
#' ```
#' shinyApp(movies_ui, movies_server)
#' ```
#'
#' In [launch_app()]:
#' * UI is stored in [movies_ui()]
#' * server is stored in `movies_server()`
#'
#' @section `var_input` module:
#' [mod_var_input_server()] returns following reactive values:
#' * `x`
#' * `y`
#' * `z`
#' * `alpha`
#' * `size`
#' * `plot_title`
#'
#' @seealso [mod_var_input_ui()]
#'
#' @section `scatter_display` module:
#' [mod_scatter_display_server()] displays the `ggplot2` graph with the [scatter_plot()] function.
#'
#' @seealso [mod_scatter_display_ui()]
#'
#' @section Communication:
#' The output from [mod_var_input_server()] should be supplied to the
#' `var_inputs` argument of [mod_scatter_display_server()].
#
#' @return `server` argument in `shiny::shinyApp()`
#'
Ctrl/Cmd + Shift + D
5.3.2 launch_app()
The standalone app function (launch_app()
) automatically calls shinyApp()
, with the movies_ui
and movies_server
functions supplied to ui
and server
(without parentheses).
<- function() {
launch_app ::shinyApp(ui = movies_ui, server = movies_server)
shiny }
The documentation for the standalone app function can be minimal–as long as it provides links to the UI and server.
#' Movies app standalone function
#'
#' Wrapper function for `shiny::shinyApp()`
#'
#' @return Shiny app
#'
#'
#' @seealso [mod_var_input_ui()], [mod_var_input_server()], [mod_scatter_display_ui()], [mod_scatter_display_server()]
#'
Ctrl/Cmd + Shift + D
The contents of app.R
can be changed to the following:
# pkgs <- c("shiny", "shinythemes", "stringr", "ggplot2", "rlang")
# install.packages(pkgs, quiet = TRUE)
# packages ------------------------------------
library(shiny)
library(shinythemes)
library(stringr)
library(ggplot2)
library(rlang)
# launch_app ------------------------------------
launch_app()
Now that we’ve documented everything in R/
, we’ll load()
, document()
, and install()
our package:
Ctrl/Cmd + Shift + L
::load_all(".") devtools
ℹ Loading sap
Ctrl/Cmd + Shift + D
==> devtools::document(roclets = c('rd', 'collate', 'namespace'))
ℹ Updating sap documentation
ℹ Loading sap
Documentation completed
Ctrl/Cmd + Shift + B
==> R CMD INSTALL --preclean --no-multiarch --with-keep.source sap
* installing to library ‘/path/to/local/install/sap-090c61fc/R-4.2/x86_64-apple-darwin17.0’
* installing *source* package ‘sap’ ...
** using staged installation
** R
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (sap)
In the Console, we should see the following:
Restarting R session...
> library(sap)
The roxygen2
documentation for sap
is saved in the 02-roxygen
branch. As we can see, calling devtools::document()
generates the .Rd
files in the man/
folder for each function in the R/
folder:
R/
├── launch_app.R
├── mod_scatter_display.R
├── mod_var_input.R
├── movies_server.R
├── movies_ui.R
└── scatter_plot.R
1 directory, 6 files
man/
├── launch_app.Rd
├── mod_scatter_display_server.Rd
├── mod_scatter_display_ui.Rd
├── mod_var_input_server.Rd
├── mod_var_input_ui.Rd
├── movies_server.Rd
├── movies_ui.Rd
└── scatter_plot.Rd
1 directory, 8 files
Recap
Good documentation aims to make it as easy as possible for others (and future you) to understand what your function does and how to use it.
Launch app with the shinypak
package:
launch('05_roxygen2')
Below is a recap of the topics covered in this chapter.
In the next section, we’ll use roxygen2
to manage our app-package dependencies.
‘More often, a single
.R
file will contain multiple function definitions: such as a main function and its supporting helpers, a family of related functions, or some combination of the two.’ - Organise functions into files, R Packages, 2ed↩︎Making
.R
file/function names brief but descriptive will also make writing and running tests easier.↩︎