--- title: 'Report Generation' output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Report Generation} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE, message=FALSE, eval=FALSE) require(ubiquity) require(ggplot2) require(ubiquity) require(officer) require(onbrand) require(flextable) ``` ```{r results="hide", warning=FALSE, echo=FALSE} fr = system_new(file_name = "system.txt", system_file = "mab_pk", overwrite = TRUE, output_directory = tempdir()) cfg = build_system(system_file = file.path(tempdir(), "system.txt"), output_directory = file.path(tempdir(), "output"), temporary_directory = tempdir()) cfg_pptx = system_rpt_read_template(cfg, "PowerPoint") cfg_docx = system_rpt_read_template(cfg, "Word") fr_pptx = system_rpt_template_details(cfg_pptx) fr_docx = system_rpt_template_details(cfg_docx) tdeets = list() tdeets[["pptx"]][["txt"]] = fr_pptx[["txt"]] tdeets[["docx"]][["txt"]] = fr_docx[["txt"]] tdeets[["pptx"]][["ft"]] = fr_pptx[["ft"]] tdeets[["docx"]][["ft"]] = fr_docx[["ft"]] save(tdeets, file="Reporting.RData") ``` ```{r echo=FALSE, eval=TRUE} # This should create tdeets load("Reporting.RData") ``` ## Introduction Reporting is implemented using the `onbrand` package which is a templated interface to the `officer` package. Officer provides a lot of control over the generation of both Word and PowerPoint documents. If you feel comfortable programming in R, you may wan to use that package directly. However the templated workflow in `onbrand` facilitates switching between organizational templates. Currently `ubiquity` has support for generating both PowerPoint and Word reports, and this vignette will go over example scripts for both types of reports. ### Implenting `onbrand` in `ubiquity` One objective of reporting in `ubiquity` is to allow the management of several reports simultaneously. This is done by creating wrapper functions for functions in `onbrand`. Where the `onbrand` functions take in an `onbrand` object, the wrapper functions take in a `ubiquity` system object and an optional report name (`rptname`). The other inputs to the `ubiquty` wrapper functions are passed through directly to the wrapped `onbrand` function. At the bottom of this vignette, the `ubiquity` function name is listed along with the `onbrand` name it wraps around. ### Reporting workshop files To make a copy of the example scripts in the current working directory run the following: ```{r } library(ubiquity) fr = workshop_fetch(section="Reporting", overwrite=TRUE) ``` This should create the following scripts * `make_report_PowerPoint.R` Generates a PowerPoint presentation from an R script * `make_report_Word.R` Generates a Word document from an R script Then general process for creating a report is: 1. Initialize a report using the function `system_rpt_read_template()`. 2. Use the functions that add content to reports (`system_rpt_add_slide()` for PowerPoint and `system_rpt_add_doc_content()` for Word), or append analysis results using integrated workflow reporting (e.g. `system_rpt_estimation()` to add the results of parameter estimation). 3. Lastly, save the output using `system_rpt_save_report()`. The examples below are intended to work with a `ubiquity` system object. So at the top of any script you will need to build the system. If you don't have a system file, this command will create an empty template in the current directory: ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = build_system("system.txt") ``` ## PowerPoint reports (`make_report_PowerPoint.R`) ### Creating some content Before we get started we'll create some content for the presentation. Since we're wrapping around `onbrand` functions, the content is expected in a format consistent with those functions. For details on the format of different content types, see the help for `onbrand::add_pptx_ph_content()`. First we'll create some figures. First a ggplot object (`p`) and an image file (`imgfile`). ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} library(ggplot2) p = ggplot() + annotate("text", x=0, y=0, label = "picture example") imgfile = tempfile(pattern="image", fileext=".png") ggsave(filename=imgfile, plot=p, height=5.15, width=9, units="in") ``` Next we'll create tabular data (`tdata`): ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} tdata = data.frame(Parameters = c("Vp", "Cl", "Q", "Vt"), Values = 1:4, Units = c("L", "L/hr", "L/hr", "L") ) ``` Tables can be displayed in three different ways: as an Office table, an `onbrand` abstraction of a `flextable`, or a user defined `flextable` object. These are lists with specific elements. You should see the content specifications found in `onbrand::add_pptx_ph_content()` for details on how these lists should be formatted. The examples below should be able to get you started. **Office table** ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} tco = list(table = tdata, header = TRUE, first_row = FALSE) ``` **`flextable` using `onbrand` abstraction** ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} tcf = list(table = tdata, # This element contains the table data header_top = list( # Defining the table headers Parameters = "Name", Values = "Value", Units = "Units"), cwidth = 0.8, # Column width table_autofit = TRUE, # Making the tables automatically fit table_theme = "theme_zebra", # Selecting the table theme first_row = FALSE) ``` **User-defined `flextable` object** ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} tfo = flextable::flextable(tdata) tfo = flextable::autofit(tfo) ``` Lastly list content is specified in a paired vector. The first element indicates the indention level, and the second indicates the content. **Note**: This will only work for placeholders that contain list content. ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} lcontent = c(1, "First major item", 2, "first sub bullet", 2, "second sub bullet", 3, "sub sub bullet", 1, "Second major item", 2, "first sub bullet", 2, "second sub bullet") ``` ### Creating a new report First we initialize a new report. If the template option is set to `"PowerPoint"` it will create an empty presentation using the internal `ubiquity` template. Below we discuss creating custom templates for your organization. ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = system_rpt_read_template(cfg, "PowerPoint") ``` ### Adding slides to a report Slides are added using `ubiquity::system_rpt_add_slide()` with the slide template to be used and the elements identifying the placeholders and the placeholder content. Here we're adding slides for a title (`"title_slide"`) and a section (`"section_side"`). ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = system_rpt_add_slide(cfg, template = "title_slide", elements = list( title=list(content = "Reporting in ubiquity", type = "text"))) cfg = system_rpt_add_slide(cfg, template = "section_slide", elements = list( title=list(content = "Content Types", type = "text"))) ``` For each element there is a list element (e.g. `title`) that contains content and content type. The type specifies whether the content is a figure, table, etc. In this example we're using text. There are two types of text content. One is plain text and the other is lists. This is determined by the placeholder type in PowerPoint. We'll go through examples below for different types of content and available templates. For details on the format of the `template` and `elements` inputs see the documentation for `onbrand::report_add_slide()`. To show the available templates, placeholder element names in each template, and the type of text they can hold you can do the following: ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} fr = system_rpt_template_details(cfg) ``` This will produce output in the console like the following: ```{r message=FALSE, warning=FALSE, eval=TRUE, echo=FALSE, message=FALSE} trim_idx = min(c(18, length(tdeets[["pptx"]][["txt"]]))) cat(paste(tdeets[["pptx"]][["txt"]][1:trim_idx], collapse="\n")) ``` #### Adding lists In the previous example we added text content, here we will add the list content we created above (`lcontent`). Notice that we simply specify the content type is `"list"`. **Note:** You can only do this if the content type is list in the slide master. ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = system_rpt_add_slide(cfg, template = "content_list", elements = list( title= list(content = "Lists", type = "text"), sub_title= list(content = "For placholders that contain lists.", type = "text"), content_body= list(content = lcontent, type = "list"))) ``` #### Adding figures If you specify the content type is `"ggplot"`, then you can just provide the ggplot object (`p`). ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = system_rpt_add_slide(cfg, template = "content_text", elements = list( title= list(content = "Figures: ggplot object", type = "text"), sub_title= list(content = "Using ggplot objects directly", type = "text"), content_body= list(content = p, type = "ggplot"))) ``` If you have an image in a file you want to insert you can use the `"imagefile"` content type. One was created above and the location is stored in the `imgfile` object. ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = system_rpt_add_slide(cfg, template = "content_text", elements = list( title= list(content = "Figures: image file", type = "text"), sub_title= list(content = "Inserting figures from files", type = "text"), content_body= list(content = imgfile, type = "imagefile"))) ``` #### Adding tables First we're going to add a table using the built in Office tables. The content is stored in `tco` and can be added to any placeholder as long as we specify the type as `"table"`. ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = system_rpt_add_slide(cfg, template = "content_text", elements = list( title= list(content = "Tables: Office", type = "text"), sub_title= list(content = "Table in native Office format", type = "text"), content_body= list(content = tco, type = "table"))) ``` Similarly, you can add a `flextable` using the `onbrand` abstraction by specifying the type as `"flextable"` and supply the correctly formatted content created above (`tcf`). ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = system_rpt_add_slide(cfg, template = "content_text", elements = list( title= list(content = "Tables: flextable", type = "text"), sub_title= list(content = "Flextables using onbrand abstraction", type = "text"), content_body= list(content = tcf, type = "flextable"))) ``` Lastly, you can create a `flextable` directly. This gives you a lot of control with regards to formatting. You simply need to specify the type as `"flextable_object"` and provide an object created with `flextable` (`tfo`). ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = system_rpt_add_slide(cfg, template = "content_text", elements = list( title= list(content = "Tables: flextable object", type = "text"), sub_title= list(content = "Flextables using a user-created flextable object", type = "text"), content_body= list(content = tfo, type = "flextable_object"))) ``` The rest of the reporting script (`make_report_PowerPoint.R`) simply provides examples of the other available slide templates. You can look through those to see if they are suitable for your needs. If they are not and you need other templates or wish to create a template for your school or organization, jump down to the section on custom organizational templates. #### Saving the report Once you're done you can then save the presentation to a file: ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} system_rpt_save_report(cfg, output_file = "example.pptx") ``` ## Word reports (`make_report_Word.R`) ### Creating some content Just like the PowerPoint example above, we need to create some content for Word reporting. We're using wrappers here for `onbrand` functions so document content needs to be provided in a specific format documented in the help for `onbrand::report_add_doc_content()` Text formatting can be plain text, formatted as Markdown, or formatted using the `officer::fpar()` command. ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} plain_text_content = paste(rep("The quick brown fox jumped over the lazy dog.", 70), collapse= " ") md_text_content = paste(rep("The *quick* brown fox **jumped** over the ~lazy dog~.", 70), collapse=" ") fpar_text_content = officer::fpar( officer::ftext("The quick ", prop=NULL), officer::ftext("brown", prop=officer::fp_text(color="brown")), officer::ftext(" fox jumped over the lazy dog.", prop=NULL)) ``` For figures we can use two formats a ggplot object (`p`) or an image file (`imgfile`) these are packed into content lists `gpc` and `ifc`, respectively. ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} library(ggplot2) p = ggplot() + annotate("text", x=0, y=0, label = "picture example") imgfile = tempfile(pattern="image", fileext=".png") ggsave(filename=imgfile, plot=p, height=5.15, width=9, units="in") gpc = list(image = p, caption = "This is an example of an image from a ggplot object.") ifc = list(image = imgfile, caption = "This is an example of an image from a file.") ``` The same tabular data from before is used here as well: ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} tdata = data.frame(Parameters = c("Vp", "Cl", "Q", "Vt"), Values = 1:4, Units = c("L", "L/hr", "L/hr", "L") ) ``` **Office table** The content for tables in Office format is similar but it has other elements as well such as a figure caption: ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} tco = list(table = tdata, # This element contains the table data header = TRUE, # These two lines control the header first_row = FALSE, caption = "This creates a table using an Office theme/format.") ``` **`flextable` using `onbrand` abstraction** Similarly we can create a `flextable` using the `onbrand` abstraction. In this case we're supplying a caption and specifying the format of the caption is Markdown (`"md"`). ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} tcf = list(table = tdata, # This element contains the table data caption_format = "md", caption = "This creates a flextable using the onbrand abstraction", header_top = list( # Defining the table headers Parameters = "Name", Values = "Value", Units = "Units"), cwidth = 0.8, # Column width table_autofit = TRUE, # Making the tables automatically fit table_theme = "theme_zebra", # Making the tables automatically fit first_row = FALSE) ``` **User-defined `flextable` object** ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} tfo = flextable::flextable(tdata) tfo = flextable::autofit(tfo) tcfo = list(ft = tfo, caption = "This inserts a flextable object created by the user") ``` ### Creating a new report We create a new Word report by defining the template option as `"Word"`. ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} cfg = system_rpt_read_template(cfg, "Word") ``` ### Adding content to the report Content is added using `ubiquity::system_rpt_add_doc_content()` with the content and type of content specified. This wraps around the `onbrand::report_add_doc_content()` function. See the help for that function for allowed types and the expected format of the content. Below we will cover some examples of common content to be added. #### Adding text Text content contains a `style` element where you can specify the `onbrand` style. ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} cfg = system_rpt_add_doc_content(cfg, type="text", content = list( style = "Heading_1", text = "Formatting Text")) ``` To get a list of available styles for a given template you can use `system_rpt_template_details()`: ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} fr = system_rpt_template_details(cfg) ``` This will produce output in the console like the following: ```{r message=FALSE, warning=FALSE, eval=TRUE, echo=FALSE, message=FALSE} cat(paste(tdeets[["docx"]][["txt"]], collapse="\n")) ``` Text can be added in three different formats: plain text, as Markdown, and using the `officer::fpar()` command from the `officer` package. **Plain text** If no `style` is specified a template default style will be used. ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} cfg = system_rpt_add_doc_content(cfg, type="text", content = list( text = plain_text_content)) ``` **Markdown** To add text content other than plain text you need to add a `format` element. For example you can supply text in Markdown format and then define the `format` as `"md"`. For details on Markdown supported see the help for the `onbrand::md_to_officer()` function. ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} cfg = system_rpt_add_doc_content(cfg, type="text", content = list( style = "Normal", format = "md", text = md_text_content)) ``` **Using `officer::fpar()`** Officer formats text using the `officer::fpar()` command. If you have an `fpar` object you can use that as the `text` element for the content. You just need to specify the `format` as `"fpar"`. ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} cfg = system_rpt_add_doc_content(cfg, type="text", content = list( style = "Normal", format = "fpar", text = fpar_text_content)) ``` #### Adding figures To add figures you can specify the type as either an `"imagefile"` or `"ggplot"`: ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} cfg = system_rpt_add_doc_content(cfg, type = "imagefile", content = ifc) cfg = system_rpt_add_doc_content(cfg, type = "ggplot", content = gpc) ``` #### Adding tables Tables can be added in Office table, `onbrand` abstraction of flextables, and as flextable objects. This is done by using the type `"table"`, `"flextable"`, and `"flextable_object"` respectively: ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} cfg = system_rpt_add_doc_content(cfg, type = "table", content = tco) cfg = system_rpt_add_doc_content(cfg, type = "flextable", content = tcf) cfg = system_rpt_add_doc_content(cfg, type = "flextable_object", content = tcfo) ``` #### Other content and formatting For information on adding other content (such as using placeholder text within a document) and formatting (number of columns, page orientation, etc) see the function `onbrand::report_add_doc_content()` for a list of allowed `type` values and the expected `content` format. #### Saving the report Once you're done you can then save the document to a file: ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE } system_rpt_save_report(cfg, output_file = "example.docx") ``` ## Using custom organizational templates Internally `ubiquity` uses the `onbrand` package for customizable templates. To create a template for your organization you should use the `onbrand` vignette for [Custom Templates]( https://onbrand.ubiquity.tools/articles/Custom_Office_Templates.html). This will instruct you to create a yaml mapping file for your templates. You can use the built in organizational template as a starting point for that: ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} tr = system_fetch_template(cfg, template="myOrg") ``` In order to use the `ubiquity` workflows, you will need to have specific placeholders for PowerPoint and styles for Word. You can have others, but you will at least need those listed below. These should already be defined in the organizational template. You simply need to ensure that the placeholder names and content types from PowerPoint are correct and that the style names in Word you create are also assigned to the correct `onbrand` styles. ### PowerPoint In PowerPoint you will need to have a document template with the following slide masters/templates. You will need to name the slide Master the name shown in the table below. For each slide master you will need to create placeholders for the specified content. Make sure the content type matches that shown below. When you create the `onbrand` mapping file you will need to use the placeholder names shown below. ```{r message=FALSE, warning=FALSE, eval=TRUE, echo=FALSE} tdeets[["pptx"]][["ft"]] ``` ### Word For Word reporting you will need to have a Word template with the following styles defined. When you create the `onbrand` mapping file you will need to use the onbrand Style name listed. ```{r message=FALSE, warning=FALSE, eval=TRUE, echo=FALSE} tdeets[["docx"]][["ft"]] ``` ### Using the custom templates To use the custom templates you would use `system_rpt_read_template()` and specify the mapping file and the Office document containing your template. For example if you wanted to load a PowerPoint template called `myOrg.pptx` and your yaml mapping file was called `myOrg.yaml` you would use the following: ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE} cfg = system_rpt_read_template(cfg, mapping = "myOrg.yaml", template = "myOrg.pptx") ``` After that you can just use all the normal reporting functions to add content to the report. ## Integration with `ubiquity` workflows ### Parameter estimation After performing a parameter estimation and archiving the estimation with a specified analysis name, that analysis name can be used to retrieve the results and append them to an open report using `system_rpt_estimation()`. This function supports both PowerPoint and Word reports. ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE } cfg = system_rpt_estimation(cfg=cfg, analysis_name="analysis_name") ``` ### Non-compartmental analysis (NCA) The results of NCA can be appended to a report using the `system_rpt_nca()` function and supplying the NCA analysis name. This function supports both PowerPoint and Word reports. ```{r message=FALSE, warning=FALSE, eval=FALSE, echo=TRUE } cfg = system_rpt_nca(cfg=cfg, analysis_name="analysis_name") ``` ## Modifying reports directly with `officer` Sometimes the functions provided above are not sufficient to get what you want done. It may be more convenient to directly use the `officer` functions to add content or modify your report. If you have report initialized, you can pull that report out of the ubiquity system object using `system_fetch_rpt_officer_object()`: ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} rpt = system_fetch_rpt_officer_object(cfg) ``` Now `rpt` is an `officer` object. If it contains a PowerPoint presentation you can use all of the `officer` functions for PowerPoint to modify that object. If it's a Word document you can use the Word functions from `officer` to modify/add content. Once you're done making changes you can put the object back using `system_set_rpt_officer_object()`: ```{r results="hide", message=FALSE, warning=FALSE, eval=FALSE} cfg = system_set_rpt_officer_object(cfg, rpt) ``` Then you can continue using the ubiquity functions above or save the document. ## Wrapper mapping between `ubiquity` and `onbrand` functions | `ubiquity` | `onbrand` | |:--------------------------------------|:--------------------------------------------| |`system_rpt_read_template()` | `onbrand::read_template()` | |`system_rpt_read_template()` | `onbrand::read_template()` | |`system_rpt_add_slide()` | `onbrand::report_add_slide()` | |`system_rpt_add_doc_content()` | `onbrand::report_add_doc_content()` | |`system_rpt_save_report()` | `onbrand::save_report()` | |`system_rpt_template_details()` | `onbrand::template_details()` | |`system_fetch_rpt_officer_object()` | `onbrand::fetch_officer_object()` | |`system_set_rpt_officer_object()` | `onbrand::set_officer_object()` |