--- title: "Composing Templates & Functions" author: "Doug Kelkhoff" date: "`r Sys.time()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Composing Templates & Functions} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( echo = TRUE, fig.align='center', fig.width = 6, fig.height = 4, out.width = '600px', out.height = '400px') library(ggpackets) ``` # Overview `ggpackets` is most powerful when it's being used to build template functions. By using it as a framework for packaging components of a ggplot into a single object, it provides a simple framework for constructing full featured ggplot wrappers. Most importantly, this will handle a wealth of ggplot-naitive parameters, exposing complete control over the components of a plot with minimal added syntax. That's not to say there is no work to be done - to elegantly handle parameters intuitively and with minimal syntactic burden on the end user, there might be some amount of argument handling in order to satisfy the ideal user interface. The goal is to allow the programmer to focus on building around the usability of their function instead of the nuances of constructing plots and handling interactivity with the extensible ggplot ecosystem. # Composing a template First, before we get too far along, `ggpackets` is nearly useless without components from `ggplot2`. Let's get that loaded. ```{r libraries, message = FALSE, warning = FALSE} library(ggplot2) ``` To build our first template, we'll simply add a few layers to a `ggpacket` object. We'll specify some aesthetic parameters - the size the line layer (`geom_line()`) and the point layer (`geom_point()`). ```{r simple_ggpacket} ggpk_my_template <- ggpacket() + geom_line(size = 1) + geom_point(size = 3) ``` ```{r simple_ggpacket_output} ggplot(Loblolly) + aes(x = age, y = height, color = Seed) + ggpk_my_template() + ggtitle('Growth of Loblolly Pines') ``` Although this might be helpful for making quick, reusable code, it does not provide much flexibility and is for all intents and purposes equivalent to `list(geom_line(size = 1), geom_point(size = 3))`. Perhaps the syntax is a little more familiar to those that use ggplot regularly, but aside from that, this simplist form provides very little real value. # Wrapping a template in a function Where the power of `ggpackets` begins to shine is in its transparent argument handling. If we want to bundle up our function above, but also want to provide optional handling of additional ggplot parameters, then ggpackets provides much of that functionality with very little additional syntax. Here we use the `ggpack()` function to articulate parameters to each layer and lump in any extra paramaters passed to our function. ```{r function_ggpacket} ggpk_my_template <- function(...) { ggpacket(...) %+% geom_line(size = 1, ...) %+% geom_point(size = 3, ...) } ``` ```{r function_ggpacket_output} ggplot(Loblolly) + aes(x = age, y = height) + ggpk_my_template(aes(color = age), stat = 'summary', fun.data = mean_se, size = 5, alpha = 0.5) + ggtitle('Average Growth of Loblolly Pines') ``` In the function we declared above, all arguments passed to our wrapper function get filter down into all the internal layers. When we pass through the `...`, we pass through any unnamed arguments from the parent function. In fact, when we call the function, we can pass any arguments that would typically get passed to either of the ggplot functions wrapped inside of it, `geom_line` and `geom_point`. If we choose to pass both the `data` and `mapping` parameters (typically set using the `ggplot()` and `aes()` functions when constructing a ggplot object), we could even use this as a standalone function as shown below. > Be aware that this usage isn't particularly intuitive without a bit of added documentation. If you're building a function for someone else to use or to export from a package you're making, it's probably worth writing up some good documentation about what parameters you expect people to use. ```{r function_ggpacket_standalone_output} ggpk_my_template( data = Loblolly, mapping = aes(x = age, y = height, color = Seed)) ``` # Granular Parameter Passing There is one lingering issue. In this case, all parameters are being passed to both of the ggplot layers wrapped inside of our function. You can see the impact of this issue above, because both the size (thickness) of the lines and the size (diameter) of the points changed when we set a new size. If we want to control one or the other independently, we need a more precise way of passing parameters. This can be addressed by adding an identifying string to each of our layers using the `id` parameter to `ggpack()`. If we want to restrict parameters to only one layer or another, `ggpackets` can handle the filtering of parameters through to each of the layers by parsing the parameters for prefixes that match that particular layer's `id`. For example, if a layer has an `id = 'my_ggplot_layer'`, then we might pass the function a parameter like `my_ggplot_layer.size` (the id and parameter name separated by a period) to set the size parameter of that particular layer without affecting the others. ```{r granular_ggpacket} ggpk_my_template <- function(...) { ggpacket() %+% geom_line(.id = 'line', size = 1, ...) %+% geom_point(.id = 'point', size = 3, ...) } ``` ```{r granular_ggpacket_output} ggplot(Loblolly) + aes(x = age, y = height, color = Seed) + ggpk_my_template(point.size = 5, line.size = 0.5) + ggtitle('Average Growth of Loblolly Pines') ``` Lists are also permitted as an `id`, allowing for any one of a number of different prefixes to capture arguments for a particular layer. ```{r granular_ggpacket2} ggpk_my_template <- function(...) { ggpacket() %+% geom_line(.id = c('all', 'line'), size = 1, ...) %+% geom_point(.id = c('all', 'point'), size = 3, ...) } ``` ```{r granular_ggpacket_output2} ggplot(Loblolly) + aes(x = age, y = height) + ggpk_my_template(stat = 'summary', fun.data = mean_se, point.size = 5, line.size = 0.5) + ggtitle('Average Growth of Loblolly Pines') ``` # Default, User and Fixed Parameters The ordering of the parameters passed to any layer carries its own significance and can be used to exercise further control over how your function operates. If a parameter is passed to the layer more than once, the last instance always takes priority. This includes parameters passed as a list via the `dots` parameter. This allows calls to be constructed in a way that dictates which arguments are set in stone and which can be ovewritten by the user. Take for example the following function where all ellipses parameters are passed through to the layers using the `dots` parameter. In the first layer, `size` is declared before the parameters passed in by the user, whereas the second has `size` passed after the user parameters. ```{r parameters_ggpacket} ggpk_my_template <- function(...) { ggpacket() %+% geom_line(.id = 'line', size = 3, ...) %+% geom_point(.id = 'point', ..., size = 3) } ``` The user will be able to set the size of the line layer, but will be unable to change the size of the point layer because the last `size` parameter will always take precedent (even if it's passed with a prefix, like `point.size`). This gives considerable control over what impact the user can have over the plot template. ```{r parameters_ggpacket_output} ggplot(Loblolly) + aes(x = age, y = height, color = Seed) + ggpk_my_template(line.size = 0.1, point.size = 10) + ggtitle('Average Growth of Loblolly Pines') ```