-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME.Rmd
229 lines (138 loc) · 10.8 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
---
output: github_document
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/",
out.width = "100%"
)
```
# shinyNewColumns <a href='https://github.com/AARON-CLARK/shinyNewColumns'><img src="man/figures/hex-shinyNewColumns.png" align="right" height="170"/></a>
<!-- badges: start -->
[](https://lifecycle.r-lib.org/articles/stages.html#experimental)
<!-- badges: end -->
`shinyNewColumns` is a shiny module used to derive custom columns in R data.frames on the fly. The module simply produces one small UI element: an action button titled 'Create New Column' that can be placed anywhere in an existing shiny application. Upon clicking, a modal containing a user-friendly interface will launch & allow the user to build a new column off an existing data source. Specifically, the user will be able to define a new column name, type, & label using reference column(s) as a basis to populate it's values. Along the way, the module provides simple summaries & visuals of existing column's distributions. When satisfied, the user can click 'Add variable' and the module returns the user supplied data.frame with the new column appended PLUS the `dplyr::mutate()` expressions used to generate it.
Click the `DEMO APPP` button below to launch an example app which leverages the `shinyNewColumns` module to create new columns in the popular `iris` data.frame. The app's code is provided below as well. What are you waiting for? Go empower your users to make more columns!
<center>
<a href="https://bit.ly/shinynewcolumns">
<img src="man/figures/bttn_og_refresh_darkgrey.png" alt="Demo shinyNewColumns example app" width="15%">
</a>
</center>
<br>
## Shiny Conference 2022 {#tutorial}
The `shinyNewColumns` shiny module was featured as a tutorial for the first annual Appsilon Shiny Conference in April of 2022. The subject of the tutorial was on "**Evaluating user inputs from dynamically produced UI modules**". With time, Appsilon will post the tutorial's recording to their [YouTube](https://www.youtube.com/channel/UC6LqpR5qBfNlQp5mVIVsthA) channel. In the meantime, feel free to checkout the presentation slide deck [here](https://github.com/AARON-CLARK/shinyNewColumns/tree/master/inst/example/shinyconf%20tutorial). Enjoy and please feel free to reach out with any questions by [submitting an issue](https://github.com/AARON-CLARK/shinyNewColumns/issues) to the repo.
Note: As you navigate through the repo, please keep in mind that the code has likely changed (hopefully for the better) since the presentation originally aired.
## Purpose
Imagine everyone's worse nightmare - users interacting with vital data sources used on the back-end of your application. Why create a module that allows users to change data? Won't they screw it up? Well, first of all, the data doesn't 'get changed'! When using `shinyNewColumns`, users are only allowed to **append** new columns to an existing data source, preserving the original data. Believe it or not, there **are** in fact use cases when creating new columns could help solve a lot of problems & paint points. Putting this (super)power into the hands of users can actually free up the data owner's time (which can sometimes be the users themselves or sometimes developers), so why not?
## Scope
There are already a lot of R packages out there for editing data (such as `rhandsontable` or `editData`), so `shinyNewColumns` seeks to fill a small void in the niche that is deriving new data.
## Installation
You can install the current development version of `shinyNewColumns` from [GitHub](https://github.com/AARON-CLARK/shinyNewColumns) using:
```{r, eval = FALSE}
remotes::install_github("AARON-CLARK/shinyNewColumns")
```
## Example App
This is a basic example which shows you how to integrate the `shinyNewColumns` module into a shiny app. On the UI side, you just need to decide where to place the a `Add New Column` button. The server-side logic only needs a data.frame to manipulate. The return value, `out` will contain the same data, with new column(s) appended and the `dplyr::mutate()` code used to generate it.
```{r example, eval = FALSE}
library(shinyNewColumns)
library(shiny)
library(DT)
ui <- fluidPage(
h1("shinyNewColumns" , align = "center"),
br(), br(),
# just a button...
fluidRow(column(12, align = "center", mod_launchModal_ui("snc"))),
br(),
h3("New column's code:"),
h4("(generated by user inputs)"),
verbatimTextOutput("display_expr"),
br(),
h3("Iris data:"),
wellPanel(dataTableOutput("display_data"))
)
server <- function(input, output) {
out <- mod_launchModal_srv("snc", dat = iris)
output$display_expr <- renderPrint(out$expr())
output$display_data <- renderDataTable(out$data())
}
shinyApp(ui = ui, server = server)
```
<br>
#### Test drive now
The example app code (above) has been deployed on shinyapps.io for your convenience. Please click on the button below to get started exploring `shinyNewColumns`:
<center>
<a href="https://bit.ly/shinynewcolumns">
<img src="man/figures/bttn_og_refresh_darkgrey.png" alt="Demo shinyNewColumns example app" width="15%">
</a>
</center>
<br>
#### Visuals please!
Here is how the UI of the `app.R` code above renders. Notice the only only component of `shinyNewColumns` seen below is the action button lablled `Add New Column`:
<br>
```{r, echo=FALSE, out.width="85%", fig.align='center'}
knitr::include_graphics("man/figures/app_screenshot.PNG")
```
<br>
Upon clicking the button, a modal will launch where the user is prompted to provide information about the the new column's derivation!
<br>
```{r, echo=FALSE, out.width="85%", fig.align='center'}
knitr::include_graphics("man/figures/modal_range_screenshot.PNG")
```
<br>
When done specifying your inputs, click `Add Variable` and presto! You just built a new column! In addition to the new addition, you'll also observe that `shinyNewColumns` is capturing the `dplyr::mutate()` code used to generate it and `app.R` from our example app is promptly displaying it aboveo the data:
<br>
```{r, echo=FALSE, out.width="85%", fig.align='center'}
knitr::include_graphics("man/figures/final_result_screenshot.PNG")
```
<br>
## How `shinyNewColumns` works internally
This section can serve as a 'quick start guide' for developers looking to gain some basic understanding about the module's structure & contribute to `shinyNewColumns`.
There are three re-occurring themes in `shinyNewColumns`: generating dynamic UI, bringing user inputs together into expressions (or list of expressions), and then evaluating the results at the top, high-level module. As of the date this guide was authored, there are 4 shiny modules leveraged (see image below). It's assumed that overtime, more modules will be added to accommodate the needs of various column types.
<br>
```{r, echo=FALSE, out.width="85%", fig.align='center'}
knitr::include_graphics("man/figures/dev_guide_all_modules.PNG")
```
<br>
#### Module UI
From left to right, any `app.R` may call launchModal, launchModal then calls newCol, and newCol will call either rangeConditions or spin up `N` number of advConditions modules if the column type selected is 'Custom'. launchModal is pretty basic, it only recieves a data.frame from `app.R` and contains the following UI elements:
<br>
```{r, echo=FALSE, out.width="85%", fig.align='center'}
knitr::include_graphics("man/figures/dev_guide_launchModal.PNG")
```
<br>
As you can see, in only harbors the column type `selectInput()` at the top and a 'Cancel' & 'Add Variable' button at the bottom. The rest of the content is populated by newCol, which receives the data + the column type to produce the following UI:
<br>
```{r, echo=FALSE, out.width="85%", fig.align='center'}
knitr::include_graphics("man/figures/dev_guide_newCol_UI.PNG")
```
<br>
This is where the user can begin to define the new column's initial characteristics. The user is prompted to a variable name, label, reference variable, and number of conditions / groups. The reference variables are filtered to only show the numeric variables in the data since a Range Variable, by definition, creates groups (or categories) from a numeric range of values. The reference variable's distribution is plotted on the right hand side for convenience.
The reason newCol is separate from launchModal was to provide future space to call a completely different module in this `wellPanel()` depending on the column type selected. For example, not all column types are going to require a 'reference variable' and 'number of conditions/groups'. So, keeping this `wellPanel()` modularized helps us swap in the correct UI when needed.
Next, newCol passes the data, the reference variable, the number of groups, plus any else-group information down to the next shiny module. If column type = 'Range Variable', then the rangeConditions shiny module is called and which contains the following UI elements:
<br>
```{r, echo=FALSE, out.width="85%", fig.align='center'}
knitr::include_graphics("man/figures/dev_guide_rangeConditions_UI.PNG")
```
<br>
From there, the user is prompted to input the range values using `numericInput()`s and group names. Conversely, if column type = 'Custom', then any number of a advCondition modules will be rendered to coordinate with the number of groups selected on the slider from newCol.
<br>
```{r, echo=FALSE, out.width="85%", fig.align='center'}
knitr::include_graphics("man/figures/dev_guide_advConditions_UI.PNG")
```
<br>
Here, users will provide numerous inputs to define their categorical groups.
#### Returning expressions
No matter what column type is selected, after the information is passed down to the deepest shiny module, it needs to get passed back up to `app.R`:
<br>
```{r, echo=FALSE, out.width="85%", fig.align='center'}
knitr::include_graphics("man/figures/dev_guide_modals_passing_info_dwn_up.gif")
```
<br>
Since we know we want our end result to be a mutated data.frame using `dplyr`, we combine all the inputs at the base layer, rangeConditions in this case, and create our `mutate()` expression there. Then, pass that expression back up to the newCol, where expression receives it's name & label before it's passed up to launchModal for evaluation.
We make heavy use of the amazing `rlang` pkg functions such as `call2()`, `expr()`, `sym()`, the bang-bang operator (`!!` or even `!!!`). And of course `base` R functions such as `quote()` & `eval()` to evaluate our results into actual executable `dplyr::mutate()` code. For more information on evaluation, check out the code, see `rlang` documentation, and/or watch the `shinyNewColumns` tutorial featured in [Shiny Conf 2022](#tutorial).
## Have a question?
Please please please open a new [GitHub issue](https://github.com/AARON-CLARK/shinyNewColumns/issues)!
<br>