1 |
#' Spaghetti Plot |
|
2 |
#' |
|
3 |
#' This teal module renders the UI and calls the function |
|
4 |
#' that creates a spaghetti plot. |
|
5 |
#' |
|
6 |
#' @param label menu item label of the module in the teal app. |
|
7 |
#' @param dataname analysis data passed to the data argument of \code{\link[teal]{init}}. |
|
8 |
#' E.g. `ADaM` structured laboratory data frame `ADLB`. |
|
9 |
#' @param param_var name of variable containing biomarker codes e.g. `PARAMCD`. |
|
10 |
#' @param param biomarker selected. |
|
11 |
#' @param param_var_label single name of variable in analysis data |
|
12 |
#' that includes parameter labels. |
|
13 |
#' @param idvar name of unique subject id variable. |
|
14 |
#' @param xaxis_var single name of variable in analysis data |
|
15 |
#' that is used as x-axis in the plot for the respective goshawk function. |
|
16 |
#' @param xaxis_var_level vector that can be used to define the factor level of `xaxis_var`. |
|
17 |
#' Only use it when `xaxis_var` is character or factor. |
|
18 |
#' @param filter_var data constraint variable. |
|
19 |
#' @param yaxis_var single name of variable in analysis data that is used as |
|
20 |
#' summary variable in the respective `goshawk` function. |
|
21 |
#' @param trt_group \code{\link[teal.transform]{choices_selected}} object with available choices and pre-selected option |
|
22 |
#' for variable names representing treatment group e.g. `ARM`. |
|
23 |
#' @param trt_group_level vector that can be used to define factor |
|
24 |
#' level of `trt_group`. |
|
25 |
#' @param man_color string vector representing customized colors |
|
26 |
#' @param color_comb name or hex value for combined treatment color. |
|
27 |
#' @param xtick numeric vector to define the tick values of `x-axis` |
|
28 |
#' when x variable is numeric. Default value is `waive()`. |
|
29 |
#' @param xlabel vector with same length of `xtick` to define the |
|
30 |
#' label of `x-axis` tick values. Default value is `waive()`. |
|
31 |
#' @param rotate_xlab `logical(1)` value indicating whether to rotate `x-axis` labels |
|
32 |
#' @param facet_ncol numeric value indicating number of facets per row. |
|
33 |
#' @param free_x `logical(1)` should scales be `"fixed"` (`FALSE`) of `"free"` (`TRUE`) for `x-axis` in |
|
34 |
#' \code{\link[ggplot2]{facet_wrap}} \code{scales} parameter. |
|
35 |
#' @param plot_height controls plot height. |
|
36 |
#' @param plot_width optional, controls plot width. |
|
37 |
#' @param font_size control font size for title, `x-axis`, `y-axis` and legend font. |
|
38 |
#' @param group_stats control group mean or median overlay. |
|
39 |
#' @param hline_arb numeric vector of at most 2 values identifying intercepts for arbitrary horizontal lines. |
|
40 |
#' @param hline_arb_color a character vector of at most length of \code{hline_arb}. |
|
41 |
#' naming the color for the arbitrary horizontal lines. |
|
42 |
#' @param hline_arb_label a character vector of at most length of \code{hline_arb}. |
|
43 |
#' naming the label for the arbitrary horizontal lines. |
|
44 |
#' @param hline_vars a character vector to name the columns that will define additional horizontal lines. |
|
45 |
#' @param hline_vars_colors a character vector naming the colors for the additional horizontal lines. |
|
46 |
#' @param hline_vars_labels a character vector naming the labels for the additional horizontal lines that will appear |
|
47 |
#' in the legend. |
|
48 |
#' @inheritParams teal.widgets::standard_layout |
|
49 |
#' |
|
50 |
#' @author Wenyi Liu (luiw2) wenyi.liu@roche.com |
|
51 |
#' @author Balazs Toth (tothb2) toth.balazs@gene.com |
|
52 |
#' |
|
53 |
#' @return \code{shiny} object |
|
54 |
#' |
|
55 |
#' @export |
|
56 |
#' |
|
57 |
#' @examples |
|
58 |
#' |
|
59 |
#' # Example using ADaM structure analysis dataset. |
|
60 |
#' |
|
61 |
#' library(dplyr) |
|
62 |
#' |
|
63 |
#' # original ARM value = dose value |
|
64 |
#' arm_mapping <- list( |
|
65 |
#' "A: Drug X" = "150mg QD", |
|
66 |
#' "B: Placebo" = "Placebo", |
|
67 |
#' "C: Combination" = "Combination" |
|
68 |
#' ) |
|
69 |
#' set.seed(1) |
|
70 |
#' ADSL <- goshawk::rADSL |
|
71 |
#' ADLB <- goshawk::rADLB |
|
72 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
73 |
#' ADLB <- ADLB %>% |
|
74 |
#' dplyr::mutate( |
|
75 |
#' AVISITCD = dplyr::case_when( |
|
76 |
#' AVISIT == "SCREENING" ~ "SCR", |
|
77 |
#' AVISIT == "BASELINE" ~ "BL", |
|
78 |
#' grepl("WEEK", AVISIT) ~ paste("W", stringr::str_extract(AVISIT, "(?<=(WEEK ))[0-9]+")), |
|
79 |
#' TRUE ~ as.character(NA) |
|
80 |
#' ), |
|
81 |
#' AVISITCDN = dplyr::case_when( |
|
82 |
#' AVISITCD == "SCR" ~ -2, |
|
83 |
#' AVISITCD == "BL" ~ 0, |
|
84 |
#' grepl("W", AVISITCD) ~ as.numeric(gsub("[^0-9]*", "", AVISITCD)), |
|
85 |
#' TRUE ~ as.numeric(NA) |
|
86 |
#' ), |
|
87 |
#' AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN), |
|
88 |
#' TRTORD = dplyr::case_when( |
|
89 |
#' ARMCD == "ARM C" ~ 1, |
|
90 |
#' ARMCD == "ARM B" ~ 2, |
|
91 |
#' ARMCD == "ARM A" ~ 3 |
|
92 |
#' ), |
|
93 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
94 |
#' ARM = factor(ARM) %>% reorder(TRTORD), |
|
95 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
96 |
#' ACTARM = factor(ACTARM) %>% reorder(TRTORD), |
|
97 |
#' ANRLO = 30, |
|
98 |
#' ANRHI = 75 |
|
99 |
#' ) %>% |
|
100 |
#' dplyr::rowwise() %>% |
|
101 |
#' dplyr::group_by(PARAMCD) %>% |
|
102 |
#' dplyr::mutate(LBSTRESC = ifelse(USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
103 |
#' paste("<", round(runif(1, min = 25, max = 30))), LBSTRESC |
|
104 |
#' )) %>% |
|
105 |
#' dplyr::mutate(LBSTRESC = ifelse(USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
106 |
#' paste(">", round(runif(1, min = 70, max = 75))), LBSTRESC |
|
107 |
#' )) %>% |
|
108 |
#' ungroup() |
|
109 |
#' attr(ADLB[["ARM"]], "label") <- var_labels[["ARM"]] |
|
110 |
#' attr(ADLB[["ACTARM"]], "label") <- var_labels[["ACTARM"]] |
|
111 |
#' attr(ADLB[["ANRLO"]], "label") <- "Analysis Normal Range Lower Limit" |
|
112 |
#' attr(ADLB[["ANRHI"]], "label") <- "Analysis Normal Range Upper Limit" |
|
113 |
#' |
|
114 |
#' # add LLOQ and ULOQ variables |
|
115 |
#' ALB_LOQS <- goshawk:::h_identify_loq_values(ADLB) |
|
116 |
#' ADLB <- dplyr::left_join(ADLB, ALB_LOQS, by = "PARAM") |
|
117 |
#' |
|
118 |
#' app <- teal::init( |
|
119 |
#' data = teal.data::cdisc_data( |
|
120 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- goshawk::rADSL"), |
|
121 |
#' teal.data::cdisc_dataset( |
|
122 |
#' "ADLB", |
|
123 |
#' ADLB, |
|
124 |
#' code = "set.seed(1) |
|
125 |
#' ADLB <- goshawk::rADLB |
|
126 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
127 |
#' ADLB <- ADLB %>% |
|
128 |
#' dplyr::mutate(AVISITCD = dplyr::case_when( |
|
129 |
#' AVISIT == 'SCREENING' ~ 'SCR', |
|
130 |
#' AVISIT == 'BASELINE' ~ 'BL', |
|
131 |
#' grepl('WEEK', AVISIT) ~ |
|
132 |
#' paste('W', stringr::str_extract(AVISIT, '(?<=(WEEK ))[0-9]+')), |
|
133 |
#' TRUE ~ as.character(NA)), |
|
134 |
#' AVISITCDN = dplyr::case_when( |
|
135 |
#' AVISITCD == 'SCR' ~ -2, |
|
136 |
#' AVISITCD == 'BL' ~ 0, |
|
137 |
#' grepl('W', AVISITCD) ~ as.numeric(gsub('[^0-9]*', '', AVISITCD)), |
|
138 |
#' TRUE ~ as.numeric(NA)), |
|
139 |
#' AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN), |
|
140 |
#' TRTORD = dplyr::case_when( |
|
141 |
#' ARMCD == 'ARM C' ~ 1, |
|
142 |
#' ARMCD == 'ARM B' ~ 2, |
|
143 |
#' ARMCD == 'ARM A' ~ 3), |
|
144 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
145 |
#' ARM = factor(ARM) %>% reorder(TRTORD), |
|
146 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
147 |
#' ACTARM = factor(ACTARM) %>% reorder(TRTORD), |
|
148 |
#' ANRLO = 30, |
|
149 |
#' ANRHI = 75) %>% |
|
150 |
#' dplyr::rowwise() %>% |
|
151 |
#' dplyr::group_by(PARAMCD) %>% |
|
152 |
#' dplyr::mutate(LBSTRESC = ifelse(USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
153 |
#' paste('<', round(runif(1, min = 25, max = 30))), LBSTRESC)) %>% |
|
154 |
#' dplyr::mutate(LBSTRESC = ifelse(USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
155 |
#' paste( '>', round(runif(1, min = 70, max = 75))), LBSTRESC)) %>% |
|
156 |
#' ungroup |
|
157 |
#' attr(ADLB[['ARM']], 'label') <- var_labels[['ARM']] |
|
158 |
#' attr(ADLB[['ACTARM']], 'label') <- var_labels[['ACTARM']] |
|
159 |
#' attr(ADLB[['ANRLO']], 'label') <- 'Analysis Normal Range Lower Limit' |
|
160 |
#' attr(ADLB[['ANRHI']], 'label') <- 'Analysis Normal Range Upper Limit' |
|
161 |
#' ALB_LOQS <- goshawk:::h_identify_loq_values(ADLB) |
|
162 |
#' ADLB <- left_join(ADLB, ALB_LOQS, by = 'PARAM')", |
|
163 |
#' vars = list(arm_mapping = arm_mapping) |
|
164 |
#' ), |
|
165 |
#' check = FALSE |
|
166 |
#' ), |
|
167 |
#' modules = teal::modules( |
|
168 |
#' teal.goshawk::tm_g_gh_spaghettiplot( |
|
169 |
#' label = "Spaghetti Plot", |
|
170 |
#' dataname = "ADLB", |
|
171 |
#' param_var = "PARAMCD", |
|
172 |
#' param = choices_selected(c("ALT", "CRP", "IGA"), "ALT"), |
|
173 |
#' idvar = "USUBJID", |
|
174 |
#' xaxis_var = choices_selected(c("Analysis Visit Code" = "AVISITCD"), "AVISITCD"), |
|
175 |
#' yaxis_var = choices_selected(c("AVAL", "CHG", "PCHG"), "AVAL"), |
|
176 |
#' filter_var = choices_selected( |
|
177 |
#' c("None" = "NONE", "Screening" = "BASE2", "Baseline" = "BASE"), |
|
178 |
#' "NONE" |
|
179 |
#' ), |
|
180 |
#' trt_group = choices_selected(c("ARM", "ACTARM"), "ARM"), |
|
181 |
#' color_comb = "#39ff14", |
|
182 |
#' man_color = c( |
|
183 |
#' "Combination" = "#000000", |
|
184 |
#' "Placebo" = "#fce300", |
|
185 |
#' "150mg QD" = "#5a2f5f" |
|
186 |
#' ), |
|
187 |
#' hline_arb = c(60, 50), |
|
188 |
#' hline_arb_color = c("grey", "red"), |
|
189 |
#' hline_arb_label = c("default A", "default B"), |
|
190 |
#' hline_vars = c("ANRHI", "ANRLO", "ULOQN", "LLOQN"), |
|
191 |
#' hline_vars_colors = c("pink", "brown", "purple", "black"), |
|
192 |
#' ) |
|
193 |
#' ) |
|
194 |
#' ) |
|
195 |
#' if (interactive()) { |
|
196 |
#' shinyApp(app$ui, app$server) |
|
197 |
#' } |
|
198 |
#' |
|
199 |
tm_g_gh_spaghettiplot <- function(label, |
|
200 |
dataname, |
|
201 |
param_var, |
|
202 |
param, |
|
203 |
param_var_label = "PARAM", |
|
204 |
idvar, |
|
205 |
xaxis_var, |
|
206 |
yaxis_var, |
|
207 |
xaxis_var_level = NULL, |
|
208 |
filter_var = yaxis_var, |
|
209 |
trt_group, |
|
210 |
trt_group_level = NULL, |
|
211 |
group_stats = "NONE", |
|
212 |
man_color = NULL, |
|
213 |
color_comb = NULL, |
|
214 |
xtick = ggplot2::waiver(), |
|
215 |
xlabel = xtick, |
|
216 |
rotate_xlab = FALSE, |
|
217 |
facet_ncol = 2, |
|
218 |
free_x = FALSE, |
|
219 |
plot_height = c(600, 200, 2000), |
|
220 |
plot_width = NULL, |
|
221 |
font_size = c(12, 8, 20), |
|
222 |
hline_arb = numeric(0), |
|
223 |
hline_arb_color = "red", |
|
224 |
hline_arb_label = "Horizontal line", |
|
225 |
hline_vars = character(0), |
|
226 |
hline_vars_colors = "green", |
|
227 |
hline_vars_labels = hline_vars, |
|
228 |
pre_output = NULL, |
|
229 |
post_output = NULL) { |
|
230 | ! |
logger::log_info("Initializing tm_g_gh_spaghettiplot") |
231 | ! |
checkmate::assert_class(param, "choices_selected") |
232 | ! |
checkmate::assert_class(xaxis_var, "choices_selected") |
233 | ! |
checkmate::assert_class(yaxis_var, "choices_selected") |
234 | ! |
checkmate::assert_class(trt_group, "choices_selected") |
235 | ! |
validate_line_arb_arg(hline_arb, hline_arb_color, hline_arb_label) |
236 | ! |
validate_line_vars_arg(hline_vars, hline_vars_colors, hline_vars_labels) |
237 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
238 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
239 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
240 | ! |
checkmate::assert_numeric(plot_width[1], |
241 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, |
242 | ! |
.var.name = "plot_width" |
243 |
) |
|
244 | ! |
checkmate::assert_flag(free_x) |
245 | ||
246 | ! |
args <- as.list(environment()) |
247 | ||
248 | ! |
module( |
249 | ! |
label = label, |
250 | ! |
server = srv_g_spaghettiplot, |
251 | ! |
server_args = list( |
252 | ! |
dataname = dataname, |
253 | ! |
idvar = idvar, |
254 | ! |
param_var = param_var, |
255 | ! |
trt_group = trt_group, |
256 | ! |
xaxis_var_level = xaxis_var_level, |
257 | ! |
trt_group_level = trt_group_level, |
258 | ! |
man_color = man_color, |
259 | ! |
color_comb = color_comb, |
260 | ! |
param_var_label = param_var_label, |
261 | ! |
xtick = xtick, |
262 | ! |
xlabel = xlabel, |
263 | ! |
plot_height = plot_height, |
264 | ! |
plot_width = plot_width, |
265 | ! |
hline_vars_colors = hline_vars_colors, |
266 | ! |
hline_vars_labels = hline_vars_labels |
267 |
), |
|
268 | ! |
ui = g_ui_spaghettiplot, |
269 | ! |
ui_args = args, |
270 | ! |
datanames = dataname |
271 |
) |
|
272 |
} |
|
273 | ||
274 |
g_ui_spaghettiplot <- function(id, ...) { |
|
275 | ! |
ns <- NS(id) |
276 | ! |
a <- list(...) |
277 | ||
278 | ! |
shiny::tagList( |
279 | ! |
include_css_files("custom"), |
280 | ! |
teal.widgets::standard_layout( |
281 | ! |
output = templ_ui_output_datatable(ns), |
282 | ! |
encoding = div( |
283 |
### Reporter |
|
284 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")), |
285 |
### |
|
286 | ! |
templ_ui_dataname(a$dataname), |
287 | ! |
teal.widgets::optionalSelectInput( |
288 | ! |
ns("trt_group"), |
289 | ! |
label = "Select Treatment Variable", |
290 | ! |
choices = a$trt_group$choices, |
291 | ! |
selected = a$trt_group$selected, |
292 | ! |
multiple = FALSE |
293 |
), |
|
294 | ! |
templ_ui_params_vars( |
295 | ! |
ns, |
296 |
# xparam and yparam are identical, so we only show the user one |
|
297 | ! |
xparam_choices = a$param$choices, xparam_selected = a$param$selected, xparam_label = "Select a Biomarker", |
298 | ! |
xchoices = a$xaxis_var$choices, xselected = a$xaxis_var$selected, |
299 | ! |
ychoices = a$yaxis_var$choices, yselected = a$yaxis_var$selected |
300 |
), |
|
301 | ! |
radioButtons( |
302 | ! |
ns("group_stats"), |
303 | ! |
"Group Statistics", |
304 | ! |
c("None" = "NONE", "Mean" = "MEAN", "Median" = "MEDIAN"), |
305 | ! |
inline = TRUE |
306 |
), |
|
307 | ! |
templ_ui_constraint(ns), # required by constr_anl_q |
308 | ! |
if (length(a$hline_vars) > 0) { |
309 | ! |
teal.widgets::optionalSelectInput( |
310 | ! |
ns("hline_vars"), |
311 | ! |
label = "Add Horizontal Range Line(s):", |
312 | ! |
choices = a$hline_vars, |
313 | ! |
selected = NULL, |
314 | ! |
multiple = TRUE |
315 |
) |
|
316 |
}, |
|
317 | ! |
ui_arbitrary_lines(id = ns("hline_arb"), a$hline_arb, a$hline_arb_label, a$hline_arb_color), |
318 | ! |
teal.widgets::panel_group( |
319 | ! |
teal.widgets::panel_item( |
320 | ! |
title = "Plot Aesthetic Settings", |
321 | ! |
div( |
322 | ! |
toggle_slider_ui( |
323 | ! |
ns("yrange_scale"), |
324 | ! |
label = "Y-Axis Range Zoom", |
325 | ! |
min = -1000000, |
326 | ! |
max = 1000000, |
327 | ! |
value = c(-1000000, 1000000) |
328 |
), |
|
329 | ! |
tags$div( |
330 | ! |
class = "flex flex-wrap items-center", |
331 | ! |
tags$div( |
332 | ! |
class = "mr-1", |
333 | ! |
tags$span(tags$strong("Number of Plots Per Row:")) |
334 |
), |
|
335 | ! |
tags$div( |
336 | ! |
class = "w-65px", |
337 | ! |
numericInput(ns("facet_ncol"), "", a$facet_ncol, min = 1) |
338 |
) |
|
339 |
) |
|
340 |
), |
|
341 | ! |
checkboxInput(ns("free_x"), "Free X-Axis Scales", a$free_x), |
342 | ! |
checkboxInput(ns("rotate_xlab"), "Rotate X-Axis Label", a$rotate_xlab), |
343 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("font_size"), "Font Size", a$font_size, ticks = FALSE), |
344 | ! |
teal.widgets::optionalSliderInputValMinMax( |
345 | ! |
ns("alpha"), |
346 | ! |
"Line Alpha", |
347 | ! |
a$alpha, |
348 | ! |
value_min_max = c(0.8, 0.0, 1.0), step = 0.1, ticks = FALSE |
349 |
) |
|
350 |
) |
|
351 |
) |
|
352 |
), |
|
353 | ! |
forms = tagList( |
354 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"), |
355 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code") |
356 |
), |
|
357 | ! |
pre_output = a$pre_output, |
358 | ! |
post_output = a$post_output |
359 |
) |
|
360 |
) |
|
361 |
} |
|
362 | ||
363 | ||
364 | ||
365 |
srv_g_spaghettiplot <- function(id, |
|
366 |
data, |
|
367 |
reporter, |
|
368 |
filter_panel_api, |
|
369 |
dataname, |
|
370 |
idvar, |
|
371 |
param_var, |
|
372 |
trt_group, |
|
373 |
man_color, |
|
374 |
color_comb, |
|
375 |
xaxis_var_level, |
|
376 |
trt_group_level, |
|
377 |
param_var_label, |
|
378 |
xtick, |
|
379 |
xlabel, |
|
380 |
plot_height, |
|
381 |
plot_width, |
|
382 |
hline_vars_colors, |
|
383 |
hline_vars_labels) { |
|
384 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
385 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
386 | ! |
checkmate::assert_class(data, "tdata") |
387 | ||
388 | ! |
moduleServer(id, function(input, output, session) { |
389 |
# reused in all modules |
|
390 | ! |
anl_q_output <- constr_anl_q( |
391 | ! |
session, input, data, dataname, |
392 | ! |
param_id = "xaxis_param", param_var = param_var, trt_group = input$trt_group, min_rows = 1 |
393 |
) |
|
394 | ||
395 | ! |
anl_q <- anl_q_output()$value |
396 | ||
397 |
# update sliders for axes taking constraints into account |
|
398 | ! |
yrange_slider <- toggle_slider_server("yrange_scale") |
399 | ! |
keep_range_slider_updated(session, input, yrange_slider$update_state, "yaxis_var", "xaxis_param", anl_q) |
400 | ! |
keep_data_const_opts_updated(session, input, anl_q, "xaxis_param") |
401 | ||
402 | ! |
horizontal_line <- srv_arbitrary_lines("hline_arb") |
403 | ||
404 | ! |
iv_r <- reactive({ |
405 | ! |
iv <- shinyvalidate::InputValidator$new() |
406 | ||
407 | ! |
iv$add_rule("xaxis_param", shinyvalidate::sv_required("Please select a biomarker")) |
408 | ! |
iv$add_rule("trt_group", shinyvalidate::sv_required("Please select a treatment variable")) |
409 | ! |
iv$add_rule("xaxis_var", shinyvalidate::sv_required("Please select an X-Axis variable")) |
410 | ! |
iv$add_rule("yaxis_var", shinyvalidate::sv_required("Please select a Y-Axis variable")) |
411 | ! |
iv$add_rule("facet_ncol", plots_per_row_validate_rules()) |
412 | ||
413 | ! |
iv$add_validator(horizontal_line()$iv_r()) |
414 | ! |
iv$add_validator(anl_q_output()$iv_r()) |
415 | ! |
iv$enable() |
416 | ! |
iv |
417 |
}) |
|
418 | ||
419 | ||
420 | ! |
plot_q <- reactive({ |
421 | ! |
teal::validate_inputs(iv_r()) |
422 | ! |
req(anl_q()) |
423 |
# nolint start |
|
424 | ! |
ylim <- yrange_slider$state()$value |
425 | ! |
facet_ncol <- input$facet_ncol |
426 | ! |
facet_scales <- ifelse(input$free_x, "free_x", "fixed") |
427 | ||
428 | ! |
rotate_xlab <- input$rotate_xlab |
429 | ! |
hline_arb <- horizontal_line()$line_arb |
430 | ! |
hline_arb_label <- horizontal_line()$line_arb_label |
431 | ! |
hline_arb_color <- horizontal_line()$line_arb_color |
432 | ! |
group_stats <- input$group_stats |
433 | ! |
font_size <- input$font_size |
434 | ! |
alpha <- input$alpha |
435 | ! |
validate(need(input$trt_group, "Please select a treatment variable")) |
436 | ! |
trt_group <- input$trt_group |
437 | ||
438 |
# Below inputs should trigger plot via updates of other reactive objects (i.e. anl_q()) and some inputs |
|
439 | ! |
param <- input$xaxis_param |
440 | ! |
xaxis_var <- input$xaxis_var |
441 | ! |
yaxis_var <- input$yaxis_var |
442 | ! |
hline_vars <- input$hline_vars |
443 |
# nolint end |
|
444 | ||
445 | ! |
private_qenv <- anl_q()$qenv |
446 | ||
447 |
# this code is needed to make sure the waiver attribute |
|
448 |
# of ggplot2::waiver is correctly passed to goshawk's spaghettiplot |
|
449 | ! |
if (!methods::is(xtick, "waiver")) { |
450 | ! |
private_qenv <- teal.code::eval_code( |
451 | ! |
object = private_qenv, |
452 | ! |
code = bquote(xtick <- .(xtick)) |
453 |
) |
|
454 |
} else { |
|
455 | ! |
private_qenv <- teal.code::eval_code( |
456 | ! |
object = private_qenv, |
457 | ! |
code = quote(xtick <- ggplot2::waiver()) |
458 |
) |
|
459 |
} |
|
460 | ||
461 | ! |
if (!methods::is(xlabel, "waiver")) { |
462 | ! |
private_qenv <- teal.code::eval_code( |
463 | ! |
object = private_qenv, |
464 | ! |
code = bquote(xlabel <- .(xlabel)) |
465 |
) |
|
466 |
} else { |
|
467 | ! |
private_qenv <- teal.code::eval_code( |
468 | ! |
object = private_qenv, |
469 | ! |
code = quote(xlabel <- ggplot2::waiver()) |
470 |
) |
|
471 |
} |
|
472 | ||
473 | ! |
teal.code::eval_code( |
474 | ! |
object = private_qenv, |
475 | ! |
code = bquote({ |
476 | ! |
p <- goshawk::g_spaghettiplot( |
477 | ! |
data = ANL, |
478 | ! |
subj_id = .(idvar), |
479 | ! |
biomarker_var = .(param_var), |
480 | ! |
biomarker_var_label = .(param_var_label), |
481 | ! |
biomarker = .(param), |
482 | ! |
value_var = .(yaxis_var), |
483 | ! |
trt_group = .(trt_group), |
484 | ! |
trt_group_level = .(trt_group_level), |
485 | ! |
time = .(xaxis_var), |
486 | ! |
time_level = .(xaxis_var_level), |
487 | ! |
color_manual = .(man_color), |
488 | ! |
color_comb = .(color_comb), |
489 | ! |
ylim = .(ylim), |
490 | ! |
facet_ncol = .(facet_ncol), |
491 | ! |
facet_scales = .(facet_scales), |
492 | ! |
hline_arb = .(hline_arb), |
493 | ! |
hline_arb_label = .(hline_arb_label), |
494 | ! |
hline_arb_color = .(hline_arb_color), |
495 | ! |
xtick = xtick, |
496 | ! |
xlabel = xlabel, |
497 | ! |
rotate_xlab = .(rotate_xlab), |
498 | ! |
font_size = .(font_size), |
499 | ! |
alpha = .(alpha), |
500 | ! |
group_stats = .(group_stats), |
501 | ! |
hline_vars = .(hline_vars), |
502 | ! |
hline_vars_colors = .(hline_vars_colors[seq_along(hline_vars)]), |
503 | ! |
hline_vars_labels = .(hline_vars_labels[seq_along(hline_vars)]) |
504 |
) |
|
505 | ! |
print(p) |
506 |
}) |
|
507 |
) |
|
508 |
}) |
|
509 | ||
510 | ! |
plot_r <- reactive({ |
511 | ! |
plot_q()[["p"]] |
512 |
}) |
|
513 | ||
514 | ! |
plot_data <- teal.widgets::plot_with_settings_srv( |
515 | ! |
id = "plot", |
516 | ! |
plot_r = plot_r, |
517 | ! |
height = plot_height, |
518 | ! |
width = plot_width, |
519 | ! |
brushing = TRUE |
520 |
) |
|
521 | ||
522 |
### REPORTER |
|
523 | ! |
if (with_reporter) { |
524 | ! |
card_fun <- function(comment) { |
525 | ! |
card <- teal::TealReportCard$new() |
526 | ! |
card$set_name("Spaghetti Plot") |
527 | ! |
card$append_text("Spaghetti Plot", "header2") |
528 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
529 | ! |
card$append_text("Selected Options", "header3") |
530 | ! |
card$append_text( |
531 | ! |
formatted_data_constraint(input$constraint_var, input$constraint_range_min, input$constraint_range_max) |
532 |
) |
|
533 | ! |
card$append_text("Spaghetti Plot", "header3") |
534 | ! |
card$append_plot(plot_r(), dim = plot_data$dim()) |
535 | ! |
if (!comment == "") { |
536 | ! |
card$append_text("Comment", "header3") |
537 | ! |
card$append_text(comment) |
538 |
} |
|
539 | ! |
card$append_src(paste(teal.code::get_code(plot_q()), collapse = "\n")) |
540 | ! |
card |
541 |
} |
|
542 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) |
543 |
} |
|
544 |
### |
|
545 | ||
546 | ! |
output$brush_data <- DT::renderDataTable({ |
547 | ! |
plot_brush <- plot_data$brush() |
548 | ||
549 | ! |
ANL <- isolate(anl_q()$ANL) # nolint |
550 | ! |
validate_has_data(ANL, 1) |
551 | ||
552 | ! |
xvar <- isolate(input$xaxis_var) |
553 | ! |
yvar <- isolate(input$yaxis_var) |
554 | ! |
trt_group <- isolate(input$trt_group) |
555 | ||
556 | ! |
req(all(c(xvar, yvar) %in% names(ANL))) |
557 | ||
558 | ! |
df <- teal.widgets::clean_brushedPoints( |
559 | ! |
dplyr::select( |
560 | ! |
ANL, "USUBJID", dplyr::all_of(trt_group), "PARAMCD", |
561 | ! |
dplyr::all_of(c(xvar, yvar)), "LOQFL" |
562 |
), |
|
563 | ! |
plot_brush |
564 |
) |
|
565 | ! |
df <- df[order(df$PARAMCD, df[[trt_group]], df$USUBJID, df[[xvar]]), ] |
566 | ! |
numeric_cols <- names(dplyr::select_if(df, is.numeric)) |
567 | ||
568 | ! |
DT::datatable(df, rownames = FALSE, options = list(scrollX = TRUE)) %>% |
569 | ! |
DT::formatRound(numeric_cols, 4) |
570 |
}) |
|
571 | ||
572 | ! |
teal.widgets::verbatim_popup_srv( |
573 | ! |
id = "warning", |
574 | ! |
verbatim_content = reactive(teal.code::get_warnings(plot_q())), |
575 | ! |
title = "Warning", |
576 | ! |
disabled = reactive(is.null(teal.code::get_warnings(plot_q()))) |
577 |
) |
|
578 | ||
579 | ! |
teal.widgets::verbatim_popup_srv( |
580 | ! |
id = "rcode", |
581 | ! |
verbatim_content = reactive(teal.code::get_code(plot_q())), |
582 | ! |
title = "Show R Code for Spaghetti Plot" |
583 |
) |
|
584 |
}) |
|
585 |
} |
1 |
templ_ui_constraint <- function(ns, label = "Data Constraint") { |
|
2 | ! |
div( |
3 | ! |
id = ns("constraint_var_whole"), # give an id to hide it |
4 | ! |
radioButtons(ns("constraint_var"), label, choices = "NONE"), |
5 | ! |
shinyjs::hidden(div( |
6 | ! |
id = ns("constraint_range"), |
7 | ! |
div( |
8 | ! |
class = "inline-block;", |
9 | ! |
numericInput(ns("constraint_range_min"), label = "Min", value = 0, min = 0, max = 0) |
10 |
), |
|
11 | ! |
div( |
12 | ! |
class = "inline-block;", |
13 | ! |
numericInput(ns("constraint_range_max"), label = "Min", value = 0, min = 0, max = 0) |
14 |
) |
|
15 |
)), |
|
16 | ! |
shinyjs::hidden(div( |
17 | ! |
id = ns("all_na"), |
18 | ! |
helpText("All values are missing (NA)") |
19 |
)) |
|
20 |
) |
|
21 |
} |
|
22 | ||
23 |
keep_data_const_opts_updated <- function(session, input, data, id_param_var) { |
|
24 |
# use reactiveVal so that it only updates when options actually changed and not just data |
|
25 | ! |
choices <- reactiveVal() |
26 | ! |
observeEvent(data(), { |
27 | ! |
paramname <- input[[id_param_var]] |
28 | ! |
req(length(paramname) == 1) |
29 | ||
30 | ! |
data_filtered <- data()$ANL %>% dplyr::filter(.data$PARAMCD == paramname) |
31 | ! |
choices(c("None" = "NONE", "Screening" = "BASE2", "Baseline" = "BASE")[ |
32 | ! |
c(TRUE, !all(is.na(data_filtered[["BASE2"]])), !all(is.na(data_filtered[["BASE"]]))) |
33 |
]) |
|
34 |
}) |
|
35 | ! |
observeEvent(choices(), { |
36 | ! |
updateRadioButtons(session, "constraint_var", choices = choices()) |
37 |
# hide when only one option "NONE" |
|
38 | ! |
if (length(choices()) == 1) { |
39 | ! |
shinyjs::hide("constraint_var_whole") |
40 |
} else { |
|
41 | ! |
shinyjs::show("constraint_var_whole") |
42 |
} |
|
43 |
}) |
|
44 |
} |
|
45 | ||
46 | ||
47 | ||
48 | ||
49 |
# param_id: input id that contains values of PARAMCD to filter for |
|
50 |
# param_var: currently only "PARAMCD" is supported |
|
51 |
constr_anl_q <- function(session, input, data, dataname, param_id, param_var, trt_group, min_rows) { |
|
52 | ! |
dataset_var <- dataname |
53 | ! |
if (!identical(param_var, "PARAMCD")) { |
54 |
# why is there a variable param_id which is provided to this function and always equal to "param"? |
|
55 | ! |
stop("param_var must be 'PARAMCD'. Otherwise, we cannot currently guarantee the correctness of the code.") |
56 |
} |
|
57 | ||
58 | ! |
anl_param <- reactive({ |
59 | ! |
param_var_value <- input[[param_id]] # value to filter PARAMCD for |
60 | ! |
validate(need(param_var_value, "Please select a biomarker")) |
61 | ! |
checkmate::assert_string(param_var_value) |
62 | ||
63 | ! |
ANL <- data[[dataname]]() # nolint |
64 | ! |
validate_has_data(ANL, min_rows) |
65 | ||
66 | ! |
validate_has_variable(ANL, param_var) |
67 | ! |
validate_has_variable(ANL, "AVISITCD") |
68 | ! |
validate_has_variable(ANL, "BASE") |
69 | ! |
validate_has_variable(ANL, "BASE2") |
70 | ! |
validate_has_variable(ANL, trt_group) |
71 | ||
72 |
# analysis |
|
73 | ! |
private_qenv <- teal.code::new_qenv(tdata2env(data), code = teal::get_code_tdata(data)) %>% |
74 | ! |
teal.code::eval_code( |
75 | ! |
substitute(ANL <- dataname, list(dataname = as.name(dataname))) # nolint |
76 |
) %>% |
|
77 | ! |
teal.code::eval_code( |
78 | ! |
code = bquote({ |
79 | ! |
ANL <- .(as.name(dataset_var)) %>% # nolint |
80 | ! |
dplyr::filter(.(as.name(param_var)) == .(param_var_value)) |
81 |
}) |
|
82 |
) |
|
83 | ! |
validate_has_data(private_qenv[["ANL"]], min_rows) |
84 | ! |
list(ANL = ANL, qenv = private_qenv) |
85 |
}) |
|
86 | ||
87 | ! |
observe({ |
88 | ! |
param_var_value <- input[[param_id]] |
89 | ! |
validate(need(param_var_value, "Please select a biomarker")) |
90 | ||
91 | ! |
constraint_var <- input[["constraint_var"]] |
92 | ! |
validate(need(constraint_var, "select a constraint variable")) |
93 | ||
94 | ||
95 | ! |
ANL <- data[[dataname]]() # nolint |
96 | ! |
validate_has_data(ANL, min_rows) |
97 | ||
98 | ! |
validate_has_variable(ANL, param_var) |
99 | ! |
validate_has_variable(ANL, "AVISITCD") |
100 | ! |
validate_has_variable(ANL, "BASE") |
101 | ! |
validate_has_variable(ANL, "BASE2") |
102 | ||
103 | ! |
ANL <- ANL %>% dplyr::filter(!!sym(param_var) == param_var_value) # nolint |
104 | ||
105 | ! |
visit_freq <- unique(ANL$AVISITCD) |
106 | ||
107 |
# get min max values |
|
108 | ! |
if ((constraint_var == "BASE2" && any(grepl("SCR", visit_freq))) || |
109 | ! |
(constraint_var == "BASE" && any(grepl("BL", visit_freq)))) { |
110 | ! |
val <- stats::na.omit(switch(constraint_var, |
111 | ! |
"BASE" = ANL$BASE[ANL$AVISITCD == "BL"], |
112 | ! |
"BASE2" = ANL$BASE2[ANL$AVISITCD == "SCR"], |
113 | ! |
stop(paste(constraint_var, "not allowed")) |
114 |
)) |
|
115 | ||
116 | ! |
if (length(val) == 0 || all(is.na(val))) { |
117 | ! |
shinyjs::show("all_na") |
118 | ! |
shinyjs::hide("constraint_range") |
119 | ! |
args <- list( |
120 | ! |
min = list(label = "Min", min = 0, max = 0, value = 0), |
121 | ! |
max = list(label = "Max", min = 0, max = 0, value = 0) |
122 |
) |
|
123 | ! |
update_min_max(session, args) |
124 |
} else { |
|
125 | ! |
rng <- range(val, na.rm = TRUE) |
126 | ||
127 | ! |
minmax <- c(floor(rng[1] * 1000) / 1000, ceiling(rng[2] * 1000) / 1000) |
128 | ||
129 | ! |
label_min <- sprintf("Min (%s)", minmax[1]) |
130 | ! |
label_max <- sprintf("Max (%s)", minmax[2]) |
131 | ||
132 | ! |
args <- list( |
133 | ! |
min = list(label = label_min, min = minmax[1], max = minmax[2], value = minmax[1]), |
134 | ! |
max = list(label = label_max, min = minmax[1], max = minmax[2], value = minmax[2]) |
135 |
) |
|
136 | ||
137 | ! |
update_min_max(session, args) |
138 | ! |
shinyjs::show("constraint_range") # update before show |
139 | ! |
shinyjs::hide("all_na") |
140 |
} |
|
141 | ! |
} else if (constraint_var == "NONE") { |
142 | ! |
shinyjs::hide("constraint_range") # hide before update |
143 | ! |
shinyjs::hide("all_na") |
144 | ||
145 |
# force update (and thus refresh) on different constraint_var -> pass unique value for each constraint_var name |
|
146 | ! |
args <- list( |
147 | ! |
min = list(label = "Min", min = 0, max = 0, value = 0), |
148 | ! |
max = list(label = "Max", min = 0, max = 0, value = 0) |
149 |
) |
|
150 | ||
151 | ! |
update_min_max(session, args) |
152 |
} else { |
|
153 | ! |
validate(need(FALSE, "This is an invalid data contraint for the filtered data")) |
154 |
} |
|
155 |
}) |
|
156 | ||
157 | ! |
anl_constraint <- create_anl_constraint_reactive(anl_param, input, param_id = param_id, min_rows = min_rows) |
158 | ||
159 | ! |
return(anl_constraint) |
160 |
} |
|
161 | ||
162 |
# returns a reactive that applies the `x-axis data constraint` |
|
163 |
# More precisely, all patients are filtered out that do not have the range of |
|
164 |
# `param_id.constraint_var` in the specified range |
|
165 |
# constraint var means that `param_id.constraint_var` is constrained to the filtered range (or NA), |
|
166 |
# e.g. `ALT.BASE2` (i.e. `PARAMCD = ALT & range_filter_on(BASE2)`) |
|
167 |
create_anl_constraint_reactive <- function(anl_param, input, param_id, min_rows) { |
|
168 | ||
169 | ! |
iv_r <- reactive({ |
170 | ! |
iv <- shinyvalidate::InputValidator$new() |
171 | ! |
iv$condition(~ isTRUE(input$constraint_var != "NONE")) |
172 | ! |
iv$add_rule("constraint_range_min", shinyvalidate::sv_required("A contraint minimum value is required")) |
173 | ! |
iv$add_rule("constraint_range_max", shinyvalidate::sv_required("A contraint maximum value is required")) |
174 | ! |
iv$add_rule( |
175 | ! |
"constraint_range_min", |
176 | ! |
~ if (!is.na(input$constraint_range_max) && (.) > input$constraint_range_max) |
177 | ! |
"constraint min needs to be less than max" |
178 |
) |
|
179 | ! |
iv$add_rule( |
180 | ! |
"constraint_range_max", |
181 | ! |
~ if (!is.na(input$constraint_range_min) && (.) < input$constraint_range_min) |
182 | ! |
"constraint min needs to be less than max" |
183 |
) |
|
184 | ! |
iv |
185 |
}) |
|
186 | ||
187 | ||
188 | ! |
return_val <- reactive({ |
189 | ! |
private_qenv <- anl_param()$qenv |
190 | ||
191 |
# it is assumed that constraint_var is triggering constraint_range which then trigger this clause |
|
192 | ! |
constraint_var <- isolate(input[["constraint_var"]]) |
193 | ! |
constraint_range_min <- input[["constraint_range_min"]] |
194 | ! |
constraint_range_max <- input[["constraint_range_max"]] |
195 | ! |
param <- input[[param_id]] |
196 | ! |
req(param) |
197 | ||
198 |
# filter constraint |
|
199 | ! |
if (constraint_var != "NONE") { |
200 | ! |
private_qenv <- teal.code::eval_code( |
201 | ! |
object = private_qenv, |
202 | ! |
code = bquote({ |
203 |
# the below includes patients who have at least one non-NA BASE value |
|
204 |
# ideally, a patient should either have all NA values or none at all |
|
205 |
# this could be achieved through preprocessing; otherwise, this is easily overseen |
|
206 | ! |
filtered_usubjids <- ANL %>% # nolint |
207 | ! |
dplyr::filter( |
208 | ! |
PARAMCD == .(param), |
209 | ! |
(.(constraint_range_min) <= .data[[.(constraint_var)]]) & |
210 | ! |
(.data[[.(constraint_var)]] <= .(constraint_range_max)) |
211 |
) %>% |
|
212 | ! |
dplyr::pull(USUBJID) |
213 |
# include patients with all NA values for constraint_var |
|
214 | ! |
filtered_usubjids <- c( |
215 | ! |
filtered_usubjids, |
216 | ! |
ANL %>% |
217 | ! |
dplyr::filter(PARAMCD == .(param)) %>% |
218 | ! |
dplyr::group_by(USUBJID) %>% |
219 | ! |
dplyr::summarize(all_na = all(is.na(.data[[.(constraint_var)]]))) %>% |
220 | ! |
dplyr::filter(all_na) %>% |
221 | ! |
dplyr::pull(USUBJID) |
222 |
) |
|
223 | ! |
ANL <- ANL %>% dplyr::filter(USUBJID %in% filtered_usubjids) # nolint |
224 |
}) |
|
225 |
) |
|
226 | ! |
validate_has_data(private_qenv[["ANL"]], min_rows) |
227 |
} |
|
228 | ! |
list(ANL = private_qenv[["ANL"]], qenv = private_qenv) |
229 |
}) |
|
230 | ||
231 | ! |
reactive( |
232 | ! |
list( |
233 | ! |
value = return_val, |
234 | ! |
iv_r = iv_r |
235 |
) |
|
236 |
) |
|
237 |
} |
|
238 | ||
239 |
# for outputting the constaint in the report |
|
240 |
formatted_data_constraint <- function(constraint_var, constraint_range_min, constraint_range_max) { |
|
241 | ! |
constraint_var_label <- switch(constraint_var, |
242 | ! |
"BASE2" = "Screening", |
243 | ! |
"BASE" = "Baseline", |
244 | ! |
"None" |
245 |
) |
|
246 | ! |
msg <- paste("Data Constraint:", constraint_var_label) |
247 | ! |
if (constraint_var_label != "None") { |
248 | ! |
msg <- paste(msg, "from", constraint_range_min, "to", constraint_range_max) |
249 |
} |
|
250 | ! |
msg |
251 |
} |
|
252 | ||
253 |
update_min_max <- function(session, args) { |
|
254 | ! |
do.call("updateNumericInput", c(list(session = session, inputId = "constraint_range_min"), args$min)) |
255 | ! |
do.call("updateNumericInput", c(list(session = session, inputId = "constraint_range_max"), args$max)) |
256 |
} |
1 |
#' Line plot |
|
2 |
#' |
|
3 |
#' This teal module renders the UI and calls the function that creates a line plot. |
|
4 |
#' |
|
5 |
#' @inheritParams teal.widgets::standard_layout |
|
6 |
#' @param label menu item label of the module in the teal app. |
|
7 |
#' @param dataname analysis data passed to the data argument of \code{\link[teal]{init}}. E.g. `ADaM` structured |
|
8 |
#' laboratory data frame `ADLB`. |
|
9 |
#' @param param_var name of variable containing biomarker codes e.g. `PARAMCD`. |
|
10 |
#' @param param biomarker selected. |
|
11 |
#' @param param_var_label single name of variable in analysis data that includes parameter labels. |
|
12 |
#' @param xaxis_var single name of variable in analysis data that is used as x-axis in the plot for the |
|
13 |
#' respective `goshawk` function. |
|
14 |
#' @param xvar_level vector that can be used to define the factor level of `xvar`. Only use it when |
|
15 |
#' `xvar` is character or factor. |
|
16 |
#' @param filter_var data constraint variable. |
|
17 |
#' @param filter_var_choices data constraint variable choices. |
|
18 |
#' @param yaxis_var single name of variable in analysis data that is used as summary variable in the |
|
19 |
#' respective `goshawk` function. |
|
20 |
#' @param trt_group \code{\link[teal.transform]{choices_selected}} object with available choices and pre-selected option |
|
21 |
#' for variable names representing treatment group e.g. `ARM`. |
|
22 |
#' @param trt_group_level vector that can be used to define factor level of `trt_group`. |
|
23 |
#' @param shape_choices Vector or \code{choices_selected} object with names of `ADSL` variables which |
|
24 |
#' can be used to change shape |
|
25 |
#' @param color_manual string vector representing customized colors |
|
26 |
#' @param stat string of statistics |
|
27 |
#' @param hline_arb numeric vector of at most 2 values identifying intercepts for arbitrary horizontal lines. |
|
28 |
#' @param hline_arb_color a character vector of at most length of \code{hline_arb}. |
|
29 |
#' naming the color for the arbitrary horizontal lines. |
|
30 |
#' @param hline_arb_label a character vector of at most length of \code{hline_arb}. |
|
31 |
#' naming the label for the arbitrary horizontal lines. |
|
32 |
#' @param xtick numeric vector to define the tick values of x-axis when x variable is numeric. |
|
33 |
#' Default value is waive(). |
|
34 |
#' @param xlabel vector with same length of `xtick` to define the label of x-axis tick values. |
|
35 |
#' Default value is waive(). |
|
36 |
#' @param rotate_xlab `logical(1)` value indicating whether to rotate `x-axis` labels. |
|
37 |
#' @param plot_height controls plot height. |
|
38 |
#' @param plot_width optional, controls plot width. |
|
39 |
#' @param plot_font_size control font size for title, `x-axis`, `y-axis` and legend font. |
|
40 |
#' @param dodge controls the position dodge of error bar |
|
41 |
#' @param count_threshold minimum count of observations (as listed in the output table) to plot |
|
42 |
#' nodes on the graph |
|
43 |
#' @param table_font_size controls the font size of values in the table |
|
44 |
#' @param plot_relative_height_value numeric value between 500 and 5000 for controlling the starting value |
|
45 |
#' of the relative plot height slider |
|
46 |
#' @author Wenyi Liu (luiw2) wenyi.liu@roche.com |
|
47 |
#' @author Balazs Toth (tothb2) toth.balazs@gene.com |
|
48 |
#' |
|
49 |
#' @return \code{shiny} object |
|
50 |
#' |
|
51 |
#' @export |
|
52 |
#' |
|
53 |
#' @examples |
|
54 |
#' |
|
55 |
#' # Example using ADaM structure analysis dataset. |
|
56 |
#' |
|
57 |
#' library(dplyr) |
|
58 |
#' library(stringr) |
|
59 |
#' library(nestcolor) |
|
60 |
#' |
|
61 |
#' # original ARM value = dose value |
|
62 |
#' arm_mapping <- list( |
|
63 |
#' "A: Drug X" = "150mg QD", |
|
64 |
#' "B: Placebo" = "Placebo", |
|
65 |
#' "C: Combination" = "Combination" |
|
66 |
#' ) |
|
67 |
#' |
|
68 |
#' ADSL <- goshawk::rADSL |
|
69 |
#' ADLB <- goshawk::rADLB |
|
70 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
71 |
#' ADLB <- ADLB %>% |
|
72 |
#' dplyr::mutate( |
|
73 |
#' AVISITCD = dplyr::case_when( |
|
74 |
#' AVISIT == "SCREENING" ~ "SCR", |
|
75 |
#' AVISIT == "BASELINE" ~ "BL", |
|
76 |
#' grepl("WEEK", AVISIT) ~ paste("W", stringr::str_extract(AVISIT, "(?<=(WEEK ))[0-9]+")), |
|
77 |
#' TRUE ~ as.character(NA) |
|
78 |
#' ), |
|
79 |
#' AVISITCDN = dplyr::case_when( |
|
80 |
#' AVISITCD == "SCR" ~ -2, |
|
81 |
#' AVISITCD == "BL" ~ 0, |
|
82 |
#' grepl("W", AVISITCD) ~ as.numeric(gsub("[^0-9]*", "", AVISITCD)), |
|
83 |
#' TRUE ~ as.numeric(NA) |
|
84 |
#' ), |
|
85 |
#' AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN), |
|
86 |
#' TRTORD = dplyr::case_when( |
|
87 |
#' ARMCD == "ARM C" ~ 1, |
|
88 |
#' ARMCD == "ARM B" ~ 2, |
|
89 |
#' ARMCD == "ARM A" ~ 3 |
|
90 |
#' ), |
|
91 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
92 |
#' ARM = factor(ARM) %>% reorder(TRTORD), |
|
93 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
94 |
#' ACTARM = factor(ACTARM) %>% reorder(TRTORD) |
|
95 |
#' ) |
|
96 |
#' attr(ADLB[["ARM"]], "label") <- var_labels[["ARM"]] |
|
97 |
#' attr(ADLB[["ACTARM"]], "label") <- var_labels[["ACTARM"]] |
|
98 |
#' |
|
99 |
#' app <- teal::init( |
|
100 |
#' data = teal.data::cdisc_data( |
|
101 |
#' adsl <- teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- goshawk::rADSL"), |
|
102 |
#' teal.data::cdisc_dataset("ADLB", ADLB, |
|
103 |
#' code = "ADLB <- goshawk::rADLB |
|
104 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
105 |
#' ADLB <- ADLB %>% |
|
106 |
#' dplyr::mutate(AVISITCD = dplyr::case_when( |
|
107 |
#' AVISIT == 'SCREENING' ~ 'SCR', |
|
108 |
#' AVISIT == 'BASELINE' ~ 'BL', |
|
109 |
#' grepl('WEEK', AVISIT) ~ |
|
110 |
#' paste('W', stringr::str_extract(AVISIT, '(?<=(WEEK ))[0-9]+')), |
|
111 |
#' TRUE ~ as.character(NA)), |
|
112 |
#' AVISITCDN = dplyr::case_when( |
|
113 |
#' AVISITCD == 'SCR' ~ -2, |
|
114 |
#' AVISITCD == 'BL' ~ 0, |
|
115 |
#' grepl('W', AVISITCD) ~ as.numeric(gsub('[^0-9]*', '', AVISITCD)), |
|
116 |
#' TRUE ~ as.numeric(NA)), |
|
117 |
#' AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN), |
|
118 |
#' TRTORD = dplyr::case_when( |
|
119 |
#' ARMCD == 'ARM C' ~ 1, |
|
120 |
#' ARMCD == 'ARM B' ~ 2, |
|
121 |
#' ARMCD == 'ARM A' ~ 3), |
|
122 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
123 |
#' ARM = factor(ARM) %>% reorder(TRTORD), |
|
124 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
125 |
#' ACTARM = factor(ACTARM) %>% reorder(TRTORD)) |
|
126 |
#' attr(ADLB[['ARM']], 'label') <- var_labels[['ARM']] |
|
127 |
#' attr(ADLB[['ACTARM']], 'label') <- var_labels[['ACTARM']]", |
|
128 |
#' vars = list(ADSL = adsl, arm_mapping = arm_mapping) |
|
129 |
#' ), |
|
130 |
#' check = TRUE |
|
131 |
#' ), |
|
132 |
#' modules = teal::modules( |
|
133 |
#' teal.goshawk::tm_g_gh_lineplot( |
|
134 |
#' label = "Line Plot", |
|
135 |
#' dataname = "ADLB", |
|
136 |
#' param_var = "PARAMCD", |
|
137 |
#' param = choices_selected(c("ALT", "CRP", "IGA"), "ALT"), |
|
138 |
#' shape_choices = c("SEX", "RACE"), |
|
139 |
#' xaxis_var = choices_selected("AVISITCD", "AVISITCD"), |
|
140 |
#' yaxis_var = choices_selected(c("AVAL", "BASE", "CHG", "PCHG"), "AVAL"), |
|
141 |
#' trt_group = choices_selected(c("ARM", "ACTARM"), "ARM"), |
|
142 |
#' hline_arb = c(20.5, 19.5), |
|
143 |
#' hline_arb_color = c("red", "green"), |
|
144 |
#' hline_arb_label = c("A", "B") |
|
145 |
#' ) |
|
146 |
#' ) |
|
147 |
#' ) |
|
148 |
#' if (interactive()) { |
|
149 |
#' shinyApp(app$ui, app$server) |
|
150 |
#' } |
|
151 |
tm_g_gh_lineplot <- function(label, |
|
152 |
dataname, |
|
153 |
param_var, |
|
154 |
param, |
|
155 |
param_var_label = "PARAM", |
|
156 |
xaxis_var, |
|
157 |
yaxis_var, |
|
158 |
xvar_level = NULL, |
|
159 |
filter_var = yaxis_var, |
|
160 |
filter_var_choices = filter_var, |
|
161 |
trt_group, |
|
162 |
trt_group_level = NULL, |
|
163 |
shape_choices = NULL, |
|
164 |
stat = "mean", |
|
165 |
hline_arb = numeric(0), |
|
166 |
hline_arb_color = "red", |
|
167 |
hline_arb_label = "Horizontal line", |
|
168 |
color_manual = c( |
|
169 |
getOption("ggplot2.discrete.colour"), |
|
170 |
c("#ff0000", "#008000", "#4ca3dd", "#8a2be2") |
|
171 |
)[1:4], |
|
172 |
xtick = ggplot2::waiver(), |
|
173 |
xlabel = xtick, |
|
174 |
rotate_xlab = FALSE, |
|
175 |
plot_height = c(600, 200, 4000), |
|
176 |
plot_width = NULL, |
|
177 |
plot_font_size = c(12, 8, 20), |
|
178 |
dodge = c(0.4, 0, 1), |
|
179 |
pre_output = NULL, |
|
180 |
post_output = NULL, |
|
181 |
count_threshold = 0, |
|
182 |
table_font_size = c(12, 4, 20), |
|
183 |
plot_relative_height_value = 1000) { |
|
184 | ! |
logger::log_info("Initializing tm_g_gh_lineplot") |
185 | ! |
checkmate::assert_class(param, "choices_selected") |
186 | ! |
checkmate::assert_class(xaxis_var, "choices_selected") |
187 | ! |
checkmate::assert_class(yaxis_var, "choices_selected") |
188 | ! |
checkmate::assert_class(trt_group, "choices_selected") |
189 | ! |
checkmate::assert_flag(rotate_xlab) |
190 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
191 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
192 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
193 | ! |
checkmate::assert_numeric( |
194 | ! |
plot_width[1], |
195 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
196 |
) |
|
197 | ! |
checkmate::assert_numeric(table_font_size, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
198 | ! |
checkmate::assert_numeric( |
199 | ! |
table_font_size[1], |
200 | ! |
lower = table_font_size[2], upper = table_font_size[3], |
201 | ! |
null.ok = TRUE, .var.name = "table_font_size" |
202 |
) |
|
203 | ||
204 | ! |
checkmate::assert_number(plot_relative_height_value, lower = 500, upper = 5000) |
205 | ||
206 | ! |
checkmate::assert_number(count_threshold) |
207 | ! |
validate_line_arb_arg(hline_arb, hline_arb_color, hline_arb_label) |
208 | ! |
args <- as.list(environment()) |
209 | ||
210 | ! |
module( |
211 | ! |
label = label, |
212 | ! |
server = srv_lineplot, |
213 | ! |
server_args = list( |
214 | ! |
dataname = dataname, |
215 | ! |
param_var = param_var, |
216 | ! |
trt_group = trt_group, |
217 | ! |
color_manual = color_manual, |
218 | ! |
xvar_level = xvar_level, |
219 | ! |
trt_group_level = trt_group_level, |
220 | ! |
shape_choices = shape_choices, |
221 | ! |
param_var_label = param_var_label, |
222 | ! |
xtick = xtick, |
223 | ! |
xlabel = xlabel, |
224 | ! |
plot_height = plot_height, |
225 | ! |
plot_width = plot_width |
226 |
), |
|
227 | ! |
ui = ui_lineplot, |
228 | ! |
ui_args = args, |
229 | ! |
datanames = dataname |
230 |
) |
|
231 |
} |
|
232 | ||
233 |
ui_lineplot <- function(id, ...) { |
|
234 | ! |
ns <- NS(id) |
235 | ! |
a <- list(...) |
236 | ||
237 | ! |
shiny::tagList( |
238 | ! |
include_css_files("custom"), |
239 | ! |
teal.widgets::standard_layout( |
240 | ! |
output = teal.widgets::plot_with_settings_ui(id = ns("plot")), |
241 | ! |
encoding = div( |
242 |
### Reporter |
|
243 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")), |
244 |
### |
|
245 | ! |
templ_ui_dataname(a$dataname), |
246 | ! |
teal.widgets::optionalSelectInput( |
247 | ! |
ns("trt_group"), |
248 | ! |
label = "Select Treatment Variable", |
249 | ! |
choices = a$trt_group$choices, |
250 | ! |
selected = a$trt_group$selected, |
251 | ! |
multiple = FALSE |
252 |
), |
|
253 | ! |
templ_ui_params_vars( |
254 | ! |
ns, |
255 |
# xparam and yparam are identical, so we only show the user one |
|
256 | ! |
xparam_choices = a$param$choices, xparam_selected = a$param$selected, xparam_label = "Select a Biomarker", |
257 | ! |
xchoices = a$xaxis_var$choices, xselected = a$xaxis_var$selected, |
258 | ! |
ychoices = a$yaxis_var$choices, yselected = a$yaxis_var$selected |
259 |
), |
|
260 | ! |
uiOutput(ns("shape_ui")), |
261 | ! |
radioButtons(ns("stat"), "Select a Statistic:", c("mean", "median"), a$stat), |
262 | ! |
checkboxInput(ns("include_stat"), "Include Statistic Table", value = TRUE), |
263 | ! |
div( |
264 | ! |
sliderInput( |
265 | ! |
ns("relative_height"), |
266 | ! |
div( |
267 | ! |
"Relative height of plot to table(s)", |
268 | ! |
title = |
269 | ! |
paste( |
270 | ! |
"The larger the value selected the greater the size of the plot relative\nto", |
271 | ! |
"the size of the tables. Note the units of this slider are arbitrary.\nTo", |
272 | ! |
"change the total size of the plot and table(s)\nuse", |
273 | ! |
"the plot resizing controls available at the top right of the plot." |
274 |
), |
|
275 | ! |
icon("circle-info") |
276 |
), |
|
277 | ! |
min = 500, |
278 | ! |
max = 5000, |
279 | ! |
step = 50, |
280 | ! |
value = a$plot_relative_height_value, |
281 | ! |
ticks = FALSE |
282 |
), |
|
283 |
), |
|
284 | ! |
templ_ui_constraint(ns), # required by constr_anl_q |
285 | ! |
ui_arbitrary_lines(id = ns("hline_arb"), a$hline_arb, a$hline_arb_label, a$hline_arb_color), |
286 | ! |
teal.widgets::panel_group( |
287 | ! |
teal.widgets::panel_item( |
288 | ! |
title = "Plot Aesthetic Settings", |
289 | ! |
toggle_slider_ui( |
290 | ! |
ns("yrange_scale"), |
291 | ! |
label = "Y-Axis Range Zoom", |
292 | ! |
min = -1000000, |
293 | ! |
max = 1000000, |
294 | ! |
value = c(-1000000, 1000000) |
295 |
), |
|
296 | ! |
checkboxInput(ns("rotate_xlab"), "Rotate X-axis Label", a$rotate_xlab), |
297 | ! |
numericInput(ns("count_threshold"), "Contributing Observations Threshold:", a$count_threshold) |
298 |
), |
|
299 | ! |
teal.widgets::panel_item( |
300 | ! |
title = "Plot settings", |
301 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("dodge"), "Error Bar Position Dodge", a$dodge, ticks = FALSE), |
302 | ! |
teal.widgets::panel_group( |
303 | ! |
teal.widgets::panel_item( |
304 | ! |
title = "Line Settings", |
305 | ! |
uiOutput(ns("lines")) |
306 |
), |
|
307 | ! |
teal.widgets::panel_item( |
308 | ! |
title = "Symbol settings", |
309 | ! |
uiOutput(ns("symbols")) |
310 |
) |
|
311 |
), |
|
312 | ! |
teal.widgets::optionalSliderInputValMinMax( |
313 | ! |
ns("plot_font_size"), |
314 | ! |
"Font Size", |
315 | ! |
a$plot_font_size, |
316 | ! |
ticks = FALSE |
317 |
) |
|
318 |
), |
|
319 | ! |
teal.widgets::panel_item( |
320 | ! |
title = "Table settings", |
321 | ! |
teal.widgets::optionalSliderInputValMinMax( |
322 | ! |
ns("table_font_size"), |
323 | ! |
"Table Font Size", |
324 | ! |
a$table_font_size, |
325 | ! |
ticks = FALSE |
326 |
) |
|
327 |
) |
|
328 |
) |
|
329 |
), |
|
330 | ! |
forms = tagList( |
331 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"), |
332 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code") |
333 |
), |
|
334 | ! |
pre_output = a$pre_output, |
335 | ! |
post_output = a$post_output |
336 |
) |
|
337 |
) |
|
338 |
} |
|
339 | ||
340 |
srv_lineplot <- function(id, |
|
341 |
data, |
|
342 |
reporter, |
|
343 |
filter_panel_api, |
|
344 |
dataname, |
|
345 |
param_var, |
|
346 |
trt_group, |
|
347 |
color_manual, |
|
348 |
xvar_level, |
|
349 |
trt_group_level, |
|
350 |
shape_choices, |
|
351 |
param_var_label, |
|
352 |
xtick, |
|
353 |
xlabel, |
|
354 |
plot_height, |
|
355 |
plot_width) { |
|
356 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
357 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
358 | ! |
checkmate::assert_class(data, "tdata") |
359 | ||
360 | ! |
moduleServer(id, function(input, output, session) { |
361 | ! |
ns <- session$ns |
362 | ! |
output$shape_ui <- renderUI({ |
363 | ! |
if (!is.null(shape_choices)) { |
364 | ! |
if (methods::is(shape_choices, "choices_selected")) { |
365 | ! |
choices <- shape_choices$choices |
366 | ! |
selected <- shape_choices$selected |
367 |
} else { |
|
368 | ! |
choices <- shape_choices |
369 | ! |
selected <- NULL |
370 |
} |
|
371 | ! |
teal.widgets::optionalSelectInput( |
372 | ! |
ns("shape"), |
373 | ! |
"Select Line Splitting Variable", |
374 | ! |
choices = choices, selected = selected |
375 |
) |
|
376 |
} |
|
377 |
}) |
|
378 | ||
379 | ! |
anl_q_output <- constr_anl_q( |
380 | ! |
session = session, |
381 | ! |
input = input, |
382 | ! |
data = data, |
383 | ! |
dataname = dataname, |
384 | ! |
param_id = "xaxis_param", |
385 | ! |
param_var = param_var, |
386 | ! |
trt_group = input$trt_group, |
387 | ! |
min_rows = 2 |
388 |
) |
|
389 | ||
390 | ! |
anl_q <- anl_q_output()$value |
391 | ||
392 | ! |
keep_data_const_opts_updated(session, input, anl_q, "xaxis_param") |
393 | ||
394 | ! |
yrange_slider <- toggle_slider_server("yrange_scale") |
395 | ||
396 | ! |
horizontal_line <- srv_arbitrary_lines("hline_arb") |
397 | ||
398 | ! |
iv_r <- reactive({ |
399 | ! |
iv <- shinyvalidate::InputValidator$new() |
400 | ! |
iv$add_rule("xaxis_param", shinyvalidate::sv_required("Please select a biomarker")) |
401 | ! |
iv$add_rule("trt_group", shinyvalidate::sv_required("Please select a treatment variable")) |
402 | ! |
iv$add_rule("xaxis_var", shinyvalidate::sv_required("Please select an X-Axis variable")) |
403 | ! |
iv$add_rule("yaxis_var", shinyvalidate::sv_required("Please select a Y-Axis variable")) |
404 | ||
405 | ! |
iv$add_validator(horizontal_line()$iv_r()) |
406 | ! |
iv$add_validator(anl_q_output()$iv_r()) |
407 | ! |
iv$enable() |
408 | ! |
iv |
409 |
}) |
|
410 | ||
411 | ||
412 |
# update sliders for axes |
|
413 | ! |
observe({ |
414 | ! |
varname <- input[["yaxis_var"]] |
415 | ! |
validate(need(varname, "Please select variable")) |
416 | ||
417 | ! |
ANL <- anl_q()$ANL # nolint |
418 | ! |
validate_has_variable(ANL, varname, paste("variable", varname, "does not exist")) |
419 | ||
420 | ! |
shape <- if (!(is.null(input$shape) || input$shape == "None")) { |
421 | ! |
input$shape |
422 |
} else { |
|
423 | ! |
NULL |
424 |
} |
|
425 | ||
426 |
# we don't need to additionally filter for paramvar here as in keep_range_slider_updated because |
|
427 |
# xaxis_var and yaxis_var are always distinct |
|
428 | ! |
sum_data <- ANL %>% |
429 | ! |
dplyr::group_by_at(c(input$xaxis_var, input$trt_group, shape)) %>% |
430 | ! |
dplyr::summarise( |
431 | ! |
upper = if (input$stat == "mean") { |
432 | ! |
mean(!!sym(varname), na.rm = TRUE) + |
433 | ! |
1.96 * stats::sd(!!sym(varname), na.rm = TRUE) / sqrt(dplyr::n()) |
434 |
} else { |
|
435 | ! |
stats::quantile(!!sym(varname), 0.75, na.rm = TRUE) |
436 |
}, |
|
437 | ! |
lower = if (input$stat == "mean") { |
438 | ! |
mean(!!sym(varname), na.rm = TRUE) - |
439 | ! |
1.96 * stats::sd(!!sym(varname), na.rm = TRUE) / sqrt(dplyr::n()) |
440 |
} else { |
|
441 | ! |
stats::quantile(!!sym(varname), 0.25, na.rm = TRUE) |
442 |
} |
|
443 |
) |
|
444 | ||
445 | ! |
minmax <- grDevices::extendrange( |
446 | ! |
r = c( |
447 | ! |
floor(min(sum_data$lower, na.rm = TRUE) * 10) / 10, |
448 | ! |
ceiling(max(sum_data$upper, na.rm = TRUE) * 10) / 10 |
449 |
), |
|
450 | ! |
f = 0.05 |
451 |
) |
|
452 | ||
453 |
# we don't use keep_range_slider_updated because this module computes the min, max |
|
454 |
# not from the constrained ANL, but rather by first grouping and computing confidence |
|
455 |
# intervals |
|
456 | ! |
isolate(yrange_slider$update_state( |
457 | ! |
min = minmax[[1]], |
458 | ! |
max = minmax[[2]], |
459 | ! |
value = minmax |
460 |
)) |
|
461 |
}) |
|
462 | ||
463 | ! |
line_color_defaults <- reactiveVal(color_manual) |
464 | ||
465 | ! |
line_type_defaults <- reactiveVal("solid") |
466 | ||
467 | ! |
observeEvent(input$trt_group, { |
468 | ! |
req(anl_q()) |
469 | ! |
req(input$trt_group) |
470 | ! |
anl_arm <- anl_q()$ANL[[input$trt_group]] |
471 | ! |
anl_arm_nlevels <- nlevels(anl_arm) |
472 | ||
473 | ! |
if (is.null(names(line_color_defaults()))) { |
474 |
# if color_manual did not specify arms (i.e. didn't have names) then order |
|
475 |
# of the vector does not need to match order of level(anl_arm) |
|
476 | ! |
line_color_to_set <- line_color_defaults()[seq_len(anl_arm_nlevels)] |
477 |
} else { |
|
478 |
# if color_manual did specify arms then we need to make sure the order of |
|
479 |
# line_color_to_set matches the order of level(anl_arm) and if any arms are invalid |
|
480 |
# or missing then we fill with a random colour |
|
481 | ! |
line_color_to_set <- stats::setNames(line_color_defaults()[levels(anl_arm)], nm = levels(anl_arm)) |
482 |
} |
|
483 | ! |
line_color_to_set[is.na(line_color_to_set)] <- grDevices::rainbow(anl_arm_nlevels)[is.na(line_color_to_set)] |
484 | ! |
line_color_defaults(line_color_to_set) |
485 | ||
486 | ! |
line_type_to_set <- if (length(line_type_defaults()) <= anl_arm_nlevels) { |
487 | ! |
c(line_type_defaults(), rep(line_type_defaults(), anl_arm_nlevels - length(line_type_defaults()))) |
488 |
} else { |
|
489 | ! |
line_type_defaults()[seq_len(anl_arm_nlevels)] |
490 |
} |
|
491 | ||
492 | ! |
line_type_defaults(line_type_to_set) |
493 |
}) |
|
494 | ||
495 | ! |
line_color_selected <- reactive({ |
496 | ! |
req(anl_q()) |
497 | ! |
if (is.null(input$trt_group)) { |
498 | ! |
return(NULL) |
499 |
} |
|
500 | ! |
anl_arm <- isolate(anl_q()$ANL[[input$trt_group]]) |
501 | ! |
anl_arm_nlevels <- nlevels(anl_arm) |
502 | ! |
anl_arm_levels <- levels(anl_arm) |
503 | ||
504 | ! |
stats::setNames( |
505 | ! |
vapply( |
506 | ! |
seq_len(anl_arm_nlevels), |
507 | ! |
function(idx) { |
508 | ! |
x <- input[[paste0("line_color_", idx)]] |
509 | ! |
if (is.null(x)) isolate(line_color_defaults())[[idx]] else x |
510 |
}, |
|
511 | ! |
character(1) |
512 |
), |
|
513 | ! |
anl_arm_levels |
514 |
) |
|
515 |
}) |
|
516 | ||
517 | ! |
line_type_selected <- reactive({ |
518 | ! |
req(anl_q()) |
519 | ! |
if (is.null(input$trt_group)) { |
520 | ! |
return(NULL) |
521 |
} |
|
522 | ! |
anl_arm <- isolate(anl_q()$ANL[[input$trt_group]]) |
523 | ! |
anl_arm_nlevels <- nlevels(anl_arm) |
524 | ! |
anl_arm_levels <- levels(anl_arm) |
525 | ||
526 | ! |
stats::setNames( |
527 | ! |
vapply( |
528 | ! |
seq_len(anl_arm_nlevels), |
529 | ! |
function(idx) { |
530 | ! |
x <- input[[paste0("line_type_", idx)]] |
531 | ! |
if (is.null(x)) isolate(line_type_defaults())[[idx]] else x |
532 |
}, |
|
533 | ! |
character(1) |
534 |
), |
|
535 | ! |
anl_arm_levels |
536 |
) |
|
537 |
}) |
|
538 | ||
539 | ! |
output$lines <- renderUI({ |
540 | ! |
req(anl_q()) |
541 | ! |
req(input$trt_group) |
542 | ! |
anl_arm <- isolate(anl_q()$ANL[[input$trt_group]]) |
543 | ! |
anl_arm_nlevels <- nlevels(anl_arm) |
544 | ! |
anl_arm_levels <- levels(anl_arm) |
545 | ! |
color_def <- line_color_defaults() |
546 | ! |
type_def <- line_type_defaults() |
547 | ! |
tagList( |
548 | ! |
lapply( |
549 | ! |
seq_len(anl_arm_nlevels), |
550 | ! |
function(idx) { |
551 | ! |
x <- anl_arm_levels[[idx]] |
552 | ! |
x_color <- color_def[[idx]] |
553 | ! |
color_input <- colourpicker::colourInput( |
554 | ! |
ns(paste0("line_color_", idx)), |
555 | ! |
"Color:", |
556 | ! |
x_color |
557 |
) |
|
558 | ! |
x_type <- type_def[[idx]] |
559 | ! |
type_input <- selectInput( |
560 | ! |
ns(paste0("line_type_", idx)), |
561 | ! |
"Type:", |
562 | ! |
choices = c( |
563 | ! |
"blank", |
564 | ! |
"solid", |
565 | ! |
"dashed", |
566 | ! |
"dotted", |
567 | ! |
"dotdash", |
568 | ! |
"longdash", |
569 | ! |
"twodash", |
570 | ! |
"1F", |
571 | ! |
"F1", |
572 | ! |
"4C88C488", |
573 | ! |
"12345678" |
574 |
), |
|
575 | ! |
selected = x_type |
576 |
) |
|
577 | ! |
div( |
578 | ! |
tags$label("Line configuration for:", tags$code(x)), |
579 | ! |
div( |
580 | ! |
class = "flex", |
581 | ! |
div( |
582 | ! |
class = "flex-grow-1", |
583 | ! |
color_input |
584 |
), |
|
585 | ! |
div( |
586 | ! |
class = "flex-grow-1", |
587 | ! |
type_input |
588 |
) |
|
589 |
) |
|
590 |
) |
|
591 |
} |
|
592 |
) |
|
593 |
) |
|
594 |
}) |
|
595 | ||
596 | ||
597 | ! |
symbol_type_start <- c( |
598 | ! |
"circle", |
599 | ! |
"square", |
600 | ! |
"diamond", |
601 | ! |
"triangle", |
602 | ! |
"circle open", |
603 | ! |
"square open", |
604 | ! |
"diamond open", |
605 | ! |
"triangle open", |
606 | ! |
"triangle down open", |
607 | ! |
"circle cross", |
608 | ! |
"square cross", |
609 | ! |
"circle plus", |
610 | ! |
"square plus", |
611 | ! |
"diamond plus", |
612 | ! |
"square triangle", |
613 | ! |
"plus", |
614 | ! |
"cross", |
615 | ! |
"asterisk" |
616 |
) |
|
617 | ! |
symbol_type_defaults <- reactiveVal(symbol_type_start) |
618 | ||
619 |
# reset shapes when different splitting variable is selected |
|
620 | ! |
observeEvent( |
621 | ! |
eventExpr = input$shape, |
622 | ! |
handlerExpr = symbol_type_defaults(symbol_type_start), |
623 | ! |
ignoreNULL = TRUE |
624 |
) |
|
625 | ||
626 | ! |
observe({ |
627 | ! |
req(input$shape) |
628 | ! |
req(anl_q()) |
629 | ! |
anl_shape <- anl_q()$ANL[[input$shape]] |
630 | ! |
anl_shape_nlevels <- nlevels(anl_shape) |
631 | ! |
symbol_type_to_set <- symbol_type_defaults()[pmin(length(symbol_type_defaults()), seq_len(anl_shape_nlevels))] |
632 | ! |
symbol_type_defaults(symbol_type_to_set) |
633 |
}) |
|
634 | ||
635 | ! |
symbol_type_selected <- reactive({ |
636 | ! |
req(anl_q()) |
637 | ! |
if (is.null(input$shape)) { |
638 | ! |
return(NULL) |
639 |
} |
|
640 | ! |
anl_shape <- isolate(anl_q()$ANL[[input$shape]]) |
641 | ! |
anl_shape_nlevels <- nlevels(anl_shape) |
642 | ! |
anl_shape_levels <- levels(anl_shape) |
643 | ||
644 | ! |
stats::setNames( |
645 | ! |
vapply( |
646 | ! |
seq_len(anl_shape_nlevels), |
647 | ! |
function(idx) { |
648 | ! |
x <- input[[paste0("symbol_type_", idx)]] |
649 | ! |
if (is.null(x)) isolate(symbol_type_defaults())[[idx]] else x |
650 |
}, |
|
651 | ! |
character(1) |
652 |
), |
|
653 | ! |
anl_shape_levels |
654 |
) |
|
655 |
}) |
|
656 | ||
657 | ! |
output$symbols <- renderUI({ |
658 | ! |
req(symbol_type_defaults()) |
659 | ! |
validate(need(input$shape, "Please select line splitting variable first.")) |
660 | ||
661 | ! |
anl_shape <- isolate(anl_q()$ANL[[input$shape]]) |
662 | ! |
validate(need(is.factor(anl_shape), "Line splitting variable must be a factor.")) |
663 | ||
664 | ! |
anl_shape_nlevels <- nlevels(anl_shape) |
665 | ! |
anl_shape_levels <- levels(anl_shape) |
666 | ! |
symbol_def <- symbol_type_defaults() |
667 | ||
668 | ! |
tagList( |
669 | ! |
lapply( |
670 | ! |
seq_len(anl_shape_nlevels), |
671 | ! |
function(idx) { |
672 | ! |
x <- anl_shape_levels[[idx]] |
673 | ! |
x_color <- symbol_def[[idx]] |
674 | ! |
selectInput( |
675 | ! |
ns(paste0("symbol_type_", idx)), |
676 | ! |
HTML(paste0("Symbol for: ", tags$code(x))), |
677 | ! |
choices = symbol_type_start, |
678 | ! |
selected = x_color |
679 |
) |
|
680 |
} |
|
681 |
) |
|
682 |
) |
|
683 |
}) |
|
684 | ||
685 | ! |
plot_q <- reactive({ |
686 | ! |
teal::validate_inputs(iv_r()) |
687 | ! |
req(anl_q(), line_color_selected(), line_type_selected()) |
688 |
# nolint start |
|
689 | ||
690 | ! |
ylim <- yrange_slider$state()$value |
691 | ! |
plot_font_size <- input$plot_font_size |
692 | ! |
dodge <- input$dodge |
693 | ! |
rotate_xlab <- input$rotate_xlab |
694 | ! |
count_threshold <- `if`(is.na(as.numeric(input$count_threshold)), 0, as.numeric(input$count_threshold)) |
695 | ! |
table_font_size <- input$table_font_size |
696 | ||
697 | ! |
median <- ifelse(input$stat == "median", TRUE, FALSE) |
698 | ! |
relative_height <- input$relative_height |
699 | ! |
trt_group <- input$trt_group |
700 | ! |
color_selected <- line_color_selected() |
701 | ! |
type_selected <- line_type_selected() |
702 | ! |
symbol_selected <- symbol_type_selected() |
703 | ! |
include_stat <- input$include_stat |
704 | ||
705 | ! |
param <- input$xaxis_param |
706 | ! |
xaxis <- input$xaxis_var |
707 | ! |
yaxis <- input$yaxis_var |
708 |
# nolint end |
|
709 | ||
710 | ! |
shape <- if (!(is.null(input$shape) || input$shape == "None")) { |
711 | ! |
input$shape |
712 |
} else { |
|
713 | ! |
NULL |
714 |
} |
|
715 | ||
716 | ! |
validate( |
717 | ! |
need( |
718 | ! |
nrow(anl_q()$ANL[stats::complete.cases(anl_q()$ANL[, c(yaxis, xaxis)]), ]) >= 2, |
719 | ! |
"Number of complete rows on x and y axis variables is less than 2" |
720 |
) |
|
721 |
) |
|
722 | ||
723 | ! |
private_qenv <- anl_q()$qenv |
724 | ||
725 | ! |
if (!methods::is(xtick, "waiver") && !is.null(xtick)) { |
726 | ! |
private_qenv <- teal.code::eval_code( |
727 | ! |
object = private_qenv, |
728 | ! |
code = bquote({ |
729 | ! |
keep_index <- which(.(xtick) %in% ANL[[.(xaxis)]]) |
730 | ! |
xtick <- (.(xtick))[keep_index] # extra parentheses needed for edge case, e.g. 1:5[keep_index] |
731 | ! |
xlabel <- (.(xlabel))[keep_index] |
732 |
}) |
|
733 |
) |
|
734 | ! |
} else if (methods::is(xtick, "waiver")) { |
735 | ! |
private_qenv <- teal.code::eval_code( |
736 | ! |
object = private_qenv, |
737 | ! |
code = " |
738 | ! |
xtick <- ggplot2::waiver() |
739 | ! |
xlabel <- ggplot2::waiver() |
740 |
" |
|
741 |
) |
|
742 |
} |
|
743 | ||
744 | ! |
hline_arb <- horizontal_line()$line_arb |
745 | ! |
hline_arb_label <- horizontal_line()$line_arb_label |
746 | ! |
hline_arb_color <- horizontal_line()$line_arb_color |
747 | ||
748 | ! |
teal.code::eval_code( |
749 | ! |
object = private_qenv, |
750 | ! |
code = bquote({ |
751 | ! |
p <- goshawk::g_lineplot( |
752 | ! |
data = ANL[stats::complete.cases(ANL[, c(.(yaxis), .(xaxis))]), ], |
753 | ! |
biomarker_var = .(param_var), |
754 | ! |
biomarker_var_label = .(param_var_label), |
755 | ! |
biomarker = .(param), |
756 | ! |
value_var = .(yaxis), |
757 | ! |
ylim = .(ylim), |
758 | ! |
trt_group = .(trt_group), |
759 | ! |
trt_group_level = .(trt_group_level), |
760 | ! |
shape = .(shape), |
761 | ! |
shape_type = .(symbol_selected), |
762 | ! |
time = .(xaxis), |
763 | ! |
time_level = .(xvar_level), |
764 | ! |
color_manual = .(color_selected), |
765 | ! |
line_type = .(type_selected), |
766 | ! |
median = .(median), |
767 | ! |
hline_arb = .(hline_arb), |
768 | ! |
hline_arb_label = .(hline_arb_label), |
769 | ! |
hline_arb_color = .(hline_arb_color), |
770 | ! |
xtick = .(if (!is.null(xtick)) quote(xtick) else xtick), |
771 | ! |
xlabel = .(if (!is.null(xtick)) quote(xlabel) else xlabel), |
772 | ! |
rotate_xlab = .(rotate_xlab), |
773 | ! |
plot_height = .(relative_height), # in g_lineplot this is relative height of plot to table |
774 | ! |
plot_font_size = .(plot_font_size), |
775 | ! |
dodge = .(dodge), |
776 | ! |
count_threshold = .(count_threshold), |
777 | ! |
table_font_size = .(table_font_size), |
778 | ! |
display_center_tbl = .(include_stat) |
779 |
) |
|
780 | ! |
print(p) |
781 |
}) |
|
782 |
) |
|
783 |
}) |
|
784 | ||
785 | ! |
plot_r <- reactive(plot_q()[["p"]]) |
786 | ||
787 | ! |
plot_data <- teal.widgets::plot_with_settings_srv( |
788 | ! |
id = "plot", |
789 | ! |
plot_r = plot_r, |
790 | ! |
height = plot_height, |
791 | ! |
width = plot_width, |
792 |
) |
|
793 | ||
794 |
### REPORTER |
|
795 | ! |
if (with_reporter) { |
796 | ! |
card_fun <- function(comment) { |
797 | ! |
card <- teal::TealReportCard$new() |
798 | ! |
card$set_name("Line Plot") |
799 | ! |
card$append_text("Line Plot", "header2") |
800 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
801 | ! |
card$append_text("Selected Options", "header3") |
802 | ! |
card$append_text( |
803 | ! |
paste( |
804 | ! |
formatted_data_constraint(input$constraint_var, input$constraint_range_min, input$constraint_range_max), |
805 | ! |
"\nSelect Line Splitting Variable:", |
806 | ! |
if (!is.null(input$shape)) input$shape else "None", |
807 | ! |
"\nContributing Observations Threshold:", |
808 | ! |
input$count_threshold |
809 |
), |
|
810 | ! |
style = "verbatim" |
811 |
) |
|
812 | ! |
card$append_text("Plot", "header3") |
813 | ! |
card$append_plot(plot_r(), dim = plot_data$dim()) |
814 | ! |
if (!comment == "") { |
815 | ! |
card$append_text("Comment", "header3") |
816 | ! |
card$append_text(comment) |
817 |
} |
|
818 | ! |
card$append_src(paste(teal.code::get_code(plot_q()), collapse = "\n")) |
819 | ! |
card |
820 |
} |
|
821 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) |
822 |
} |
|
823 |
### |
|
824 | ||
825 | ! |
teal.widgets::verbatim_popup_srv( |
826 | ! |
id = "warning", |
827 | ! |
verbatim_content = reactive(teal.code::get_warnings(plot_q())), |
828 | ! |
title = "Warning", |
829 | ! |
disabled = reactive(is.null(teal.code::get_warnings(plot_q()))) |
830 |
) |
|
831 | ||
832 | ! |
teal.widgets::verbatim_popup_srv( |
833 | ! |
id = "rcode", |
834 | ! |
verbatim_content = reactive(teal.code::get_code(plot_q())), |
835 | ! |
title = "Show R Code for Line Plot" |
836 |
) |
|
837 |
}) |
|
838 |
} |
1 |
#' Scatter Plot Teal Module For Biomarker Analysis |
|
2 |
#' |
|
3 |
#' @description Scatter Plot Teal Module For Biomarker Analysis |
|
4 |
#' |
|
5 |
#' @inheritParams teal.widgets::standard_layout |
|
6 |
#' @param label menu item label of the module in the teal app. |
|
7 |
#' @param dataname analysis data passed to the data argument of \code{\link[teal]{init}}. E.g. `ADaM` structured |
|
8 |
#' laboratory data frame \code{ADLB}. |
|
9 |
#' @param param_var name of variable containing biomarker codes e.g. \code{PARAMCD}. |
|
10 |
#' @param xaxis_param biomarker selected for `x-axis`. |
|
11 |
#' @param yaxis_param biomarker selected for `y-axis`. |
|
12 |
#' @param xaxis_var name of variable containing biomarker results displayed on x-axis e.g. \code{BASE}. |
|
13 |
#' @param yaxis_var name of variable containing biomarker results displayed on y-axis e.g. \code{AVAL}. |
|
14 |
#' @param trt_group \code{\link[teal.transform]{choices_selected}} object with available choices and pre-selected option |
|
15 |
#' for variable names representing treatment group e.g. `ARM`. |
|
16 |
#' @param color_manual vector of colors applied to treatment values. |
|
17 |
#' @param shape_manual vector of symbols applied to `LOQ` values. |
|
18 |
#' @param facet_ncol numeric value indicating number of facets per row. |
|
19 |
#' @param trt_facet facet by treatment group \code{trt_group}. |
|
20 |
#' @param visit_facet visit facet toggle. |
|
21 |
#' @param reg_line include regression line and annotations for slope and coefficient in visualization. Use with facet |
|
22 |
#' TRUE. |
|
23 |
#' @param loq_legend `loq` legend toggle. |
|
24 |
#' @param rotate_xlab 45 degree rotation of `x-axis` values. |
|
25 |
#' @param hline_arb numeric vector of at most 2 values identifying intercepts for arbitrary horizontal lines. |
|
26 |
#' @param hline_arb_color a character vector of at most length of \code{hline_arb}. |
|
27 |
#' naming the color for the arbitrary horizontal lines. |
|
28 |
#' @param hline_arb_label a character vector of at most length of \code{hline_arb}. |
|
29 |
#' naming the label for the arbitrary horizontal lines. |
|
30 |
#' @param hline_vars a character vector to name the columns that will define additional horizontal lines. |
|
31 |
#' @param hline_vars_colors a character vector naming the colors for the additional horizontal lines. |
|
32 |
#' @param hline_vars_labels a character vector naming the labels for the additional horizontal lines that will appear |
|
33 |
#' @param vline_arb numeric vector of at most 2 values identifying intercepts for arbitrary horizontal lines. |
|
34 |
#' @param vline_arb_color a character vector of at most length of \code{vline_arb}. |
|
35 |
#' naming the color for the arbitrary horizontal lines. |
|
36 |
#' @param vline_arb_label a character vector of at most length of \code{vline_arb}. |
|
37 |
#' naming the label for the arbitrary horizontal lines. |
|
38 |
#' @param vline_vars a character vector to name the columns that will define additional vertical lines. |
|
39 |
#' @param vline_vars_colors a character vector naming the colors for the additional vertical lines. |
|
40 |
#' @param vline_vars_labels a character vector naming the labels for the additional vertical lines that will appear |
|
41 |
#' @param plot_height controls plot height. |
|
42 |
#' @param plot_width optional, controls plot width. |
|
43 |
#' @param font_size font size control for title, `x-axis` label, `y-axis` label and legend. |
|
44 |
#' @param dot_size plot dot size. |
|
45 |
#' @param reg_text_size font size control for regression line annotations. |
|
46 |
#' |
|
47 |
#' @export |
|
48 |
#' |
|
49 |
#' @author Nick Paszty (npaszty) paszty.nicholas@gene.com |
|
50 |
#' @author Balazs Toth (tothb2) toth.balazs@gene.com |
|
51 |
#' |
|
52 |
#' @examples |
|
53 |
#' |
|
54 |
#' # Example using ADaM structure analysis dataset. |
|
55 |
#' |
|
56 |
#' # original ARM value = dose value |
|
57 |
#' arm_mapping <- list( |
|
58 |
#' "A: Drug X" = "150mg QD", |
|
59 |
#' "B: Placebo" = "Placebo", |
|
60 |
#' "C: Combination" = "Combination" |
|
61 |
#' ) |
|
62 |
#' color_manual <- c("150mg QD" = "#000000", "Placebo" = "#3498DB", "Combination" = "#E74C3C") |
|
63 |
#' # assign LOQ flag symbols: circles for "N" and triangles for "Y", squares for "NA" |
|
64 |
#' shape_manual <- c("N" = 1, "Y" = 2, "NA" = 0) |
|
65 |
#' |
|
66 |
#' set.seed(1) |
|
67 |
#' ADSL <- goshawk::rADSL |
|
68 |
#' ADLB <- goshawk::rADLB |
|
69 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
70 |
#' ADLB <- ADLB %>% |
|
71 |
#' dplyr::mutate(AVISITCD = dplyr::case_when( |
|
72 |
#' AVISIT == "SCREENING" ~ "SCR", |
|
73 |
#' AVISIT == "BASELINE" ~ "BL", |
|
74 |
#' grepl("WEEK", AVISIT) ~ |
|
75 |
#' paste( |
|
76 |
#' "W", |
|
77 |
#' trimws( |
|
78 |
#' substr( |
|
79 |
#' AVISIT, |
|
80 |
#' start = 6, |
|
81 |
#' stop = stringr::str_locate(AVISIT, "DAY") - 1 |
|
82 |
#' ) |
|
83 |
#' ) |
|
84 |
#' ), |
|
85 |
#' TRUE ~ NA_character_ |
|
86 |
#' )) %>% |
|
87 |
#' dplyr::mutate(AVISITCDN = dplyr::case_when( |
|
88 |
#' AVISITCD == "SCR" ~ -2, |
|
89 |
#' AVISITCD == "BL" ~ 0, |
|
90 |
#' grepl("W", AVISITCD) ~ as.numeric(gsub("[^0-9]*", "", AVISITCD)), |
|
91 |
#' TRUE ~ NA_real_ |
|
92 |
#' )) %>% |
|
93 |
#' # use ARMCD values to order treatment in visualization legend |
|
94 |
#' dplyr::mutate(TRTORD = ifelse(grepl("C", ARMCD), 1, |
|
95 |
#' ifelse(grepl("B", ARMCD), 2, |
|
96 |
#' ifelse(grepl("A", ARMCD), 3, NA) |
|
97 |
#' ) |
|
98 |
#' )) %>% |
|
99 |
#' dplyr::mutate(ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))])) %>% |
|
100 |
#' dplyr::mutate(ARM = factor(ARM) %>% |
|
101 |
#' reorder(TRTORD)) %>% |
|
102 |
#' dplyr::mutate( |
|
103 |
#' ANRHI = dplyr::case_when( |
|
104 |
#' PARAMCD == "ALT" ~ 60, |
|
105 |
#' PARAMCD == "CRP" ~ 70, |
|
106 |
#' PARAMCD == "IGA" ~ 80, |
|
107 |
#' TRUE ~ NA_real_ |
|
108 |
#' ), |
|
109 |
#' ANRLO = dplyr::case_when( |
|
110 |
#' PARAMCD == "ALT" ~ 20, |
|
111 |
#' PARAMCD == "CRP" ~ 30, |
|
112 |
#' PARAMCD == "IGA" ~ 40, |
|
113 |
#' TRUE ~ NA_real_ |
|
114 |
#' ) |
|
115 |
#' ) %>% |
|
116 |
#' dplyr::rowwise() %>% |
|
117 |
#' dplyr::group_by(PARAMCD) %>% |
|
118 |
#' dplyr::mutate(LBSTRESC = ifelse( |
|
119 |
#' USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
120 |
#' paste("<", round(runif(1, min = 25, max = 30))), LBSTRESC |
|
121 |
#' )) %>% |
|
122 |
#' dplyr::mutate(LBSTRESC = ifelse( |
|
123 |
#' USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
124 |
#' paste(">", round(runif(1, min = 70, max = 75))), LBSTRESC |
|
125 |
#' )) %>% |
|
126 |
#' ungroup() |
|
127 |
#' attr(ADLB[["ARM"]], "label") <- var_labels[["ARM"]] |
|
128 |
#' attr(ADLB[["ANRHI"]], "label") <- "Analysis Normal Range Upper Limit" |
|
129 |
#' attr(ADLB[["ANRLO"]], "label") <- "Analysis Normal Range Lower Limit" |
|
130 |
#' |
|
131 |
#' # add LLOQ and ULOQ variables |
|
132 |
#' ADLB_LOQS <- goshawk:::h_identify_loq_values(ADLB) |
|
133 |
#' ADLB <- dplyr::left_join(ADLB, ADLB_LOQS, by = "PARAM") |
|
134 |
#' |
|
135 |
#' app <- teal::init( |
|
136 |
#' data = teal.data::cdisc_data( |
|
137 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- goshawk::rADSL"), |
|
138 |
#' teal.data::cdisc_dataset( |
|
139 |
#' "ADLB", |
|
140 |
#' ADLB, |
|
141 |
#' code = "set.seed(1) |
|
142 |
#' ADLB <- goshawk::rADLB |
|
143 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
144 |
#' ADLB <- ADLB %>% |
|
145 |
#' dplyr::mutate(AVISITCD = dplyr::case_when( |
|
146 |
#' AVISIT == 'SCREENING' ~ 'SCR', |
|
147 |
#' AVISIT == 'BASELINE' ~ 'BL', |
|
148 |
#' grepl('WEEK', AVISIT) ~ |
|
149 |
#' paste( |
|
150 |
#' 'W', |
|
151 |
#' trimws( |
|
152 |
#' substr( |
|
153 |
#' AVISIT, |
|
154 |
#' start = 6, |
|
155 |
#' stop = stringr::str_locate(AVISIT, 'DAY') - 1 |
|
156 |
#' ) |
|
157 |
#' ) |
|
158 |
#' ), |
|
159 |
#' TRUE ~ NA_character_)) %>% |
|
160 |
#' dplyr::mutate(AVISITCDN = dplyr::case_when( |
|
161 |
#' AVISITCD == 'SCR' ~ -2, |
|
162 |
#' AVISITCD == 'BL' ~ 0, |
|
163 |
#' grepl('W', AVISITCD) ~ as.numeric(gsub('[^0-9]*', '', AVISITCD)), |
|
164 |
#' TRUE ~ NA_real_)) %>% |
|
165 |
#' # use ARMCD values to order treatment in visualization legend |
|
166 |
#' dplyr::mutate(TRTORD = ifelse(grepl('C', ARMCD), 1, |
|
167 |
#' ifelse(grepl('B', ARMCD), 2, |
|
168 |
#' ifelse(grepl('A', ARMCD), 3, NA)))) %>% |
|
169 |
#' dplyr::mutate(ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))])) %>% |
|
170 |
#' dplyr::mutate(ARM = factor(ARM) %>% |
|
171 |
#' reorder(TRTORD)) %>% |
|
172 |
#' dplyr::mutate( |
|
173 |
#' ANRHI = dplyr::case_when( |
|
174 |
#' PARAMCD == 'ALT' ~ 60, |
|
175 |
#' PARAMCD == 'CRP' ~ 70, |
|
176 |
#' PARAMCD == 'IGA' ~ 80, |
|
177 |
#' TRUE ~ NA_real_ |
|
178 |
#' ), |
|
179 |
#' ANRLO = dplyr::case_when( |
|
180 |
#' PARAMCD == 'ALT' ~ 20, |
|
181 |
#' PARAMCD == 'CRP' ~ 30, |
|
182 |
#' PARAMCD == 'IGA' ~ 40, |
|
183 |
#' TRUE ~ NA_real_ |
|
184 |
#' )) %>% |
|
185 |
#' dplyr::rowwise() %>% |
|
186 |
#' dplyr::group_by(PARAMCD) %>% |
|
187 |
#' dplyr::mutate(LBSTRESC = ifelse( |
|
188 |
#' USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
189 |
#' paste('<', round(runif(1, min = 25, max = 30))), LBSTRESC)) %>% |
|
190 |
#' dplyr::mutate(LBSTRESC = ifelse( |
|
191 |
#' USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
192 |
#' paste( '>', round(runif(1, min = 70, max = 75))), LBSTRESC)) %>% |
|
193 |
#' ungroup() |
|
194 |
#' attr(ADLB[['ARM']], 'label') <- var_labels[['ARM']] |
|
195 |
#' attr(ADLB[['ANRHI']], 'label') <- 'Analysis Normal Range Upper Limit' |
|
196 |
#' attr(ADLB[['ANRLO']], 'label') <- 'Analysis Normal Range Lower Limit' |
|
197 |
#' |
|
198 |
#' # add LLOQ and ULOQ variables |
|
199 |
#' ADLB_LOQS <- goshawk:::h_identify_loq_values(ADLB) |
|
200 |
#' ADLB <- left_join(ADLB, ADLB_LOQS, by = 'PARAM')", |
|
201 |
#' vars = list(arm_mapping = arm_mapping) |
|
202 |
#' ), |
|
203 |
#' check = TRUE |
|
204 |
#' ), |
|
205 |
#' modules = teal::modules( |
|
206 |
#' teal.goshawk::tm_g_gh_correlationplot( |
|
207 |
#' label = "Correlation Plot", |
|
208 |
#' dataname = "ADLB", |
|
209 |
#' param_var = "PARAMCD", |
|
210 |
#' xaxis_param = choices_selected(c("ALT", "CRP", "IGA"), "ALT"), |
|
211 |
#' yaxis_param = choices_selected(c("ALT", "CRP", "IGA"), "CRP"), |
|
212 |
#' xaxis_var = choices_selected(c("AVAL", "BASE", "CHG", "PCHG"), "BASE"), |
|
213 |
#' yaxis_var = choices_selected(c("AVAL", "BASE", "CHG", "PCHG"), "AVAL"), |
|
214 |
#' trt_group = choices_selected(c("ARM", "ACTARM"), "ARM"), |
|
215 |
#' color_manual = c( |
|
216 |
#' "Drug X 100mg" = "#000000", |
|
217 |
#' "Placebo" = "#3498DB", |
|
218 |
#' "Combination 100mg" = "#E74C3C" |
|
219 |
#' ), |
|
220 |
#' shape_manual = c("N" = 1, "Y" = 2, "NA" = 0), |
|
221 |
#' plot_height = c(500, 200, 2000), |
|
222 |
#' facet_ncol = 2, |
|
223 |
#' visit_facet = TRUE, |
|
224 |
#' reg_line = FALSE, |
|
225 |
#' loq_legend = TRUE, |
|
226 |
#' font_size = c(12, 8, 20), |
|
227 |
#' dot_size = c(1, 1, 12), |
|
228 |
#' reg_text_size = c(3, 3, 10), |
|
229 |
#' hline_arb = c(40, 50), |
|
230 |
#' hline_arb_label = "arb hori label", |
|
231 |
#' hline_arb_color = c("red", "blue"), |
|
232 |
#' hline_vars = c("ANRHI", "ANRLO", "ULOQN", "LLOQN"), |
|
233 |
#' hline_vars_colors = c("green", "blue", "purple", "cyan"), |
|
234 |
#' hline_vars_label = c("ANRHI Label", "ANRLO Label", "ULOQN Label", "LLOQN Label"), |
|
235 |
#' vline_vars = c("ANRHI", "ANRLO", "ULOQN", "LLOQN"), |
|
236 |
#' vline_vars_colors = c("yellow", "orange", "brown", "gold"), |
|
237 |
#' vline_vars_labels = c("ANRHI Label", "ANRLO Label", "ULOQN Label", "LLOQN Label"), |
|
238 |
#' vline_arb = c(50, 70), |
|
239 |
#' vline_arb_label = "arb vert A", |
|
240 |
#' vline_arb_color = c("green", "orange") |
|
241 |
#' ) |
|
242 |
#' ) |
|
243 |
#' ) |
|
244 |
#' if (interactive()) { |
|
245 |
#' shinyApp(app$ui, app$server) |
|
246 |
#' } |
|
247 |
tm_g_gh_correlationplot <- function(label, |
|
248 |
dataname, |
|
249 |
param_var = "PARAMCD", |
|
250 |
xaxis_param = "ALT", |
|
251 |
xaxis_var = "BASE", |
|
252 |
yaxis_param = "CRP", |
|
253 |
yaxis_var = "AVAL", |
|
254 |
trt_group, |
|
255 |
color_manual = NULL, |
|
256 |
shape_manual = NULL, |
|
257 |
facet_ncol = 2, |
|
258 |
visit_facet = TRUE, |
|
259 |
trt_facet = FALSE, |
|
260 |
reg_line = FALSE, |
|
261 |
loq_legend = TRUE, |
|
262 |
rotate_xlab = FALSE, |
|
263 |
hline_arb = numeric(0), |
|
264 |
hline_arb_color = "red", |
|
265 |
hline_arb_label = "Horizontal line", |
|
266 |
hline_vars = character(0), |
|
267 |
hline_vars_colors = "green", |
|
268 |
hline_vars_labels = hline_vars, |
|
269 |
vline_arb = numeric(0), |
|
270 |
vline_arb_color = "red", |
|
271 |
vline_arb_label = "Vertical line", |
|
272 |
vline_vars = character(0), |
|
273 |
vline_vars_colors = "green", |
|
274 |
vline_vars_labels = vline_vars, |
|
275 |
plot_height = c(500, 200, 2000), |
|
276 |
plot_width = NULL, |
|
277 |
font_size = c(12, 8, 20), |
|
278 |
dot_size = c(1, 1, 12), |
|
279 |
reg_text_size = c(3, 3, 10), |
|
280 |
pre_output = NULL, |
|
281 |
post_output = NULL) { |
|
282 | ! |
logger::log_info("Initializing tm_g_gh_correlationplot") |
283 | ! |
checkmate::assert_class(xaxis_param, "choices_selected") |
284 | ! |
checkmate::assert_class(yaxis_param, "choices_selected") |
285 | ! |
checkmate::assert_class(xaxis_var, "choices_selected") |
286 | ! |
checkmate::assert_class(yaxis_var, "choices_selected") |
287 | ! |
checkmate::assert_class(trt_group, "choices_selected") |
288 | ! |
checkmate::assert_flag(trt_facet) |
289 | ! |
validate_line_arb_arg(hline_arb, hline_arb_color, hline_arb_label) |
290 | ! |
validate_line_arb_arg(vline_arb, vline_arb_color, vline_arb_label) |
291 | ! |
validate_line_vars_arg(hline_vars, hline_vars_colors, hline_vars_labels) |
292 | ! |
validate_line_vars_arg(vline_vars, vline_vars_colors, vline_vars_labels) |
293 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
294 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
295 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
296 | ! |
checkmate::assert_numeric( |
297 | ! |
plot_width[1], |
298 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
299 |
) |
|
300 | ! |
checkmate::assert_numeric(font_size, len = 3) |
301 | ! |
checkmate::assert_numeric(dot_size, len = 3) |
302 | ! |
checkmate::assert_numeric(reg_text_size, len = 3) |
303 | ||
304 | ! |
args <- as.list(environment()) |
305 | ||
306 | ! |
module( |
307 | ! |
label = label, |
308 | ! |
datanames = dataname, |
309 | ! |
server = srv_g_correlationplot, |
310 | ! |
server_args = list( |
311 | ! |
dataname = dataname, |
312 | ! |
param_var = param_var, |
313 | ! |
trt_group = trt_group, |
314 | ! |
trt_facet = trt_facet, |
315 | ! |
color_manual = color_manual, |
316 | ! |
shape_manual = shape_manual, |
317 | ! |
plot_height = plot_height, |
318 | ! |
plot_width = plot_width, |
319 | ! |
hline_vars_colors = hline_vars_colors, |
320 | ! |
hline_vars_labels = hline_vars_labels, |
321 | ! |
vline_vars_colors = vline_vars_colors, |
322 | ! |
vline_vars_labels = vline_vars_labels |
323 |
), |
|
324 | ! |
ui = ui_g_correlationplot, |
325 | ! |
ui_args = args |
326 |
) |
|
327 |
} |
|
328 | ||
329 |
ui_g_correlationplot <- function(id, ...) { |
|
330 | ! |
ns <- NS(id) |
331 | ! |
a <- list(...) |
332 | ||
333 | ! |
teal.widgets::standard_layout( |
334 | ! |
output = templ_ui_output_datatable(ns), |
335 | ! |
encoding = div( |
336 |
### Reporter |
|
337 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")), |
338 |
### |
|
339 | ! |
templ_ui_dataname(a$dataname), |
340 | ! |
teal.widgets::optionalSelectInput( |
341 | ! |
ns("trt_group"), |
342 | ! |
label = "Select Treatment Variable", |
343 | ! |
choices = a$trt_group$choices, |
344 | ! |
selected = a$trt_group$selected, |
345 | ! |
multiple = FALSE |
346 |
), |
|
347 | ! |
templ_ui_params_vars( |
348 | ! |
ns, |
349 | ! |
xparam_choices = a$xaxis_param$choices, xparam_selected = a$xaxis_param$selected, |
350 | ! |
xchoices = a$xaxis_var$choices, xselected = a$xaxis_var$selected, |
351 | ! |
yparam_choices = a$yaxis_param$choices, yparam_selected = a$yaxis_param$selected, |
352 | ! |
ychoices = a$yaxis_var$choices, yselected = a$yaxis_var$selected |
353 |
), |
|
354 | ! |
templ_ui_constraint(ns, "X-Axis Data Constraint"), # required by constr_anl_q |
355 | ! |
if (length(a$hline_vars) > 0) { |
356 | ! |
teal.widgets::optionalSelectInput( |
357 | ! |
ns("hline_vars"), |
358 | ! |
label = "Add Horizontal Range Line(s):", |
359 | ! |
choices = a$hline_vars, |
360 | ! |
selected = NULL, |
361 | ! |
multiple = TRUE |
362 |
) |
|
363 |
}, |
|
364 | ! |
ui_arbitrary_lines(id = ns("hline_arb"), a$hline_arb, a$hline_arb_label, a$hline_arb_color), |
365 | ! |
if (length(a$vline_vars) > 0) { |
366 | ! |
teal.widgets::optionalSelectInput( |
367 | ! |
ns("vline_vars"), |
368 | ! |
label = "Add Vertical Range Line(s):", |
369 | ! |
choices = a$vline_vars, |
370 | ! |
selected = NULL, |
371 | ! |
multiple = TRUE |
372 |
) |
|
373 |
}, |
|
374 | ! |
ui_arbitrary_lines( |
375 | ! |
id = ns("vline_arb"), |
376 | ! |
a$vline_arb, |
377 | ! |
a$vline_arb_label, |
378 | ! |
a$vline_arb_color, |
379 | ! |
title = "Arbitrary Vertical Lines:" |
380 |
), |
|
381 | ! |
teal.widgets::panel_group( |
382 | ! |
teal.widgets::panel_item( |
383 | ! |
title = "Plot Aesthetic Settings", |
384 | ! |
toggle_slider_ui( |
385 | ! |
ns("xrange_scale"), |
386 | ! |
label = "X-Axis Range Zoom", |
387 | ! |
min = -1000000, max = 1000000, value = c(-1000000, 1000000) |
388 |
), |
|
389 | ! |
toggle_slider_ui( |
390 | ! |
ns("yrange_scale"), |
391 | ! |
label = "Y-Axis Range Zoom", |
392 | ! |
min = -1000000, max = 1000000, value = c(-1000000, 1000000) |
393 |
), |
|
394 | ! |
numericInput(ns("facet_ncol"), "Number of Plots Per Row:", a$facet_ncol, min = 1), |
395 | ! |
checkboxInput(ns("trt_facet"), "Treatment Variable Faceting", a$trt_facet), |
396 | ! |
checkboxInput(ns("visit_facet"), "Visit Faceting", a$visit_facet), |
397 | ! |
checkboxInput(ns("reg_line"), "Regression Line", a$reg_line), |
398 | ! |
checkboxInput(ns("loq_legend"), "Display LoQ Legend", a$loq_legend), |
399 | ! |
checkboxInput(ns("rotate_xlab"), "Rotate X-axis Label", a$rotate_xlab) |
400 |
), |
|
401 | ! |
teal.widgets::panel_item( |
402 | ! |
title = "Plot settings", |
403 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("font_size"), "Font Size", a$font_size, ticks = FALSE), |
404 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("dot_size"), "Dot Size", a$dot_size, ticks = FALSE), |
405 | ! |
teal.widgets::optionalSliderInputValMinMax( |
406 | ! |
ns("reg_text_size"), |
407 | ! |
"Regression Annotations Size", |
408 | ! |
a$reg_text_size, |
409 | ! |
ticks = FALSE |
410 |
) |
|
411 |
) |
|
412 |
) |
|
413 |
), |
|
414 | ! |
forms = tagList( |
415 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"), |
416 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code") |
417 |
), |
|
418 | ! |
pre_output = a$pre_output, |
419 | ! |
post_output = a$post_output |
420 |
) |
|
421 |
} |
|
422 | ||
423 |
srv_g_correlationplot <- function(id, |
|
424 |
data, |
|
425 |
reporter, |
|
426 |
filter_panel_api, |
|
427 |
dataname, |
|
428 |
param_var, |
|
429 |
trt_group, |
|
430 |
trt_facet, |
|
431 |
color_manual, |
|
432 |
shape_manual, |
|
433 |
plot_height, |
|
434 |
plot_width, |
|
435 |
hline_vars_colors, |
|
436 |
hline_vars_labels, |
|
437 |
vline_vars_colors, |
|
438 |
vline_vars_labels) { |
|
439 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
440 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
441 | ! |
checkmate::assert_class(data, "tdata") |
442 | ||
443 | ! |
moduleServer(id, function(input, output, session) { |
444 | ! |
iv_r <- reactive({ |
445 | ! |
iv <- shinyvalidate::InputValidator$new() |
446 | ||
447 | ! |
iv$add_rule("xaxis_param", shinyvalidate::sv_required("Please select an X-Axis biomarker")) |
448 | ! |
iv$add_rule("yaxis_param", shinyvalidate::sv_required("Please select a Y-Axis biomarker")) |
449 | ! |
iv$add_rule("trt_group", shinyvalidate::sv_required("Please select a treatment variable")) |
450 | ! |
iv$add_rule("xaxis_var", shinyvalidate::sv_required("Please select an X-Axis variable")) |
451 | ! |
iv$add_rule("yaxis_var", shinyvalidate::sv_required("Please select a Y-Axis variable")) |
452 | ! |
iv$add_rule("facet_ncol", plots_per_row_validate_rules()) |
453 | ||
454 | ! |
iv$add_validator(anl_constraint_output()$iv_r()) |
455 | ! |
iv$add_validator(horizontal_line()$iv_r()) |
456 | ! |
iv$add_validator(vertical_line()$iv_r()) |
457 | ! |
iv$enable() |
458 | ! |
iv |
459 |
}) |
|
460 | ||
461 | ||
462 | ||
463 |
# filter selected biomarkers |
|
464 | ! |
anl_param <- reactive({ |
465 | ! |
dataset_var <- dataname |
466 | ! |
ANL <- data[[dataname]]() # nolint |
467 | ! |
validate_has_data(ANL, 1) |
468 | ||
469 | ! |
if (length(input$hline_vars) > 0) { |
470 | ! |
validate( |
471 | ! |
need( |
472 | ! |
all(input$hline_vars %in% names(ANL)), |
473 | ! |
"One or more selected horizontal line variable(s) is/are not names to any column in the data" |
474 |
), |
|
475 | ! |
need( |
476 | ! |
all(input$vline_vars %in% names(ANL)), |
477 | ! |
"One or more selected vertical line variable(s) is/are not names to any column in the data" |
478 |
) |
|
479 |
) |
|
480 |
} |
|
481 | ||
482 | ! |
validate_has_variable(ANL, param_var) |
483 | ||
484 | ! |
validate_in( |
485 | ! |
input$xaxis_param, unique(ANL[[param_var]]), |
486 | ! |
sprintf("X-Axis Biomarker %s is not available in data %s", input$xaxis_param, dataname) |
487 |
) |
|
488 | ||
489 | ! |
validate_in( |
490 | ! |
input$yaxis_param, unique(ANL[[param_var]]), |
491 | ! |
sprintf("Y-Axis Biomarker %s is not available in data %s", input$yaxis_param, dataname) |
492 |
) |
|
493 | ||
494 | ! |
validate_has_variable( |
495 | ! |
ANL, |
496 | ! |
"AVISITCD", |
497 | ! |
sprintf("Variable AVISITCD is not available in data %s", dataname) |
498 |
) |
|
499 | ||
500 | ! |
validate_has_variable( |
501 | ! |
ANL, |
502 | ! |
"BASE", |
503 | ! |
sprintf("Variable BASE is not available in data %s", dataname) |
504 |
) |
|
505 | ||
506 | ! |
validate_has_variable( |
507 | ! |
ANL, |
508 | ! |
"BASE2", |
509 | ! |
sprintf("Variable BASE2 is not available in data %s", dataname) |
510 |
) |
|
511 | ||
512 | ! |
validate_has_variable( |
513 | ! |
ANL, |
514 | ! |
"LOQFL", |
515 | ! |
sprintf("Variable LOQFL is not available in data %s", dataname) |
516 |
) |
|
517 | ||
518 | ! |
validate_has_variable( |
519 | ! |
ANL, |
520 | ! |
"PARAM", |
521 | ! |
sprintf("Variable PARAM is not available in data %s", dataname) |
522 |
) |
|
523 | ||
524 | ! |
validate_has_variable( |
525 | ! |
ANL, |
526 | ! |
"LBSTRESC", |
527 | ! |
sprintf("Variable LBSTRESC is not available in data %s", dataname) |
528 |
) |
|
529 | ||
530 | ! |
validate_has_variable( |
531 | ! |
ANL, |
532 | ! |
input$trt_group, |
533 | ! |
sprintf("Variable %s is not available in data %s", input$trt_group, dataname) |
534 |
) |
|
535 | ||
536 | ! |
validate_has_variable( |
537 | ! |
ANL, |
538 | ! |
"USUBJID", |
539 | ! |
sprintf("Variable USUBJID is not available in data %s", dataname) |
540 |
) |
|
541 | ||
542 | ! |
validate_has_variable( |
543 | ! |
ANL, |
544 | ! |
input$xaxis_var, |
545 | ! |
sprintf("Variable %s is not available in data %s", input$xaxis_var, dataname) |
546 |
) |
|
547 | ||
548 | ! |
validate_has_variable( |
549 | ! |
ANL, |
550 | ! |
input$yaxis_var, |
551 | ! |
sprintf("Variable %s is not available in data %s", input$yaxis_var, dataname) |
552 |
) |
|
553 | ||
554 |
# analysis |
|
555 | ! |
private_qenv <- teal.code::new_qenv(tdata2env(data), code = get_code_tdata(data)) %>% |
556 | ! |
teal.code::eval_code( |
557 | ! |
code = bquote({ |
558 | ! |
ANL <- .(as.name(dataset_var)) %>% # nolint |
559 | ! |
dplyr::filter(.data[[.(param_var)]] %in% union(.(input$xaxis_param), .(input$yaxis_param))) |
560 |
}) |
|
561 |
) |
|
562 | ! |
validate_has_data(private_qenv[["ANL"]], 1) |
563 | ! |
return(list(ANL = ANL, qenv = private_qenv)) |
564 |
}) |
|
565 | ||
566 |
# constraints |
|
567 | ! |
observe({ |
568 | ! |
req(input$xaxis_param) |
569 | ||
570 | ! |
constraint_var <- input$constraint_var |
571 | ! |
req(constraint_var) |
572 | ||
573 |
# note that filtered is false thus we cannot use anl_param()$ANL |
|
574 | ! |
ANL <- data[[dataname]]() # nolint |
575 | ! |
validate_has_data(ANL, 1) |
576 | ||
577 | ! |
validate_has_variable(ANL, param_var) |
578 | ! |
validate_has_variable(ANL, "AVISITCD") |
579 | ! |
validate_has_variable(ANL, "BASE") |
580 | ! |
validate_has_variable(ANL, "BASE2") |
581 | ||
582 | ! |
ANL <- ANL %>% dplyr::filter(.data[[param_var]] == input$xaxis_param) # nolint |
583 | ||
584 | ! |
visit_freq <- unique(ANL$AVISITCD) |
585 | ||
586 |
# get min max values |
|
587 | ! |
if ((constraint_var == "BASE2" && any(grepl("SCR", visit_freq))) || |
588 | ! |
(constraint_var == "BASE" && any(grepl("BL", visit_freq)))) { |
589 | ! |
val <- stats::na.omit(switch(constraint_var, |
590 | ! |
"BASE" = ANL$BASE[ANL$AVISITCD == "BL"], |
591 | ! |
"BASE2" = ANL$BASE2[ANL$AVISITCD == "SCR"], |
592 | ! |
stop(paste(constraint_var, "not allowed")) |
593 |
)) |
|
594 | ||
595 | ! |
if (length(val) == 0 || all(is.na(val))) { |
596 | ! |
shinyjs::show("all_na") |
597 | ! |
shinyjs::hide("constraint_range") |
598 | ! |
args <- list( |
599 | ! |
min = list(label = "Min", min = 0, max = 0, value = 0), |
600 | ! |
max = list(label = "Max", min = 0, max = 0, value = 0) |
601 |
) |
|
602 | ! |
update_min_max(session, args) |
603 |
} else { |
|
604 | ! |
rng <- range(val, na.rm = TRUE) |
605 | ||
606 | ! |
minmax <- c(floor(rng[1] * 1000) / 1000, ceiling(rng[2] * 1000) / 1000) |
607 | ||
608 | ! |
label_min <- sprintf("Min (%s)", minmax[1]) |
609 | ! |
label_max <- sprintf("Max (%s)", minmax[2]) |
610 | ||
611 | ! |
args <- list( |
612 | ! |
min = list(label = label_min, min = minmax[1], max = minmax[2], value = minmax[1]), |
613 | ! |
max = list(label = label_max, min = minmax[1], max = minmax[2], value = minmax[2]) |
614 |
) |
|
615 | ||
616 | ! |
update_min_max(session, args) |
617 | ! |
shinyjs::show("constraint_range") # update before show |
618 | ! |
shinyjs::hide("all_na") |
619 |
} |
|
620 | ! |
} else if (constraint_var == "NONE") { |
621 | ! |
shinyjs::hide("constraint_range") # hide before update |
622 | ! |
shinyjs::hide("all_na") |
623 | ||
624 |
# force update (and thus refresh) on different constraint_var -> pass unique value for each constraint_var name |
|
625 | ! |
args <- list( |
626 | ! |
min = list(label = "Min", min = 0, max = 0, value = 0), |
627 | ! |
max = list(label = "Max", min = 0, max = 0, value = 0) |
628 |
) |
|
629 | ||
630 | ! |
update_min_max(session, args) |
631 |
} else { |
|
632 | ! |
stop("invalid contraint_var", constraint_var) |
633 |
} |
|
634 |
}) |
|
635 | ||
636 | ! |
anl_constraint_output <- create_anl_constraint_reactive(anl_param, input, param_id = "xaxis_param", min_rows = 1) |
637 | ! |
anl_constraint <- anl_constraint_output()$value |
638 | ||
639 |
# update sliders for axes taking constraints into account |
|
640 | ! |
xrange_slider <- toggle_slider_server("xrange_scale") |
641 | ! |
yrange_slider <- toggle_slider_server("yrange_scale") |
642 | ! |
keep_range_slider_updated(session, input, xrange_slider$update_state, "xaxis_var", "xaxis_param", anl_constraint) |
643 | ! |
keep_range_slider_updated(session, input, yrange_slider$update_state, "yaxis_var", "yaxis_param", anl_constraint) |
644 | ! |
keep_data_const_opts_updated(session, input, anl_constraint, "xaxis_param") |
645 | ||
646 |
# selector names after transposition |
|
647 | ! |
xvar <- reactive(paste0(input$xaxis_var, ".", input$xaxis_param)) |
648 | ! |
yvar <- reactive(paste0(input$yaxis_var, ".", input$yaxis_param)) |
649 | ! |
xloqfl <- reactive(paste0("LOQFL_", input$xaxis_param)) |
650 | ! |
yloqfl <- reactive(paste0("LOQFL_", input$yaxis_param)) |
651 | ||
652 |
# transpose data to plot |
|
653 | ! |
plot_data_transpose <- reactive({ |
654 | ! |
teal::validate_inputs(iv_r()) |
655 | ||
656 | ! |
req(anl_constraint()) |
657 | ! |
ANL <- anl_constraint()$ANL # nolint |
658 | ! |
trt_group <- input$trt_group |
659 | ! |
line_vars <- unique(c(input$hline_vars, input$vline_vars)) |
660 | ||
661 | ! |
private_q <- anl_constraint()$qenv %>% teal.code::eval_code( |
662 | ! |
code = bquote({ |
663 | ! |
ANL_TRANSPOSED1 <- ANL %>% # nolint |
664 | ! |
dplyr::select( |
665 | ! |
.data[["USUBJID"]], |
666 | ! |
.data[[.(trt_group)]], |
667 | ! |
.data[["AVISITCD"]], |
668 | ! |
.data[[.(param_var)]], |
669 | ! |
.data[[.(input$xaxis_var)]], |
670 | ! |
.data[[.(input$yaxis_var)]], |
671 | ! |
.(`if`(length(line_vars) == 0, NULL, line_vars)) |
672 |
) %>% |
|
673 | ! |
tidyr::pivot_longer( |
674 | ! |
c( |
675 | ! |
.data[[.(input$xaxis_var)]], |
676 | ! |
.data[[.(input$yaxis_var)]], |
677 | ! |
.(`if`(length(line_vars) == 0, NULL, line_vars)) |
678 |
), |
|
679 | ! |
names_to = "ANLVARS", |
680 | ! |
values_to = "ANLVALS" |
681 |
) %>% |
|
682 | ! |
tidyr::unite( |
683 | ! |
"ANL.PARAM", |
684 | ! |
"ANLVARS", |
685 | ! |
.(param_var), |
686 | ! |
sep = ".", |
687 | ! |
remove = TRUE |
688 |
) %>% |
|
689 | ! |
tidyr::pivot_wider(names_from = "ANL.PARAM", values_from = "ANLVALS") %>% |
690 | ! |
dplyr::filter(!is.na(.data[[.(xvar())]]) & !is.na(.data[[.(yvar())]])) |
691 | ||
692 | ! |
ANL_TRANSPOSED2 <- ANL %>% # nolint |
693 | ! |
dplyr::select( |
694 | ! |
.data[["USUBJID"]], |
695 | ! |
.data[[.(trt_group)]], |
696 | ! |
.data[["AVISITCD"]], |
697 | ! |
.data[[.(param_var)]], |
698 | ! |
.data[["LOQFL"]], |
699 | ! |
.data[["PARAM"]], |
700 | ! |
.data[["LBSTRESC"]] |
701 |
) %>% |
|
702 | ! |
tidyr::pivot_longer( |
703 | ! |
c( |
704 | ! |
.data[["LOQFL"]], |
705 | ! |
.data[["PARAM"]], |
706 | ! |
.data[["LBSTRESC"]] |
707 |
), |
|
708 | ! |
names_to = "ANLVARS", |
709 | ! |
values_to = "ANLVALS" |
710 |
) %>% |
|
711 | ! |
tidyr::unite( |
712 | ! |
"ANL.PARAM", |
713 | ! |
"ANLVARS", |
714 | ! |
.(param_var), |
715 | ! |
sep = "_", |
716 | ! |
remove = TRUE |
717 |
) %>% |
|
718 | ! |
tidyr::pivot_wider(names_from = "ANL.PARAM", values_from = "ANLVALS") %>% |
719 | ! |
dplyr::mutate(LOQFL_COMB = dplyr::case_when( |
720 | ! |
.data[[.(xloqfl())]] == "Y" | .data[[.(yloqfl())]] == "Y" ~ "Y", |
721 | ! |
.data[[.(xloqfl())]] == "N" & .data[[.(yloqfl())]] == "N" ~ "N", |
722 | ! |
.data[[.(xloqfl())]] == "N" & .data[[.(yloqfl())]] == "NA" ~ "N", |
723 | ! |
.data[[.(xloqfl())]] == "NA" & .data[[.(yloqfl())]] == "N" ~ "N", |
724 | ! |
.data[[.(xloqfl())]] == "NA" & .data[[.(yloqfl())]] == "NA" ~ "NA", |
725 | ! |
TRUE ~ as.character(NA) |
726 |
)) |
|
727 | ||
728 | ! |
ANL_TRANSPOSED <- merge(ANL_TRANSPOSED1, ANL_TRANSPOSED2) # nolint |
729 |
}) |
|
730 |
) |
|
731 | ||
732 | ! |
validate(need(nrow(private_q[["ANL_TRANSPOSED"]]) > 0, "Plot Data No Observations Left")) |
733 | ! |
validate_has_variable(data = private_q[["ANL_TRANSPOSED"]], varname = c(xvar(), yvar(), xloqfl(), yloqfl())) |
734 | ||
735 | ! |
private_q <- teal.code::eval_code( |
736 | ! |
object = private_q, |
737 | ! |
code = |
738 | ! |
bquote(attr(ANL_TRANSPOSED[[.(trt_group)]], "label") <- attr(ANL[[.(trt_group)]], "label")) # nolint |
739 |
) |
|
740 | ! |
return(list(ANL_TRANSPOSED = private_q[["ANL_TRANSPOSED"]], qenv = private_q)) |
741 |
}) |
|
742 | ||
743 | ! |
plot_labels <- reactive({ |
744 | ! |
req(anl_constraint()) |
745 | ! |
ANL <- anl_constraint()$qenv[["ANL"]] # nolint |
746 | ||
747 | ! |
xparam <- ANL$PARAM[ANL[[param_var]] == input$xaxis_param][1] |
748 | ! |
yparam <- ANL$PARAM[ANL[[param_var]] == input$yaxis_param][1] |
749 | ||
750 |
# setup the x-axis label. Combine the biomarker and the units (if available) |
|
751 | ! |
if (is.null(ANL$AVALU) || all(ANL[["AVALU"]] == "")) { |
752 | ! |
title_text <- paste(xparam, "and", yparam, "@ Visits") |
753 | ! |
xaxis_lab <- paste(xparam, input$xaxis_var, "Values") |
754 | ! |
yaxis_lab <- paste(yparam, input$yaxis_var, "Values") |
755 |
} else { |
|
756 | ! |
xunit <- ANL$AVALU[ANL[[param_var]] == input$xaxis_param][1] |
757 | ! |
yunit <- ANL$AVALU[ANL[[param_var]] == input$yaxis_param][1] |
758 | ||
759 | ! |
title_text <- paste0(xparam, " (", xunit, ") and ", yparam, " (", yunit, ") @ Visits") |
760 | ! |
xaxis_lab <- paste0(xparam, " (", xunit, ") ", input$xaxis_var, " Values") |
761 | ! |
yaxis_lab <- paste0(yparam, " (", yunit, ") ", input$yaxis_var, " Values") |
762 |
} |
|
763 | ||
764 | ! |
list(title_text = title_text, xaxis_lab = xaxis_lab, yaxis_lab = yaxis_lab) |
765 |
}) |
|
766 | ||
767 | ! |
horizontal_line <- srv_arbitrary_lines("hline_arb") |
768 | ! |
vertical_line <- srv_arbitrary_lines("vline_arb") |
769 | ||
770 |
# plot |
|
771 | ! |
plot_q <- reactive({ |
772 | ! |
req(plot_data_transpose()) |
773 |
# nolint start |
|
774 | ! |
xaxis_param <- input$xaxis_param |
775 | ! |
xaxis_var <- input$xaxis_var |
776 | ! |
yaxis_param <- input$yaxis_param |
777 | ! |
yaxis_var <- input$yaxis_var |
778 | ! |
xlim <- xrange_slider$state()$value |
779 | ! |
ylim <- yrange_slider$state()$value |
780 | ! |
font_size <- input$font_size |
781 | ! |
dot_size <- input$dot_size |
782 | ! |
reg_text_size <- input$reg_text_size |
783 | ! |
hline_arb <- horizontal_line()$line_arb |
784 | ! |
hline_arb_label <- horizontal_line()$line_arb_label |
785 | ! |
hline_arb_color <- horizontal_line()$line_arb_color |
786 | ! |
hline_vars <- if (length(input$hline_vars) == 0) { |
787 | ! |
NULL |
788 |
} else { |
|
789 | ! |
paste0(input$hline_vars, ".", yaxis_param) |
790 |
} |
|
791 | ! |
vline_arb <- vertical_line()$line_arb |
792 | ! |
vline_arb_label <- vertical_line()$line_arb_label |
793 | ! |
vline_arb_color <- vertical_line()$line_arb_color |
794 | ! |
vline_vars <- if (length(input$vline_vars) == 0) { |
795 | ! |
NULL |
796 |
} else { |
|
797 | ! |
paste0(input$vline_vars, ".", xaxis_param) |
798 |
} |
|
799 | ! |
facet_ncol <- input$facet_ncol |
800 | ! |
validate(need( |
801 | ! |
is.na(facet_ncol) || (as.numeric(facet_ncol) > 0 && as.numeric(facet_ncol) %% 1 == 0), |
802 | ! |
"Number of plots per row must be a positive integer" |
803 |
)) |
|
804 | ! |
visit_facet <- input$visit_facet |
805 | ! |
facet <- input$trt_facet |
806 | ! |
reg_line <- input$reg_line |
807 | ! |
loq_legend <- input$loq_legend |
808 | ! |
rotate_xlab <- input$rotate_xlab |
809 |
# nolint end |
|
810 | ! |
title_text <- plot_labels()$title_text |
811 | ! |
xaxis_lab <- plot_labels()$xaxis_lab |
812 | ! |
yaxis_lab <- plot_labels()$yaxis_lab |
813 | ! |
validate(need(input$trt_group, "Please select a treatment variable")) |
814 | ! |
trt_group <- input$trt_group |
815 | ||
816 | ! |
teal.code::eval_code( |
817 | ! |
object = plot_data_transpose()$qenv, |
818 | ! |
code = bquote({ |
819 |
# re-establish treatment variable label |
|
820 | ! |
p <- goshawk::g_correlationplot( |
821 | ! |
data = ANL_TRANSPOSED, |
822 | ! |
param_var = .(param_var), |
823 | ! |
xaxis_param = .(xaxis_param), |
824 | ! |
xaxis_var = .(xaxis_var), |
825 | ! |
xvar = .(xvar()), |
826 | ! |
yaxis_param = .(yaxis_param), |
827 | ! |
yaxis_var = .(yaxis_var), |
828 | ! |
yvar = .(yvar()), |
829 | ! |
trt_group = .(trt_group), |
830 | ! |
xlim = .(xlim), |
831 | ! |
ylim = .(ylim), |
832 | ! |
title_text = .(title_text), |
833 | ! |
xaxis_lab = .(xaxis_lab), |
834 | ! |
yaxis_lab = .(yaxis_lab), |
835 | ! |
color_manual = .(color_manual), |
836 | ! |
shape_manual = .(shape_manual), |
837 | ! |
facet_ncol = .(facet_ncol), |
838 | ! |
visit_facet = .(visit_facet), |
839 | ! |
facet = .(facet), |
840 | ! |
facet_var = .(trt_group), |
841 | ! |
reg_line = .(reg_line), |
842 | ! |
font_size = .(font_size), |
843 | ! |
dot_size = .(dot_size), |
844 | ! |
reg_text_size = .(reg_text_size), |
845 | ! |
loq_legend = .(loq_legend), |
846 | ! |
rotate_xlab = .(rotate_xlab), |
847 | ! |
hline_arb = .(hline_arb), |
848 | ! |
hline_arb_label = .(hline_arb_label), |
849 | ! |
hline_arb_color = .(hline_arb_color), |
850 | ! |
hline_vars = .(hline_vars), |
851 | ! |
hline_vars_colors = .(hline_vars_colors[seq_along(hline_vars)]), |
852 | ! |
hline_vars_labels = .(paste(hline_vars_labels[seq_along(hline_vars)], "-", yaxis_param)), |
853 | ! |
vline_arb = .(vline_arb), |
854 | ! |
vline_arb_label = .(vline_arb_label), |
855 | ! |
vline_arb_color = .(vline_arb_color), |
856 | ! |
vline_vars = .(vline_vars), |
857 | ! |
vline_vars_colors = .(vline_vars_colors[seq_along(vline_vars)]), |
858 | ! |
vline_vars_labels = .(paste(vline_vars_labels[seq_along(vline_vars)], "-", xaxis_param)) |
859 |
) |
|
860 | ! |
print(p) |
861 |
}) |
|
862 |
) |
|
863 |
}) |
|
864 | ||
865 | ! |
plot_r <- reactive(plot_q()[["p"]]) |
866 | ||
867 | ! |
plot_data <- teal.widgets::plot_with_settings_srv( |
868 | ! |
id = "plot", |
869 | ! |
plot_r = plot_r, |
870 | ! |
height = plot_height, |
871 | ! |
width = plot_width, |
872 | ! |
brushing = TRUE |
873 |
) |
|
874 | ||
875 | ||
876 |
### REPORTER |
|
877 | ! |
if (with_reporter) { |
878 | ! |
card_fun <- function(comment) { |
879 | ! |
card <- teal::TealReportCard$new() |
880 | ! |
card$set_name("Correlation Plot") |
881 | ! |
card$append_text("Correlation Plot", "header2") |
882 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
883 | ! |
card$append_text("Selected Options", "header3") |
884 | ! |
card$append_text( |
885 | ! |
paste( |
886 | ! |
formatted_data_constraint(input$constraint_var, input$constraint_range_min, input$constraint_range_max), |
887 | ! |
"\nTreatment Variable Faceting:", |
888 | ! |
input$trt_facet, |
889 | ! |
"\nRegression Line:", |
890 | ! |
input$reg_line |
891 |
), |
|
892 | ! |
style = "verbatim" |
893 |
) |
|
894 | ! |
card$append_text("Plot", "header3") |
895 | ! |
card$append_plot(plot_r(), dim = plot_data$dim()) |
896 | ! |
if (!comment == "") { |
897 | ! |
card$append_text("Comment", "header3") |
898 | ! |
card$append_text(comment) |
899 |
} |
|
900 | ! |
card$append_src(paste(teal.code::get_code(plot_q()), collapse = "\n")) |
901 | ! |
card |
902 |
} |
|
903 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) |
904 |
} |
|
905 |
### |
|
906 | ||
907 |
# highlight plot area |
|
908 | ! |
output$brush_data <- DT::renderDataTable({ |
909 | ! |
req(iv_r()$is_valid()) |
910 | ! |
plot_brush <- plot_data$brush() |
911 | ||
912 | ! |
ANL_TRANSPOSED <- isolate(plot_data_transpose()$ANL_TRANSPOSED) # nolint |
913 | ||
914 | ! |
df <- teal.widgets::clean_brushedPoints( |
915 | ! |
dplyr::select( |
916 | ! |
ANL_TRANSPOSED, "USUBJID", dplyr::all_of(input$trt_group), "AVISITCD", |
917 | ! |
dplyr::all_of(c(xvar(), yvar())), "LOQFL_COMB" |
918 |
), |
|
919 | ! |
plot_brush |
920 |
) |
|
921 | ||
922 | ! |
numeric_cols <- names(dplyr::select_if(df, is.numeric)) |
923 | ||
924 | ! |
DT::datatable(df, rownames = FALSE, options = list(scrollX = TRUE)) %>% |
925 | ! |
DT::formatRound(numeric_cols, 4) |
926 |
}) |
|
927 | ||
928 | ! |
teal.widgets::verbatim_popup_srv( |
929 | ! |
id = "warning", |
930 | ! |
verbatim_content = reactive(teal.code::get_warnings(plot_q())), |
931 | ! |
title = "Warning", |
932 | ! |
disabled = reactive(is.null(teal.code::get_warnings(plot_q()))) |
933 |
) |
|
934 | ||
935 | ! |
teal.widgets::verbatim_popup_srv( |
936 | ! |
id = "rcode", |
937 | ! |
verbatim_content = reactive(teal.code::get_code(plot_q())), |
938 | ! |
title = "Show R Code for Correlation Plot" |
939 |
) |
|
940 |
}) |
|
941 |
} |
1 |
keep_range_slider_updated <- function(session, |
|
2 |
input, |
|
3 |
update_slider_fcn, |
|
4 |
id_var, |
|
5 |
id_param_var, |
|
6 |
reactive_ANL, # nolint |
|
7 |
is_density = FALSE) { |
|
8 | ! |
stopifnot(is.function(update_slider_fcn)) |
9 | ||
10 | ! |
observe({ |
11 | ! |
varname <- input[[id_var]] |
12 | ! |
validate(need(varname, "Please select variable")) |
13 | ! |
paramname <- input[[id_param_var]] |
14 | ! |
validate(need(paramname, "Please select variable")) |
15 | ! |
req(length(paramname) == 1) |
16 | ||
17 |
# we need id_param_var (e.g. ALT) to filter down because the y-axis may have a different |
|
18 |
# param var and the range of id_var (e.g. BASE) values may be larger due to this |
|
19 |
# therefore, we need to filter |
|
20 | ! |
ANL <- reactive_ANL()$ANL %>% dplyr::filter(.data$PARAMCD == paramname) # nolint |
21 | ! |
validate_has_variable(ANL, varname, paste("variable", varname, "does not exist")) |
22 | ||
23 | ! |
var <- stats::na.omit(ANL[[varname]]) |
24 | ! |
minmax <- if (length(var)) c(floor(min(var)), ceiling(max(var))) else c(0, 0) |
25 | ! |
step <- NULL |
26 | ||
27 | ! |
if (isTRUE(is_density)) { |
28 | ! |
minmax <- c(0, round(max(stats::density(stats::na.omit(ANL[[varname]]))$y) * 1.5, 5)) |
29 | ! |
step <- round(max(stats::density(stats::na.omit(ANL[[varname]]))$y) / 100, 5) |
30 |
} |
|
31 | ||
32 | ! |
isolate(update_slider_fcn( |
33 | ! |
min = minmax[[1]], |
34 | ! |
max = minmax[[2]], |
35 | ! |
value = minmax, |
36 | ! |
step = step |
37 |
)) |
|
38 |
}) |
|
39 |
} |
1 |
#' Density Distribution Plot |
|
2 |
#' |
|
3 |
#' This teal module renders the UI and calls the functions that create a density distribution plot |
|
4 |
#' and an accompanying summary table. |
|
5 |
#' |
|
6 |
#' @param label menu item label of the module in the teal app. |
|
7 |
#' @param trt_group \code{\link[teal.transform]{choices_selected}} object with available choices and pre-selected option |
|
8 |
#' for variable names representing treatment group e.g. `ARM`. |
|
9 |
#' @param color_manual vector of colors applied to treatment values. |
|
10 |
#' @param color_comb name or hex value for combined treatment color. |
|
11 |
#' @param plot_height controls plot height. |
|
12 |
#' @param plot_width optional, controls plot width. |
|
13 |
#' @param font_size font size control for title, `x-axis` label, `y-axis` label and legend. |
|
14 |
#' @param line_size plot line thickness. |
|
15 |
#' @param hline_arb numeric vector of at most 2 values identifying intercepts for arbitrary horizontal lines. |
|
16 |
#' @param hline_arb_color a character vector of at most length of \code{hline_arb}. |
|
17 |
#' naming the color for the arbitrary horizontal lines. |
|
18 |
#' @param hline_arb_label a character vector of at most length of \code{hline_arb}. |
|
19 |
#' naming the label for the arbitrary horizontal lines. |
|
20 |
#' @param facet_ncol numeric value indicating number of facets per row. |
|
21 |
#' @param comb_line display combined treatment line toggle. |
|
22 |
#' @param rotate_xlab 45 degree rotation of `x-axis` values. |
|
23 |
#' |
|
24 |
#' @inheritParams teal.widgets::standard_layout |
|
25 |
#' @inheritParams tm_g_gh_scatterplot |
|
26 |
#' |
|
27 |
#' |
|
28 |
#' @author Nick Paszty (npaszty) paszty.nicholas@gene.com |
|
29 |
#' @author Balazs Toth (tothb2) toth.balazs@gene.com |
|
30 |
#' |
|
31 |
#' @details None |
|
32 |
#' |
|
33 |
#' @export |
|
34 |
#' |
|
35 |
#' @examples |
|
36 |
#' |
|
37 |
#' # Example using ADaM structure analysis dataset. |
|
38 |
#' |
|
39 |
#' library(dplyr) |
|
40 |
#' |
|
41 |
#' # original ARM value = dose value |
|
42 |
#' arm_mapping <- list( |
|
43 |
#' "A: Drug X" = "150mg QD", |
|
44 |
#' "B: Placebo" = "Placebo", |
|
45 |
#' "C: Combination" = "Combination" |
|
46 |
#' ) |
|
47 |
#' ADSL <- goshawk::rADSL |
|
48 |
#' ADLB <- goshawk::rADLB |
|
49 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
50 |
#' ADLB <- ADLB %>% |
|
51 |
#' dplyr::mutate( |
|
52 |
#' AVISITCD = dplyr::case_when( |
|
53 |
#' AVISIT == "SCREENING" ~ "SCR", |
|
54 |
#' AVISIT == "BASELINE" ~ "BL", |
|
55 |
#' grepl("WEEK", AVISIT) ~ paste("W", stringr::str_extract(AVISIT, "(?<=(WEEK ))[0-9]+")), |
|
56 |
#' TRUE ~ as.character(NA) |
|
57 |
#' ), |
|
58 |
#' AVISITCDN = dplyr::case_when( |
|
59 |
#' AVISITCD == "SCR" ~ -2, |
|
60 |
#' AVISITCD == "BL" ~ 0, |
|
61 |
#' grepl("W", AVISITCD) ~ as.numeric(gsub("[^0-9]*", "", AVISITCD)), |
|
62 |
#' TRUE ~ as.numeric(NA) |
|
63 |
#' ), |
|
64 |
#' AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN), |
|
65 |
#' TRTORD = dplyr::case_when( |
|
66 |
#' ARMCD == "ARM C" ~ 1, |
|
67 |
#' ARMCD == "ARM B" ~ 2, |
|
68 |
#' ARMCD == "ARM A" ~ 3 |
|
69 |
#' ), |
|
70 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
71 |
#' ARM = factor(ARM) %>% reorder(TRTORD), |
|
72 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
73 |
#' ACTARM = factor(ACTARM) %>% reorder(TRTORD) |
|
74 |
#' ) |
|
75 |
#' |
|
76 |
#' attr(ADLB[["ARM"]], "label") <- var_labels[["ARM"]] |
|
77 |
#' attr(ADLB[["ACTARM"]], "label") <- var_labels[["ACTARM"]] |
|
78 |
#' |
|
79 |
#' app <- teal::init( |
|
80 |
#' data = teal.data::cdisc_data( |
|
81 |
#' teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- goshawk::rADSL"), |
|
82 |
#' teal.data::cdisc_dataset( |
|
83 |
#' "ADLB", |
|
84 |
#' ADLB, |
|
85 |
#' code = "ADLB <- goshawk::rADLB |
|
86 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
87 |
#' ADLB <- ADLB %>% |
|
88 |
#' dplyr::mutate(AVISITCD = dplyr::case_when( |
|
89 |
#' AVISIT == 'SCREENING' ~ 'SCR', |
|
90 |
#' AVISIT == 'BASELINE' ~ 'BL', |
|
91 |
#' grepl('WEEK', AVISIT) ~ |
|
92 |
#' paste('W', stringr::str_extract(AVISIT, '(?<=(WEEK ))[0-9]+')), |
|
93 |
#' TRUE ~ as.character(NA)), |
|
94 |
#' AVISITCDN = dplyr::case_when( |
|
95 |
#' AVISITCD == 'SCR' ~ -2, |
|
96 |
#' AVISITCD == 'BL' ~ 0, |
|
97 |
#' grepl('W', AVISITCD) ~ as.numeric(gsub('[^0-9]*', '', AVISITCD)), |
|
98 |
#' TRUE ~ as.numeric(NA)), |
|
99 |
#' AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN), |
|
100 |
#' TRTORD = dplyr::case_when( |
|
101 |
#' ARMCD == 'ARM C' ~ 1, |
|
102 |
#' ARMCD == 'ARM B' ~ 2, |
|
103 |
#' ARMCD == 'ARM A' ~ 3), |
|
104 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
105 |
#' ARM = factor(ARM) %>% reorder(TRTORD), |
|
106 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
107 |
#' ACTARM = factor(ACTARM) %>% reorder(TRTORD)) |
|
108 |
#' attr(ADLB[['ARM']], 'label') <- var_labels[['ARM']] |
|
109 |
#' attr(ADLB[['ACTARM']], 'label') <- var_labels[['ACTARM']]", |
|
110 |
#' vars = list(arm_mapping = arm_mapping) |
|
111 |
#' ), |
|
112 |
#' check = TRUE |
|
113 |
#' ), |
|
114 |
#' modules = teal::modules( |
|
115 |
#' teal.goshawk::tm_g_gh_density_distribution_plot( |
|
116 |
#' label = "Density Distribution Plot", |
|
117 |
#' dataname = "ADLB", |
|
118 |
#' param_var = "PARAMCD", |
|
119 |
#' param = choices_selected(c("ALT", "CRP", "IGA"), "ALT"), |
|
120 |
#' xaxis_var = choices_selected(c("AVAL", "BASE", "CHG", "PCHG"), "AVAL"), |
|
121 |
#' trt_group = choices_selected(c("ARM", "ACTARM"), "ARM"), |
|
122 |
#' color_manual = c( |
|
123 |
#' "150mg QD" = "#000000", |
|
124 |
#' "Placebo" = "#3498DB", |
|
125 |
#' "Combination" = "#E74C3C" |
|
126 |
#' ), |
|
127 |
#' color_comb = "#39ff14", |
|
128 |
#' comb_line = TRUE, |
|
129 |
#' plot_height = c(500, 200, 2000), |
|
130 |
#' font_size = c(12, 8, 20), |
|
131 |
#' line_size = c(1, .25, 3), |
|
132 |
#' hline_arb = c(.02, .05), |
|
133 |
#' hline_arb_color = c("red", "black"), |
|
134 |
#' hline_arb_label = c("Horizontal Line A", "Horizontal Line B") |
|
135 |
#' ) |
|
136 |
#' ) |
|
137 |
#' ) |
|
138 |
#' if (interactive()) { |
|
139 |
#' shinyApp(app$ui, app$server) |
|
140 |
#' } |
|
141 |
tm_g_gh_density_distribution_plot <- function(label, # nolint |
|
142 |
dataname, |
|
143 |
param_var, |
|
144 |
param, |
|
145 |
xaxis_var, |
|
146 |
trt_group, |
|
147 |
color_manual = NULL, |
|
148 |
color_comb = NULL, |
|
149 |
plot_height = c(500, 200, 2000), |
|
150 |
plot_width = NULL, |
|
151 |
font_size = c(12, 8, 20), |
|
152 |
line_size = c(1, .25, 3), |
|
153 |
hline_arb = numeric(0), |
|
154 |
hline_arb_color = "red", |
|
155 |
hline_arb_label = "Horizontal line", |
|
156 |
facet_ncol = 2L, |
|
157 |
comb_line = TRUE, |
|
158 |
rotate_xlab = FALSE, |
|
159 |
pre_output = NULL, |
|
160 |
post_output = NULL) { |
|
161 | ! |
logger::log_info("Initializing tm_g_gh_density_distribution_plot") |
162 | ! |
checkmate::assert_string(label) |
163 | ! |
checkmate::assert_string(dataname) |
164 | ! |
checkmate::assert_string(param_var) |
165 | ! |
checkmate::assert_class(param, "choices_selected") |
166 | ! |
checkmate::assert_class(xaxis_var, "choices_selected") |
167 | ! |
checkmate::assert_class(trt_group, "choices_selected") |
168 | ! |
checkmate::assert_numeric(font_size, len = 3) |
169 | ! |
checkmate::assert_numeric(line_size, len = 3) |
170 | ! |
checkmate::assert_int(facet_ncol) |
171 | ! |
checkmate::assert_flag(comb_line) |
172 | ! |
checkmate::assert_flag(rotate_xlab) |
173 | ! |
validate_line_arb_arg(hline_arb, hline_arb_color, hline_arb_label) |
174 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
175 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
176 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
177 | ! |
checkmate::assert_numeric( |
178 | ! |
plot_width[1], |
179 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
180 |
) |
|
181 | ||
182 | ! |
args <- as.list(environment()) |
183 | ||
184 | ! |
module( |
185 | ! |
label = label, |
186 | ! |
datanames = dataname, |
187 | ! |
server = srv_g_density_distribution_plot, |
188 | ! |
server_args = list( |
189 | ! |
dataname = dataname, |
190 | ! |
param_var = param_var, |
191 | ! |
param = param, |
192 | ! |
trt_group = trt_group, |
193 | ! |
color_manual = color_manual, |
194 | ! |
color_comb = color_comb, |
195 | ! |
plot_height = plot_height, |
196 | ! |
plot_width = plot_width |
197 |
), |
|
198 | ! |
ui = ui_g_density_distribution_plot, |
199 | ! |
ui_args = args |
200 |
) |
|
201 |
} |
|
202 | ||
203 |
ui_g_density_distribution_plot <- function(id, ...) { |
|
204 | ! |
ns <- NS(id) |
205 | ! |
a <- list(...) |
206 | ||
207 | ! |
teal.widgets::standard_layout( |
208 | ! |
output = div( |
209 | ! |
fluidRow( |
210 | ! |
teal.widgets::plot_with_settings_ui(id = ns("plot")) |
211 |
), |
|
212 | ! |
fluidRow(column( |
213 | ! |
width = 12, |
214 | ! |
br(), hr(), |
215 | ! |
h4("Descriptive Statistics"), |
216 | ! |
DT::dataTableOutput(ns("table_ui")) |
217 |
)) |
|
218 |
), |
|
219 | ! |
encoding = div( |
220 |
### Reporter |
|
221 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")), |
222 |
### |
|
223 | ! |
templ_ui_dataname(a$dataname), |
224 | ! |
teal.widgets::optionalSelectInput( |
225 | ! |
ns("trt_group"), |
226 | ! |
label = "Select Treatment Variable", |
227 | ! |
choices = a$trt_group$choices, |
228 | ! |
selected = a$trt_group$selected, |
229 | ! |
multiple = FALSE |
230 |
), |
|
231 | ! |
templ_ui_params_vars( |
232 | ! |
ns, |
233 | ! |
xparam_choices = a$param$choices, xparam_selected = a$param$selected, xparam_label = "Select a Biomarker", |
234 | ! |
xchoices = a$xaxis_var$choices, xselected = a$xaxis_var$selected |
235 |
), |
|
236 | ! |
templ_ui_constraint(ns, label = "Data Constraint"), |
237 | ! |
ui_arbitrary_lines(id = ns("hline_arb"), a$hline_arb, a$hline_arb_label, a$hline_arb_color), |
238 | ! |
teal.widgets::panel_group( |
239 | ! |
teal.widgets::panel_item( |
240 | ! |
title = "Plot Aesthetic Settings", |
241 | ! |
toggle_slider_ui( |
242 | ! |
ns("xrange_scale"), |
243 | ! |
label = "X-Axis Range Zoom", |
244 | ! |
min = -1000000, |
245 | ! |
max = 1000000, |
246 | ! |
value = c(-1000000, 1000000) |
247 |
), |
|
248 | ! |
toggle_slider_ui( |
249 | ! |
ns("yrange_scale"), |
250 | ! |
label = "Y-Axis Range Zoom", |
251 | ! |
min = -1000000, |
252 | ! |
max = 1000000, |
253 | ! |
value = c(-1000000, 1000000) |
254 |
), |
|
255 | ! |
numericInput(ns("facet_ncol"), "Number of Plots Per Row:", a$facet_ncol, min = 1), |
256 | ! |
checkboxInput(ns("comb_line"), "Display Combined line", a$comb_line), |
257 | ! |
checkboxInput(ns("rug_plot"), "Include rug plot", value = FALSE), |
258 | ! |
checkboxInput(ns("rotate_xlab"), "Rotate X-axis Label", a$rotate_xlab) |
259 |
), |
|
260 | ! |
teal.widgets::panel_item( |
261 | ! |
title = "Plot settings", |
262 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("font_size"), "Font Size", a$font_size, ticks = FALSE), |
263 | ! |
teal.widgets::optionalSliderInputValMinMax( |
264 | ! |
ns("line_size"), |
265 | ! |
"Line Size", |
266 | ! |
value_min_max = a$line_size, |
267 | ! |
step = .25, |
268 | ! |
ticks = FALSE |
269 |
) |
|
270 |
) |
|
271 |
) |
|
272 |
), |
|
273 | ! |
forms = tagList( |
274 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"), |
275 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code") |
276 |
), |
|
277 | ! |
pre_output = a$pre_output, |
278 | ! |
post_output = a$post_output |
279 |
) |
|
280 |
} |
|
281 | ||
282 |
srv_g_density_distribution_plot <- function(id, # nolint |
|
283 |
data, |
|
284 |
reporter, |
|
285 |
filter_panel_api, |
|
286 |
dataname, |
|
287 |
param_var, |
|
288 |
param, |
|
289 |
trt_group, |
|
290 |
color_manual, |
|
291 |
color_comb, |
|
292 |
plot_height, |
|
293 |
plot_width) { |
|
294 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
295 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
296 | ! |
checkmate::assert_class(data, "tdata") |
297 | ||
298 | ! |
moduleServer(id, function(input, output, session) { |
299 | ! |
anl_q_output <- constr_anl_q( |
300 | ! |
session, input, data, dataname, |
301 | ! |
param_id = "xaxis_param", param_var = param_var, trt_group = input$trt_group, min_rows = 2 |
302 |
) |
|
303 | ||
304 | ! |
anl_q <- anl_q_output()$value |
305 | ||
306 |
# update sliders for axes taking constraints into account |
|
307 | ! |
xrange_slider <- toggle_slider_server("xrange_scale") |
308 | ! |
yrange_slider <- toggle_slider_server("yrange_scale") |
309 | ! |
keep_range_slider_updated(session, input, xrange_slider$update_state, "xaxis_var", "xaxis_param", anl_q) |
310 | ! |
keep_range_slider_updated( |
311 | ! |
session, |
312 | ! |
input, |
313 | ! |
yrange_slider$update_state, |
314 | ! |
"xaxis_var", |
315 | ! |
"xaxis_param", |
316 | ! |
anl_q, |
317 | ! |
is_density = TRUE |
318 |
) |
|
319 | ! |
keep_data_const_opts_updated(session, input, anl_q, "xaxis_param") |
320 | ||
321 | ! |
horizontal_line <- srv_arbitrary_lines("hline_arb") |
322 | ||
323 | ! |
iv_r <- reactive({ |
324 | ! |
iv <- shinyvalidate::InputValidator$new() |
325 | ||
326 | ! |
iv$add_rule("xaxis_param", shinyvalidate::sv_required("Please select a biomarker")) |
327 | ! |
iv$add_rule("trt_group", shinyvalidate::sv_required("Please select a treatment variable")) |
328 | ! |
iv$add_rule("xaxis_var", shinyvalidate::sv_required("Please select an X-Axis variable")) |
329 | ! |
iv$add_rule("facet_ncol", plots_per_row_validate_rules()) |
330 | ||
331 | ! |
iv$add_validator(horizontal_line()$iv_r()) |
332 | ! |
iv$add_validator(anl_q_output()$iv_r()) |
333 | ! |
iv$enable() |
334 | ! |
iv |
335 |
}) |
|
336 | ||
337 | ||
338 | ! |
create_plot <- reactive({ |
339 | ! |
teal::validate_inputs(iv_r()) |
340 | ! |
req(anl_q()) |
341 | ||
342 |
# nolint start |
|
343 | ! |
param <- input$xaxis_param |
344 | ! |
xaxis_var <- input$xaxis_var |
345 | ! |
xlim <- xrange_slider$state()$value |
346 | ! |
ylim <- yrange_slider$state()$value |
347 | ! |
font_size <- input$font_size |
348 | ! |
line_size <- input$line_size |
349 | ! |
hline_arb <- horizontal_line()$line_arb |
350 | ! |
hline_arb_label <- horizontal_line()$line_arb_label |
351 | ! |
hline_arb_color <- horizontal_line()$line_arb_color |
352 | ! |
facet_ncol <- input$facet_ncol |
353 | ||
354 | ! |
comb_line <- input$comb_line |
355 | ! |
rug_plot <- input$rug_plot |
356 | ! |
rotate_xlab <- input$rotate_xlab |
357 | ! |
trt_group <- input$trt_group |
358 |
# nolint end |
|
359 | ||
360 | ||
361 | ! |
teal.code::eval_code( |
362 | ! |
object = anl_q()$qenv, |
363 | ! |
code = bquote({ |
364 | ! |
p <- goshawk::g_density_distribution_plot( |
365 | ! |
data = ANL, |
366 | ! |
param_var = .(param_var), |
367 | ! |
param = .(param), |
368 | ! |
xaxis_var = .(xaxis_var), |
369 | ! |
trt_group = .(trt_group), |
370 | ! |
xlim = .(xlim), |
371 | ! |
ylim = .(ylim), |
372 | ! |
color_manual = .(color_manual), |
373 | ! |
color_comb = .(color_comb), |
374 | ! |
font_size = .(font_size), |
375 | ! |
line_size = .(line_size), |
376 | ! |
facet_ncol = .(facet_ncol), |
377 | ! |
comb_line = .(comb_line), |
378 | ! |
hline_arb = .(hline_arb), |
379 | ! |
hline_arb_label = .(hline_arb_label), |
380 | ! |
hline_arb_color = .(hline_arb_color), |
381 | ! |
rug_plot = .(rug_plot) |
382 |
) |
|
383 |
}) |
|
384 |
) |
|
385 |
}) |
|
386 | ||
387 | ! |
create_table <- reactive({ |
388 | ! |
req(iv_r()$is_valid()) |
389 | ! |
req(anl_q()) |
390 | ! |
param <- input$xaxis_param |
391 | ! |
xaxis_var <- input$xaxis_var |
392 | ! |
font_size <- input$font_size |
393 | ! |
trt_group <- input$trt_group |
394 | ||
395 | ! |
teal.code::eval_code( |
396 | ! |
object = anl_q()$qenv, |
397 | ! |
code = bquote( |
398 | ! |
tbl <- goshawk::t_summarytable( |
399 | ! |
data = ANL, |
400 | ! |
trt_group = .(trt_group), |
401 | ! |
param_var = .(param_var), |
402 | ! |
param = .(param), |
403 | ! |
xaxis_var = .(xaxis_var), |
404 | ! |
font_size = .(font_size) |
405 |
) |
|
406 |
) |
|
407 |
) |
|
408 |
}) |
|
409 | ||
410 | ! |
plot_r <- reactive({ |
411 | ! |
create_plot()[["p"]] |
412 |
}) |
|
413 | ||
414 | ! |
plot_data <- teal.widgets::plot_with_settings_srv( |
415 | ! |
id = "plot", |
416 | ! |
plot_r = plot_r, |
417 | ! |
height = plot_height, |
418 | ! |
width = plot_width, |
419 |
) |
|
420 | ||
421 | ! |
output$table_ui <- DT::renderDataTable({ |
422 | ! |
req(create_table()) |
423 | ! |
tbl <- create_table()[["tbl"]] |
424 | ! |
numeric_cols <- names(dplyr::select_if(tbl, is.numeric)) |
425 | ||
426 | ! |
DT::datatable(tbl, rownames = FALSE, options = list(scrollX = TRUE)) %>% |
427 | ! |
DT::formatRound(numeric_cols, 2) |
428 |
}) |
|
429 | ||
430 | ! |
joined_qenvs <- reactive({ |
431 | ! |
req(create_plot(), create_table()) |
432 | ! |
teal.code::join(create_plot(), create_table()) |
433 |
}) |
|
434 | ||
435 | ! |
teal.widgets::verbatim_popup_srv( |
436 | ! |
id = "warning", |
437 | ! |
verbatim_content = reactive(teal.code::get_warnings(joined_qenvs())), |
438 | ! |
title = "Warning", |
439 | ! |
disabled = reactive(is.null(teal.code::get_warnings(joined_qenvs()))) |
440 |
) |
|
441 | ||
442 | ! |
teal.widgets::verbatim_popup_srv( |
443 | ! |
id = "rcode", |
444 | ! |
verbatim_content = reactive( |
445 | ! |
teal.code::get_code(joined_qenvs()) |
446 |
), |
|
447 | ! |
title = "Show R Code for Density Distribution Plot" |
448 |
) |
|
449 | ||
450 |
### REPORTER |
|
451 | ! |
if (with_reporter) { |
452 | ! |
card_fun <- function(comment) { |
453 | ! |
card <- teal::TealReportCard$new() |
454 | ! |
card$set_name("Density Distribution Plot") |
455 | ! |
card$append_text("Density Distribution Plot", "header2") |
456 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
457 | ! |
card$append_text("Selected Options", "header3") |
458 | ! |
card$append_text( |
459 | ! |
formatted_data_constraint(input$constraint_var, input$constraint_range_min, input$constraint_range_max) |
460 |
) |
|
461 | ! |
card$append_text("Plot", "header3") |
462 | ! |
card$append_plot(plot_r(), dim = plot_data$dim()) |
463 | ! |
card$append_text("Descriptive Statistics", "header3") |
464 | ! |
card$append_table( |
465 | ! |
create_table()[["tbl"]] %>% dplyr::mutate_if(is.numeric, round, 2) |
466 |
) |
|
467 | ! |
if (!comment == "") { |
468 | ! |
card$append_text("Comment", "header3") |
469 | ! |
card$append_text(comment) |
470 |
} |
|
471 | ! |
card$append_src( |
472 | ! |
paste( |
473 | ! |
teal.code::get_code( |
474 | ! |
teal.code::join(create_plot(), create_table()) |
475 |
), |
|
476 | ! |
collapse = "\n" |
477 |
) |
|
478 |
) |
|
479 | ! |
card |
480 |
} |
|
481 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) |
482 |
} |
|
483 |
### |
|
484 |
}) |
|
485 |
} |
1 |
#' Scatter Plot Teal Module For Biomarker Analysis |
|
2 |
#' |
|
3 |
#' @description |
|
4 |
#' `r lifecycle::badge("deprecated")` |
|
5 |
#' |
|
6 |
#' `tm_g_gh_scatterplot` is deprecated. Please use [tm_g_gh_correlationplot] |
|
7 |
#' instead. |
|
8 |
#' |
|
9 |
#' @inheritParams teal.widgets::standard_layout |
|
10 |
#' @param label menu item label of the module in the teal app. |
|
11 |
#' @param dataname analysis data passed to the data argument of \code{\link[teal]{init}}. E.g. `ADaM` structured |
|
12 |
#' laboratory data frame \code{ADLB}. |
|
13 |
#' @param param_var name of variable containing biomarker codes e.g. \code{PARAMCD}. |
|
14 |
#' @param param biomarker selected. |
|
15 |
#' @param xaxis_var name of variable containing biomarker results displayed on `x-axis` e.g. \code{BASE}. |
|
16 |
#' @param yaxis_var name of variable containing biomarker results displayed on `y-axis` e.g. \code{AVAL}. |
|
17 |
#' @param trt_group \code{\link[teal.transform]{choices_selected}} object with available choices and pre-selected option |
|
18 |
#' for variable names representing treatment group e.g. `ARM`. |
|
19 |
#' @param color_manual vector of colors applied to treatment values. |
|
20 |
#' @param shape_manual vector of symbols applied to `LOQ` values. |
|
21 |
#' @param facet_ncol numeric value indicating number of facets per row. |
|
22 |
#' @param trt_facet facet by treatment group \code{trt_group}. |
|
23 |
#' @param reg_line include regression line and annotations for slope and coefficient in visualization. Use with facet |
|
24 |
#' TRUE. |
|
25 |
#' @param rotate_xlab 45 degree rotation of `x-axis` values. |
|
26 |
#' @param hline y-axis value to position of horizontal line. |
|
27 |
#' @param vline x-axis value to position a vertical line. |
|
28 |
#' @param plot_height controls plot height. |
|
29 |
#' @param plot_width optional, controls plot width. |
|
30 |
#' @param font_size font size control for title, `x-axis` label, `y-axis` label and legend. |
|
31 |
#' @param dot_size plot dot size. |
|
32 |
#' @param reg_text_size font size control for regression line annotations. |
|
33 |
#' |
|
34 |
#' |
|
35 |
#' @export |
|
36 |
#' |
|
37 |
#' @author Nick Paszty (npaszty) paszty.nicholas@gene.com |
|
38 |
#' @author Balazs Toth (tothb2) toth.balazs@gene.com |
|
39 |
#' |
|
40 |
#' @examples |
|
41 |
#' # Example using ADaM structure analysis dataset. |
|
42 |
#' |
|
43 |
#' # original ARM value = dose value |
|
44 |
#' arm_mapping <- list( |
|
45 |
#' "A: Drug X" = "150mg QD", |
|
46 |
#' "B: Placebo" = "Placebo", |
|
47 |
#' "C: Combination" = "Combination" |
|
48 |
#' ) |
|
49 |
#' |
|
50 |
#' ADSL <- goshawk::rADSL |
|
51 |
#' ADLB <- goshawk::rADLB |
|
52 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
53 |
#' ADLB <- ADLB %>% |
|
54 |
#' dplyr::mutate( |
|
55 |
#' AVISITCD = dplyr::case_when( |
|
56 |
#' AVISIT == "SCREENING" ~ "SCR", |
|
57 |
#' AVISIT == "BASELINE" ~ "BL", |
|
58 |
#' grepl("WEEK", AVISIT) ~ paste("W", stringr::str_extract(AVISIT, "(?<=(WEEK ))[0-9]+")), |
|
59 |
#' TRUE ~ as.character(NA) |
|
60 |
#' ), |
|
61 |
#' AVISITCDN = dplyr::case_when( |
|
62 |
#' AVISITCD == "SCR" ~ -2, |
|
63 |
#' AVISITCD == "BL" ~ 0, |
|
64 |
#' grepl("W", AVISITCD) ~ as.numeric(gsub("[^0-9]*", "", AVISITCD)), |
|
65 |
#' TRUE ~ as.numeric(NA) |
|
66 |
#' ), |
|
67 |
#' AVISITCD = factor(AVISITCD) %>% stats::reorder(AVISITCDN), |
|
68 |
#' TRTORD = dplyr::case_when( |
|
69 |
#' ARMCD == "ARM C" ~ 1, |
|
70 |
#' ARMCD == "ARM B" ~ 2, |
|
71 |
#' ARMCD == "ARM A" ~ 3 |
|
72 |
#' ), |
|
73 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
74 |
#' ARM = factor(ARM) %>% stats::reorder(TRTORD), |
|
75 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
76 |
#' ACTARM = factor(ACTARM) %>% stats::reorder(TRTORD) |
|
77 |
#' ) |
|
78 |
#' attr(ADLB[["ARM"]], "label") <- var_labels[["ARM"]] |
|
79 |
#' attr(ADLB[["ACTARM"]], "label") <- var_labels[["ACTARM"]] |
|
80 |
#' |
|
81 |
#' app <- teal::init( |
|
82 |
#' data = teal.data::cdisc_data( |
|
83 |
#' adsl <- teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- goshawk::rADSL"), |
|
84 |
#' teal.data::cdisc_dataset( |
|
85 |
#' "ADLB", |
|
86 |
#' ADLB, |
|
87 |
#' code = "ADLB <- goshawk::rADLB |
|
88 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
89 |
#' ADLB <- ADLB %>% |
|
90 |
#' dplyr::mutate(AVISITCD = dplyr::case_when( |
|
91 |
#' AVISIT == 'SCREENING' ~ 'SCR', |
|
92 |
#' AVISIT == 'BASELINE' ~ 'BL', |
|
93 |
#' grepl('WEEK', AVISIT) ~ |
|
94 |
#' paste('W', stringr::str_extract(AVISIT, '(?<=(WEEK ))[0-9]+')), |
|
95 |
#' TRUE ~ as.character(NA)), |
|
96 |
#' AVISITCDN = dplyr::case_when( |
|
97 |
#' AVISITCD == 'SCR' ~ -2, |
|
98 |
#' AVISITCD == 'BL' ~ 0, |
|
99 |
#' grepl('W', AVISITCD) ~ as.numeric(gsub('[^0-9]*', '', AVISITCD)), |
|
100 |
#' TRUE ~ as.numeric(NA)), |
|
101 |
#' AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN), |
|
102 |
#' TRTORD = dplyr::case_when( |
|
103 |
#' ARMCD == 'ARM C' ~ 1, |
|
104 |
#' ARMCD == 'ARM B' ~ 2, |
|
105 |
#' ARMCD == 'ARM A' ~ 3), |
|
106 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
107 |
#' ARM = factor(ARM) %>% reorder(TRTORD), |
|
108 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
109 |
#' ACTARM = factor(ACTARM) %>% reorder(TRTORD)) |
|
110 |
#' attr(ADLB[['ARM']], 'label') <- var_labels[['ARM']] |
|
111 |
#' attr(ADLB[['ACTARM']], 'label') <- var_labels[['ACTARM']]", |
|
112 |
#' vars = list(ADSL = adsl, arm_mapping = arm_mapping) |
|
113 |
#' ), |
|
114 |
#' check = TRUE |
|
115 |
#' ), |
|
116 |
#' modules = teal::modules( |
|
117 |
#' teal.goshawk::tm_g_gh_scatterplot( |
|
118 |
#' label = "Scatter Plot", |
|
119 |
#' dataname = "ADLB", |
|
120 |
#' param_var = "PARAMCD", |
|
121 |
#' param = choices_selected(c("ALT", "CRP", "IGA"), "ALT"), |
|
122 |
#' xaxis_var = choices_selected(c("AVAL", "BASE", "CHG", "PCHG"), "BASE"), |
|
123 |
#' yaxis_var = choices_selected(c("AVAL", "BASE", "CHG", "PCHG"), "AVAL"), |
|
124 |
#' trt_group = choices_selected(c("ARM", "ACTARM"), "ARM"), |
|
125 |
#' color_manual = c( |
|
126 |
#' "150mg QD" = "#000000", |
|
127 |
#' "Placebo" = "#3498DB", |
|
128 |
#' "Combination" = "#E74C3C" |
|
129 |
#' ), |
|
130 |
#' shape_manual = c("N" = 1, "Y" = 2, "NA" = 0), |
|
131 |
#' plot_height = c(500, 200, 2000), |
|
132 |
#' facet_ncol = 2, |
|
133 |
#' trt_facet = FALSE, |
|
134 |
#' reg_line = FALSE, |
|
135 |
#' font_size = c(12, 8, 20), |
|
136 |
#' dot_size = c(1, 1, 12), |
|
137 |
#' reg_text_size = c(3, 3, 10) |
|
138 |
#' ) |
|
139 |
#' ) |
|
140 |
#' ) |
|
141 |
#' if (interactive()) { |
|
142 |
#' shinyApp(app$ui, app$server) |
|
143 |
#' } |
|
144 |
#' |
|
145 |
tm_g_gh_scatterplot <- function(label, |
|
146 |
dataname, |
|
147 |
param_var, |
|
148 |
param, |
|
149 |
xaxis_var, |
|
150 |
yaxis_var, |
|
151 |
trt_group, |
|
152 |
color_manual = NULL, |
|
153 |
shape_manual = NULL, |
|
154 |
facet_ncol = 2, |
|
155 |
trt_facet = FALSE, |
|
156 |
reg_line = FALSE, |
|
157 |
rotate_xlab = FALSE, |
|
158 |
hline = NULL, |
|
159 |
vline = NULL, |
|
160 |
plot_height = c(500, 200, 2000), |
|
161 |
plot_width = NULL, |
|
162 |
font_size = c(12, 8, 20), |
|
163 |
dot_size = c(1, 1, 12), |
|
164 |
reg_text_size = c(3, 3, 10), |
|
165 |
pre_output = NULL, |
|
166 |
post_output = NULL) { |
|
167 | ! |
lifecycle::deprecate_soft( |
168 | ! |
when = "0.1.15", |
169 | ! |
what = "tm_g_gh_scatterplot()", |
170 | ! |
details = "You should use teal.goshawk::tm_g_gh_correlationplot instead of teal.goshawk::tm_g_gh_scatterplot" |
171 |
) |
|
172 | ||
173 | ! |
logger::log_info("Initializing tm_g_gh_scatterplot") |
174 | ! |
checkmate::assert_class(param, "choices_selected") |
175 | ! |
checkmate::assert_class(xaxis_var, "choices_selected") |
176 | ! |
checkmate::assert_class(yaxis_var, "choices_selected") |
177 | ! |
checkmate::assert_class(trt_group, "choices_selected") |
178 | ! |
checkmate::assert_flag(trt_facet) |
179 | ! |
checkmate::assert_flag(reg_line) |
180 | ! |
checkmate::assert_flag(rotate_xlab) |
181 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
182 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
183 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
184 | ! |
checkmate::assert_numeric( |
185 | ! |
plot_width[1], |
186 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
187 |
) |
|
188 | ||
189 | ! |
args <- as.list(environment()) |
190 | ||
191 | ! |
module( |
192 | ! |
label = label, |
193 | ! |
datanames = dataname, |
194 | ! |
server = srv_g_scatterplot, |
195 | ! |
server_args = list( |
196 | ! |
dataname = dataname, |
197 | ! |
param_var = param_var, |
198 | ! |
trt_group = trt_group, |
199 | ! |
trt_facet = trt_facet, |
200 | ! |
color_manual = color_manual, |
201 | ! |
shape_manual = shape_manual, |
202 | ! |
plot_height = plot_height, |
203 | ! |
plot_width = plot_width |
204 |
), |
|
205 | ! |
ui = ui_g_scatterplot, |
206 | ! |
ui_args = args |
207 |
) |
|
208 |
} |
|
209 | ||
210 |
ui_g_scatterplot <- function(id, ...) { |
|
211 | ! |
ns <- NS(id) |
212 | ! |
a <- list(...) |
213 | ||
214 | ! |
teal.widgets::standard_layout( |
215 | ! |
output = templ_ui_output_datatable(ns), |
216 | ! |
encoding = div( |
217 |
### Reporter |
|
218 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")), |
219 |
### |
|
220 | ! |
templ_ui_dataname(a$dataname), |
221 | ! |
teal.widgets::optionalSelectInput( |
222 | ! |
ns("trt_group"), |
223 | ! |
label = "Select Treatment Variable", |
224 | ! |
choices = a$trt_group$choices, |
225 | ! |
selected = a$trt_group$selected, |
226 | ! |
multiple = FALSE |
227 |
), |
|
228 | ! |
templ_ui_params_vars( |
229 | ! |
ns, |
230 |
# xparam and yparam are identical, so we only show the user one |
|
231 | ! |
xparam_choices = a$param$choices, xparam_selected = a$param$selected, xparam_label = "Select a Biomarker", |
232 | ! |
xchoices = a$xaxis_var$choices, xselected = a$xaxis_var$selected, |
233 | ! |
ychoices = a$yaxis_var$choices, yselected = a$yaxis_var$selected |
234 |
), |
|
235 | ! |
templ_ui_constraint(ns), # required by constr_anl_q |
236 | ! |
teal.widgets::panel_group( |
237 | ! |
teal.widgets::panel_item( |
238 | ! |
title = "Plot Aesthetic Settings", |
239 | ! |
toggle_slider_ui(ns("xrange_scale"), |
240 | ! |
label = "X-Axis Range Zoom", |
241 | ! |
min = -1000000, |
242 | ! |
max = 1000000, |
243 | ! |
value = c(-1000000, 1000000) |
244 |
), |
|
245 | ! |
toggle_slider_ui(ns("yrange_scale"), |
246 | ! |
label = "Y-Axis Range Zoom", |
247 | ! |
min = -1000000, |
248 | ! |
max = 1000000, |
249 | ! |
value = c(-1000000, 1000000) |
250 |
), |
|
251 | ! |
numericInput(ns("facet_ncol"), "Number of Plots Per Row:", a$facet_ncol, min = 1), |
252 | ! |
checkboxInput(ns("trt_facet"), "Treatment Variable Faceting", a$trt_facet), |
253 | ! |
checkboxInput(ns("reg_line"), "Regression Line", a$reg_line), |
254 | ! |
checkboxInput(ns("rotate_xlab"), "Rotate X-axis Label", a$rotate_xlab), |
255 | ! |
numericInput(ns("hline"), "Add a horizontal line:", a$hline), |
256 | ! |
numericInput(ns("vline"), "Add a vertical line:", a$vline) |
257 |
), |
|
258 | ! |
teal.widgets::panel_item( |
259 | ! |
title = "Plot settings", |
260 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("font_size"), "Font Size", a$font_size, ticks = FALSE), |
261 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("dot_size"), "Dot Size", a$dot_size, ticks = FALSE), |
262 | ! |
teal.widgets::optionalSliderInputValMinMax( |
263 | ! |
ns("reg_text_size"), |
264 | ! |
"Regression Annotations Size", |
265 | ! |
a$reg_text_size, |
266 | ! |
ticks = FALSE |
267 |
) |
|
268 |
) |
|
269 |
) |
|
270 |
), |
|
271 | ! |
forms = tagList( |
272 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"), |
273 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code") |
274 |
), |
|
275 | ! |
pre_output = a$pre_output, |
276 | ! |
post_output = a$post_output |
277 |
) |
|
278 |
} |
|
279 | ||
280 |
srv_g_scatterplot <- function(id, |
|
281 |
data, |
|
282 |
reporter, |
|
283 |
filter_panel_api, |
|
284 |
dataname, |
|
285 |
param_var, |
|
286 |
trt_group, |
|
287 |
trt_facet, |
|
288 |
color_manual, |
|
289 |
shape_manual, |
|
290 |
plot_height, |
|
291 |
plot_width) { |
|
292 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
293 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
294 | ! |
checkmate::assert_class(data, "tdata") |
295 | ||
296 | ! |
moduleServer(id, function(input, output, session) { |
297 |
# reused in all modules |
|
298 | ! |
anl_q_output <- constr_anl_q( |
299 | ! |
session, input, data, dataname, |
300 | ! |
param_id = "xaxis_param", param_var = param_var, trt_group = input$trt_group, min_rows = 1 |
301 |
) |
|
302 | ||
303 | ! |
anl_q <- anl_q_output()$value |
304 | ||
305 |
# update sliders for axes taking constraints into account |
|
306 | ! |
xrange_slider <- toggle_slider_server("xrange_scale") |
307 | ! |
yrange_slider <- toggle_slider_server("yrange_scale") |
308 | ! |
keep_range_slider_updated(session, input, xrange_slider$update_state, "xaxis_var", "xaxis_param", anl_q) |
309 | ! |
keep_range_slider_updated(session, input, yrange_slider$update_state, "yaxis_var", "xaxis_param", anl_q) |
310 | ! |
keep_data_const_opts_updated(session, input, anl_q, "xaxis_param") |
311 | ||
312 |
# plot |
|
313 | ! |
plot_q <- reactive({ |
314 | ! |
req(anl_q()) |
315 |
# nolint start |
|
316 | ! |
xlim <- xrange_slider$state()$value |
317 | ! |
ylim <- yrange_slider$state()$value |
318 | ! |
facet_ncol <- input$facet_ncol |
319 | ! |
validate(need( |
320 | ! |
is.na(facet_ncol) || (as.numeric(facet_ncol) > 0 && as.numeric(facet_ncol) %% 1 == 0), |
321 | ! |
"Number of plots per row must be a positive integer" |
322 |
)) |
|
323 | ! |
reg_line <- input$reg_line |
324 | ! |
font_size <- input$font_size |
325 | ! |
dot_size <- input$dot_size |
326 | ! |
reg_text_size <- input$reg_text_size |
327 | ! |
rotate_xlab <- input$rotate_xlab |
328 | ! |
hline <- input$hline |
329 | ! |
vline <- input$vline |
330 | ! |
trt_group <- input$trt_group |
331 | ! |
facet <- input$trt_facet |
332 | ! |
validate(need(trt_group, "Please select a treatment variable")) |
333 | ||
334 |
# Below inputs should trigger plot via updates of other reactive objects (i.e. anl_q()) and some inputs |
|
335 | ! |
validate(need(input$xaxis_var, "Please select an X-Axis Variable")) |
336 | ! |
validate(need(input$yaxis_var, "Please select a Y-Axis Variable")) |
337 | ! |
param <- input$xaxis_param |
338 | ! |
xaxis <- input$xaxis_var |
339 | ! |
yaxis <- input$yaxis_var |
340 | ||
341 |
# nolint end |
|
342 | ! |
teal.code::eval_code( |
343 | ! |
object = anl_q()$qenv, |
344 | ! |
code = bquote({ |
345 |
# re-establish treatment variable label |
|
346 | ! |
p <- goshawk::g_scatterplot( |
347 | ! |
data = ANL, |
348 | ! |
param_var = .(param_var), |
349 | ! |
param = .(param), |
350 | ! |
xaxis_var = .(xaxis), |
351 | ! |
yaxis_var = .(yaxis), |
352 | ! |
trt_group = .(trt_group), |
353 | ! |
xlim = .(xlim), |
354 | ! |
ylim = .(ylim), |
355 | ! |
color_manual = .(color_manual), |
356 | ! |
shape_manual = .(shape_manual), |
357 | ! |
facet_ncol = .(facet_ncol), |
358 | ! |
facet = .(facet), |
359 | ! |
facet_var = .(trt_group), |
360 | ! |
reg_line = .(reg_line), |
361 | ! |
font_size = .(font_size), |
362 | ! |
dot_size = .(dot_size), |
363 | ! |
reg_text_size = .(reg_text_size), |
364 | ! |
rotate_xlab = .(rotate_xlab), |
365 | ! |
hline = .(`if`(is.na(hline), NULL, as.numeric(hline))), |
366 | ! |
vline = .(`if`(is.na(vline), NULL, as.numeric(vline))) |
367 |
) |
|
368 | ! |
print(p) |
369 |
}) |
|
370 |
) |
|
371 |
}) |
|
372 | ||
373 | ! |
plot_r <- reactive(plot_q()[["p"]]) |
374 | ||
375 | ! |
plot_data <- teal.widgets::plot_with_settings_srv( |
376 | ! |
id = "plot", |
377 | ! |
plot_r = plot_r, |
378 | ! |
height = plot_height, |
379 | ! |
width = plot_width, |
380 | ! |
brushing = TRUE |
381 |
) |
|
382 | ||
383 |
### REPORTER |
|
384 | ! |
if (with_reporter) { |
385 | ! |
card_fun <- function(comment) { |
386 | ! |
card <- teal::TealReportCard$new() |
387 | ! |
card$set_name("Scatter Plot") |
388 | ! |
card$append_text("Scatter Plot", "header2") |
389 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
390 | ! |
card$append_text("Selected Options", "header3") |
391 | ! |
card$append_text( |
392 | ! |
paste( |
393 | ! |
formatted_data_constraint(input$constraint_var, input$constraint_range_min, input$constraint_range_max), |
394 | ! |
"\nTreatment Variable Faceting:", |
395 | ! |
input$trt_facet, |
396 | ! |
"\nRegression Line:", |
397 | ! |
input$reg_line |
398 |
), |
|
399 | ! |
style = "verbatim" |
400 |
) |
|
401 | ! |
card$append_text("Scatter Plot", "header3") |
402 | ! |
card$append_plot(plot_r(), dim = plot_data$dim()) |
403 | ! |
if (!comment == "") { |
404 | ! |
card$append_text("Comment", "header3") |
405 | ! |
card$append_text(comment) |
406 |
} |
|
407 | ! |
card$append_src(paste(teal.code::get_code(plot_q()), collapse = "\n")) |
408 | ! |
card |
409 |
} |
|
410 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) |
411 |
} |
|
412 |
### |
|
413 | ||
414 |
# highlight plot area |
|
415 | ! |
output$brush_data <- DT::renderDataTable({ |
416 | ! |
plot_brush <- plot_data$brush() |
417 | ||
418 | ! |
ANL <- isolate(anl_q()$ANL) # nolint |
419 | ! |
validate_has_data(ANL, 1) |
420 | ||
421 | ! |
xvar <- isolate(input$xaxis_var) |
422 | ! |
yvar <- isolate(input$yaxis_var) |
423 | ! |
trt_group <- isolate(input$trt_group) |
424 | ||
425 | ! |
req(all(c(xvar, yvar) %in% names(ANL))) |
426 | ||
427 | ! |
df <- teal.widgets::clean_brushedPoints( |
428 | ! |
dplyr::select( |
429 | ! |
ANL, "USUBJID", dplyr::all_of(trt_group), "AVISITCD", "PARAMCD", |
430 | ! |
dplyr::all_of(c(xvar, yvar)), "LOQFL" |
431 |
), |
|
432 | ! |
plot_brush |
433 |
) |
|
434 | ||
435 | ! |
numeric_cols <- names(dplyr::select_if(df, is.numeric)) |
436 | ||
437 | ! |
DT::datatable(df, rownames = FALSE, options = list(scrollX = TRUE)) %>% |
438 | ! |
DT::formatRound(numeric_cols, 4) |
439 |
}) |
|
440 | ||
441 | ! |
teal.widgets::verbatim_popup_srv( |
442 | ! |
id = "warning", |
443 | ! |
verbatim_content = reactive(teal.code::get_warnings(plot_q())), |
444 | ! |
title = "Warning", |
445 | ! |
disabled = reactive(is.null(teal.code::get_warnings(plot_q()))) |
446 |
) |
|
447 | ||
448 | ! |
teal.widgets::verbatim_popup_srv( |
449 | ! |
id = "rcode", |
450 | ! |
verbatim_content = reactive(teal.code::get_code(plot_q())), |
451 | ! |
title = "Show R Code for Scatterplot" |
452 |
) |
|
453 |
}) |
|
454 |
} |
1 |
#' UI with a toggleable slider to change between slider and numeric input fields |
|
2 |
#' |
|
3 |
#' This is useful when a slider should be shown, but it is sometimes hard to configure sliders, |
|
4 |
#' so one can toggle to one or two numeric input fields to set slider instead. |
|
5 |
#' Both normal sliders (for a single number in a range) and dichotomous sliders (for a range |
|
6 |
#' within the slider range) are supported. In the former case, the toggle button |
|
7 |
#' will show one numeric input field, in the latter case two. |
|
8 |
#' |
|
9 |
#' Value is not checked to be within minmax range |
|
10 |
#' |
|
11 |
#' @md |
|
12 |
#' @param id `character` module id |
|
13 |
#' @param label `label` label for input field, e.g. slider or numeric inputs |
|
14 |
#' @param min `numeric or integer` minimum value |
|
15 |
#' @param max `numeric or integer` maximum value |
|
16 |
#' @param value `numeric or integer` either of length 1 for normal slider or of |
|
17 |
#' length 2 for dichotomous slider. |
|
18 |
#' @param slider_initially `logical` whether to show slider or numeric fields |
|
19 |
#' initially |
|
20 |
#' @param step_slider `numeric or integer` step for slider |
|
21 |
#' @param step_numeric `numeric or integer` step for numeric input fields |
|
22 |
#' @param width `numeric` width of slider or of each numeric field |
|
23 |
#' @param ... additional parameters to pass to `sliderInput` |
|
24 |
#' |
|
25 |
#' @return Shiny HTML UI |
|
26 |
#' @keywords internal |
|
27 |
#' |
|
28 |
#' @examples |
|
29 |
#' value <- c(20.3, 81.5) # dichotomous slider |
|
30 |
#' # value <- c(50.1) # normal slider |
|
31 |
#' app <- shinyApp( |
|
32 |
#' ui = div( |
|
33 |
#' teal.goshawk:::toggle_slider_ui( |
|
34 |
#' "toggle_slider", "Select value", |
|
35 |
#' min = 0.2, max = 100.1, value = value, |
|
36 |
#' slider_initially = FALSE, step_slider = 0.1, step_numeric = 0.001 |
|
37 |
#' ), |
|
38 |
#' verbatimTextOutput("value") |
|
39 |
#' ), |
|
40 |
#' server = function(input, output, session) { |
|
41 |
#' is_dichotomous_slider <- (length(value) == 2) |
|
42 |
#' range_value <- toggle_slider_server("toggle_slider", |
|
43 |
#' is_dichotomous_slider = is_dichotomous_slider |
|
44 |
#' ) |
|
45 |
#' messages <- reactiveVal() # to keep history |
|
46 |
#' observeEvent(range_value$state(), { |
|
47 |
#' list_with_names_str <- function(x) paste(names(x), x, sep = ": ", collapse = ", ") |
|
48 |
#' messages(c(messages(), list_with_names_str(range_value$state()))) |
|
49 |
#' }) |
|
50 |
#' output$value <- renderText({ |
|
51 |
#' paste(messages(), collapse = "\n") |
|
52 |
#' }) |
|
53 |
#' |
|
54 |
#' # for stress-testing example, update slider settings |
|
55 |
#' # bug with invalidateLater not working inside `observeEvent` |
|
56 |
#' # observe({ |
|
57 |
#' # invalidateLater(1000, session) |
|
58 |
#' # a <- sample(0:100, 1) # for range |
|
59 |
#' # b <- sample(0:100, 1) |
|
60 |
#' # isolate(do.call( |
|
61 |
#' # range_value$update_state, |
|
62 |
#' # list( |
|
63 |
#' # value = sort(sample(0:100, if (is_dichotomous_slider) 2 else 1)), |
|
64 |
#' # min = min(a, b), max = max(a, b), |
|
65 |
#' # step = sample(1:20, 1) / 10 |
|
66 |
#' # )[sample(1:4, sample(4, 1))] # select up to four fields from the list |
|
67 |
#' # )) |
|
68 |
#' # }) |
|
69 |
#' } |
|
70 |
#' ) |
|
71 |
#' shinyApp(app$ui, app$server) %>% invisible() |
|
72 |
toggle_slider_ui <- function(id, |
|
73 |
label, |
|
74 |
min, |
|
75 |
max, |
|
76 |
value, |
|
77 |
slider_initially = TRUE, |
|
78 |
step_slider = NULL, |
|
79 |
step_numeric = step_slider, |
|
80 |
width = NULL, |
|
81 |
...) { |
|
82 | ! |
checkmate::assert_number(min) |
83 | ! |
checkmate::assert_number(max) |
84 | ! |
checkmate::assert_flag(slider_initially) |
85 | ! |
checkmate::assert_number(step_slider, null.ok = TRUE) |
86 | ! |
checkmate::assert_number(step_numeric, null.ok = TRUE) |
87 | ! |
checkmate::assert_numeric(value, min.len = 1, max.len = 2) |
88 | ! |
if (is.null(step_numeric)) { |
89 | ! |
step_numeric <- NA # numericInput does not support NULL |
90 |
} |
|
91 | ||
92 | ! |
show_or_not <- function(show) if (show) identity else shinyjs::hidden |
93 | ! |
ns <- NS(id) |
94 | ! |
div( |
95 | ! |
include_css_files("custom"), |
96 | ! |
shinyjs::useShinyjs(), |
97 | ! |
div( |
98 | ! |
class = "flex justify-between mb-1", |
99 | ! |
tags$span(tags$strong(label)), |
100 | ! |
actionButton(ns("toggle"), "Toggle", class = "btn-xs") |
101 |
), |
|
102 | ! |
show_or_not(slider_initially)( |
103 | ! |
sliderInput( |
104 | ! |
ns("slider"), |
105 | ! |
label = NULL, |
106 | ! |
min = min, |
107 | ! |
max = max, |
108 | ! |
value = value, |
109 | ! |
step = step_slider, |
110 | ! |
width = width, |
111 |
... |
|
112 |
) |
|
113 |
), |
|
114 | ! |
show_or_not(!slider_initially)(tags$span( |
115 | ! |
id = ns("numeric_view"), |
116 | ! |
if (length(value) == 1) { |
117 | ! |
numericInput( |
118 | ! |
ns("value"), |
119 | ! |
label = NULL, |
120 | ! |
min = min, |
121 | ! |
max = max, |
122 | ! |
value = value[[1]], |
123 | ! |
step = step_numeric, |
124 | ! |
width = width |
125 |
) |
|
126 |
} else { |
|
127 | ! |
div( |
128 | ! |
numericInput( |
129 | ! |
ns("value_low"), |
130 | ! |
"From:", |
131 | ! |
min = min, |
132 | ! |
max = max, |
133 | ! |
value = value[[1]], |
134 | ! |
step = step_numeric, |
135 | ! |
width = width |
136 |
), |
|
137 | ! |
numericInput( |
138 | ! |
ns("value_high"), |
139 | ! |
"- to:", |
140 | ! |
min = min, |
141 | ! |
max = max, |
142 | ! |
value = value[[2]], |
143 | ! |
step = step_numeric, |
144 | ! |
width = width |
145 |
) |
|
146 |
) |
|
147 |
} |
|
148 |
)) |
|
149 |
) |
|
150 |
} |
|
151 | ||
152 |
# is_dichotomous_slider `logical` whether it is a dichotomous slider or normal slider |
|
153 |
toggle_slider_server <- function(id, is_dichotomous_slider = TRUE) { |
|
154 | ! |
moduleServer(id, function(input, output, session) { |
155 | ! |
checkmate::assert_flag(is_dichotomous_slider) |
156 |
# model view controller: cur_state is the model, the sliderInput and numericInputs are two views/controllers |
|
157 |
# additionally, the module returns the cur_state, so it can be controlled from that end as well |
|
158 | ! |
cur_state <- reactiveVal(NULL) # model, can contain min, max, value etc. |
159 | ||
160 | ||
161 | ! |
iv_r <- reactive({ |
162 | ! |
iv <- shinyvalidate::InputValidator$new() |
163 | ! |
iv$condition(~ input$toggle %% 2 == 1) |
164 | ! |
iv$add_rule("value_low", shinyvalidate::sv_required("A 'from' value is required - a default is used instead")) |
165 | ! |
iv$add_rule("value_high", shinyvalidate::sv_required("A 'to' value is required - a default is used instead)")) |
166 | ! |
iv$add_rule( |
167 | ! |
"value_high", |
168 | ! |
~ if (!is.na(input$value_low) && (.) < input$value_low) { |
169 | ! |
"'From' value should be lower than 'to' value - axis has been flipped" |
170 |
} |
|
171 |
) |
|
172 | ! |
iv$add_rule( |
173 | ! |
"value_low", |
174 | ! |
~ if (!is.na(input$value_high) && (.) > input$value_high) { |
175 | ! |
"'To' value should be greater than 'from' value - axis has been flipped" |
176 |
} |
|
177 |
) |
|
178 | ! |
iv$enable() |
179 | ! |
iv |
180 |
}) |
|
181 | ||
182 | ! |
set_state <- function(new_state) { |
183 | ! |
stopifnot(all(names(new_state) %in% c("min", "max", "step", "value"))) |
184 | ! |
iv_r()$is_valid() |
185 |
# when value does not fall into min, max range, it will automatically get truncated |
|
186 | ||
187 |
# only update provided components, do not discasrd others |
|
188 | ! |
old_state <- cur_state() |
189 | ! |
new_state <- c(new_state, old_state[!names(old_state) %in% names(new_state)]) |
190 | ! |
new_state <- new_state[sort(names(new_state))] |
191 | ! |
if (!setequal(new_state, cur_state())) { |
192 | ! |
cur_state(new_state) |
193 |
} |
|
194 |
} |
|
195 | ! |
observeEvent(input$slider, { |
196 | ! |
set_state(list(value = input$slider)) |
197 |
}) |
|
198 |
# two values for range (dichotomous slider) |
|
199 | ! |
observeEvent( |
200 | ! |
eventExpr = { # nolint |
201 | ! |
input$value_low |
202 | ! |
input$value_high |
203 |
}, |
|
204 | ! |
handlerExpr = { # nolint |
205 | ! |
set_state(list(value = c(input$value_low, input$value_high))) |
206 |
} |
|
207 |
) |
|
208 |
# one value for value in range |
|
209 | ! |
observeEvent( |
210 | ! |
input$value, |
211 | ! |
handlerExpr = { # nolint |
212 | ! |
set_state(list(value = input$value)) |
213 |
} |
|
214 |
) |
|
215 | ||
216 | ! |
update_widgets <- function() { |
217 | ! |
state_slider <- cur_state() |
218 | ! |
req(length(state_slider) > 0) # update will otherwise not work |
219 | ! |
state_low <- state_slider |
220 | ! |
state_high <- state_slider |
221 | ! |
if (!is.null(state_slider$value) && (length(state_slider$value) > 1)) { |
222 | ! |
state_low$value <- state_low$value[[1]] |
223 | ! |
state_high$value <- state_high$value[[2]] |
224 |
} |
|
225 | ! |
if (input$toggle %% 2 == 0) { |
226 | ! |
do.call(updateSliderInput, c(list(session, "slider"), state_slider)) |
227 |
} else { |
|
228 | ! |
if (length(state_slider$value) > 1) { |
229 | ! |
do.call(updateNumericInput, c(list(session, "value_low"), state_low)) |
230 | ! |
do.call(updateNumericInput, c(list(session, "value_high"), state_high)) |
231 |
} else { |
|
232 | ! |
do.call(updateNumericInput, c(list(session, "value"), state_low)) |
233 |
} |
|
234 |
} |
|
235 |
} |
|
236 | ! |
observeEvent(input$toggle, { |
237 | ! |
update_widgets() |
238 | ! |
shinyjs::toggle("numeric_view") |
239 | ! |
shinyjs::toggle("slider") |
240 |
}) |
|
241 | ||
242 | ! |
update_toggle_slider <- function(value = NULL, min = NULL, max = NULL, step = NULL) { |
243 | ! |
if (!is.null(value) && is_dichotomous_slider) { |
244 | ! |
stopifnot(length(value) == 2) |
245 |
} |
|
246 | ! |
set_state(Filter(Negate(is.null), list(value = value, min = min, max = max, step = step))) |
247 | ! |
update_widgets() |
248 |
} |
|
249 | ! |
return(list( |
250 | ! |
state = cur_state, |
251 | ! |
update_state = update_toggle_slider |
252 |
)) |
|
253 |
}) |
|
254 |
} |
1 |
#' UI module to arbitrary lines |
|
2 |
#' |
|
3 |
#' UI module to input either horizontal or vertical lines to a plot via comma separated values |
|
4 |
#' |
|
5 |
#' @param id (`character(1)`)\cr |
|
6 |
#' defining namespace of the `shiny` module. |
|
7 |
#' @param line_arb (`numeric`)\cr |
|
8 |
#' default values for the `textInput` defining values of arbitrary lines |
|
9 |
#' @param line_arb_color (`character`)\cr |
|
10 |
#' default values for the `textInput` defining colors of arbitrary lines |
|
11 |
#' @param line_arb_label (`character`)\cr |
|
12 |
#' default values for the `textInput` defining labels of arbitrary lines |
|
13 |
#' @param title (`character(1)`)\cr |
|
14 |
#' title of the arbitrary lines input. The default is "Arbitrary Horizontal Lines". |
|
15 |
#' @return (`shiny.tag`) an input to define values, colors and labels for arbitrary |
|
16 |
#' straight lines. |
|
17 |
#' @keywords internal |
|
18 |
ui_arbitrary_lines <- function(id, line_arb, line_arb_label, line_arb_color, title = "Arbitrary Horizontal Lines:") { |
|
19 | ! |
ns <- NS(id) |
20 | ! |
div( |
21 | ! |
tags$b(title), |
22 | ! |
textInput( |
23 | ! |
ns("line_arb"), |
24 | ! |
div( |
25 | ! |
class = "teal-tooltip", |
26 | ! |
tagList( |
27 | ! |
"Value:", |
28 | ! |
icon("circle-info"), |
29 | ! |
span( |
30 | ! |
class = "tooltiptext", |
31 | ! |
"For multiple lines, supply a comma separated list of values." |
32 |
) |
|
33 |
) |
|
34 |
), |
|
35 | ! |
value = paste(line_arb, collapse = ", ") |
36 |
), |
|
37 | ! |
textInput(ns("line_arb_label"), label = "Label:", value = paste(line_arb_label, collapse = ", ")), |
38 | ! |
textInput(ns("line_arb_color"), label = "Color:", value = paste(line_arb_color, collapse = ", ")) |
39 |
) |
|
40 |
} |
|
41 |
#' Server module to arbitrary lines |
|
42 |
#' |
|
43 |
#' Server to validate and transform the comma separated values into vectors of values |
|
44 |
#' to be passed into goshawk functions. |
|
45 |
#' @inheritParams shiny::moduleServer |
|
46 |
#' @return (`reactive`) returning a `list` containing `line_arb`, `line_arb_color`, |
|
47 |
#' `line_arb_label` which are validated and could be passed to `goshawk` plot functions. |
|
48 |
#' @keywords internal |
|
49 |
srv_arbitrary_lines <- function(id) { |
|
50 | ! |
moduleServer(id, function(input, output, session) { |
51 | ||
52 | ! |
comma_sep_to_values <- function(values, wrapper_fun = trimws) { |
53 | ! |
vals <- strsplit(values, "\\s{0,},\\s{0,}")[[1]] |
54 | ! |
suppressWarnings(wrapper_fun(vals)) |
55 |
} |
|
56 | ||
57 | ! |
iv_r <- reactive({ |
58 | ! |
iv <- shinyvalidate::InputValidator$new() |
59 | ! |
iv$add_rule("line_arb", shinyvalidate::sv_optional()) |
60 | ! |
iv$add_rule( |
61 | ! |
"line_arb", |
62 | ! |
~ if (any(is.na(comma_sep_to_values(., as.numeric)))) |
63 | ! |
"Arbitrary lines values should be a comma separated list of numbers" |
64 |
) |
|
65 | ||
66 | ! |
iv_color <- shinyvalidate::InputValidator$new() |
67 | ! |
iv_color$condition(~ length(line_arb()) != 0) |
68 | ||
69 | ! |
iv_color$add_rule("line_arb_color", shinyvalidate::sv_optional()) |
70 | ! |
iv_color$add_rule( |
71 | ! |
"line_arb_color", |
72 | ! |
~ if (!length(comma_sep_to_values(.)) %in% c(1, length(line_arb()))) |
73 | ! |
sprintf( |
74 | ! |
"Line input error: number of colors should be equal to 1, the number of lines (%d) or left blank for 'red'", |
75 | ! |
length(line_arb()) |
76 |
) |
|
77 |
) |
|
78 | ! |
iv_color$add_rule("line_arb_color", ~ if (!check_color(comma_sep_to_values(.))) |
79 | ! |
"The line colors entered cannot be converted to colors in R, please check your spelling") |
80 | ! |
iv$add_validator(iv_color) |
81 | ||
82 | ||
83 | ! |
iv_label <- shinyvalidate::InputValidator$new() |
84 | ! |
iv_label$condition(~ length(line_arb()) != 0) |
85 | ||
86 | ! |
iv_label$add_rule("line_arb_label", shinyvalidate::sv_optional()) |
87 | ! |
iv_label$add_rule( |
88 | ! |
"line_arb_label", |
89 | ! |
~ if (!length(comma_sep_to_values(.)) %in% c(1, length(line_arb()))) |
90 | ! |
sprintf( |
91 | ! |
"Line input error: number of labels should be equal to 1, the number of lines (%d) or left blank", |
92 | ! |
length(line_arb()) |
93 |
) |
|
94 |
) |
|
95 | ! |
iv$add_validator(iv_label) |
96 | ! |
iv |
97 |
}) |
|
98 | ||
99 | ! |
line_arb <- reactive({ |
100 | ! |
req(!is.null(input$line_arb)) |
101 | ! |
comma_sep_to_values(input$line_arb, as.numeric) |
102 |
}) |
|
103 | ||
104 | ! |
line_arb_label <- reactive({ |
105 | ! |
if (length(line_arb()) == 0) { |
106 | ! |
return(character(0)) |
107 |
} |
|
108 | ! |
val <- comma_sep_to_values(input$line_arb_label) |
109 | ! |
if (length(val) == 0) { |
110 | ! |
val <- "" |
111 |
} |
|
112 | ! |
val |
113 |
}) |
|
114 | ||
115 | ! |
line_arb_color <- reactive({ |
116 | ! |
if (length(line_arb()) == 0) { |
117 | ! |
return(character(0)) |
118 |
} |
|
119 | ! |
val <- comma_sep_to_values(input$line_arb_color) |
120 | ! |
if (length(val) == 0 || all(val == "")) { |
121 | ! |
val <- "red" |
122 |
} |
|
123 | ! |
val |
124 |
}) |
|
125 | ||
126 | ! |
return( |
127 | ! |
reactive( |
128 | ! |
list( |
129 | ! |
iv_r = iv_r, |
130 | ! |
line_arb = line_arb(), |
131 | ! |
line_arb_color = line_arb_color(), |
132 | ! |
line_arb_label = line_arb_label() |
133 |
) |
|
134 |
) |
|
135 |
) |
|
136 |
}) |
|
137 |
} |
|
138 | ||
139 |
check_color <- function(col) { |
|
140 | ! |
tryCatch( |
141 | ! |
is.matrix(grDevices::col2rgb(col)), |
142 | ! |
error = function(e) FALSE |
143 |
) |
|
144 |
} |
|
145 | ||
146 |
# to check the arbitrary line arguments |
|
147 |
validate_line_arb_arg <- function(line_arb, line_arb_color, line_arb_label) { |
|
148 | ! |
checkmate::assert_numeric(line_arb) |
149 | ! |
if (length(line_arb) > 0) { |
150 | ! |
checkmate::assert( |
151 | ! |
checkmate::check_string(line_arb_color), |
152 | ! |
checkmate::check_character(line_arb_color, len = length(line_arb)) |
153 |
) |
|
154 | ! |
checkmate::assert( |
155 | ! |
checkmate::check_string(line_arb_label), |
156 | ! |
checkmate::check_character(line_arb_label, len = length(line_arb)) |
157 |
) |
|
158 |
} |
|
159 |
} |
|
160 | ||
161 |
# to check the variable line arguments |
|
162 |
validate_line_vars_arg <- function(line_vars, line_vars_colors, line_vars_labels) { |
|
163 | ! |
checkmate::assert_character(line_vars) |
164 | ! |
if (length(line_vars) > 0) { |
165 | ! |
checkmate::assert( |
166 | ! |
checkmate::check_string(line_vars_colors), |
167 | ! |
checkmate::check_character(line_vars_colors, len = length(line_vars)) |
168 |
) |
|
169 | ! |
checkmate::assert( |
170 | ! |
checkmate::check_string(line_vars_labels), |
171 | ! |
checkmate::check_character(line_vars_labels, len = length(line_vars)) |
172 |
) |
|
173 |
} |
|
174 |
} |
1 |
#' Box Plot |
|
2 |
#' |
|
3 |
#' This teal module renders the UI and calls the functions that create a box plot and accompanying |
|
4 |
#' summary table. |
|
5 |
#' |
|
6 |
#' @param label menu item label of the module in the teal app. |
|
7 |
#' @param dataname analysis data passed to the data argument of \code{\link[teal]{init}}. E.g. `ADaM` structured |
|
8 |
#' laboratory data frame `ALB`. |
|
9 |
#' @param param_var name of variable containing biomarker codes e.g. `PARAMCD`. |
|
10 |
#' @param param list of biomarkers of interest. |
|
11 |
#' @param yaxis_var name of variable containing biomarker results displayed on y-axis e.g. `AVAL`. When not provided, |
|
12 |
#' it defaults to `choices_selected(c("AVAL", "CHG"), "AVAL")`. |
|
13 |
#' @param xaxis_var variable to categorize the x-axis. When not provided, it defaults to |
|
14 |
#' `choices_selected("AVISITCD", "AVISITCD")`. |
|
15 |
#' @param facet_var variable to facet the plots by. When not provided, it defaults to |
|
16 |
#' `choices_selected(c("ARM", "ACTARM"), "ARM")`. |
|
17 |
#' @param trt_group \code{\link[teal.transform]{choices_selected}} object with available choices and pre-selected |
|
18 |
#' option for variable names representing treatment group e.g. `ARM`. |
|
19 |
#' @param color_manual vector of colors applied to treatment values. |
|
20 |
#' @param shape_manual vector of symbols applied to `LOQ` values. |
|
21 |
#' @param facet_ncol numeric value indicating number of facets per row. |
|
22 |
#' @param loq_legend `loq` legend toggle. |
|
23 |
#' @param rotate_xlab 45 degree rotation of `x-axis` values. |
|
24 |
#' @param hline_arb numeric vector of at most 2 values identifying intercepts for arbitrary horizontal lines. |
|
25 |
#' @param hline_arb_color a character vector of at most length of \code{hline_arb}. |
|
26 |
#' naming the color for the arbitrary horizontal lines. |
|
27 |
#' @param hline_arb_label a character vector of at most length of \code{hline_arb}. |
|
28 |
#' naming the label for the arbitrary horizontal lines. |
|
29 |
#' @param hline_vars a character vector to name the columns that will define additional horizontal lines. |
|
30 |
#' @param hline_vars_colors a character vector naming the colors for the additional horizontal lines. |
|
31 |
#' @param hline_vars_labels a character vector naming the labels for the additional horizontal lines that will appear |
|
32 |
#' in the legend. |
|
33 |
#' @param plot_height controls plot height. |
|
34 |
#' @param plot_width optional, controls plot width. |
|
35 |
#' @param font_size font size control for title, `x-axis` label, `y-axis` label and legend. |
|
36 |
#' @param dot_size plot dot size. |
|
37 |
#' @param alpha numeric vector to define transparency of plotted points. |
|
38 |
#' |
|
39 |
#' @inheritParams teal.widgets::standard_layout |
|
40 |
#' |
|
41 |
#' @author Jeff Tomlinson (tomlinsj) jeffrey.tomlinson@roche.com |
|
42 |
#' @author Balazs Toth (tothb2) toth.balazs@gene.com |
|
43 |
#' |
|
44 |
#' @return an \code{\link[teal]{module}} object |
|
45 |
#' |
|
46 |
#' @export |
|
47 |
#' |
|
48 |
#' @examples |
|
49 |
#' |
|
50 |
#' # Example using ADaM structure analysis dataset. |
|
51 |
#' |
|
52 |
#' library(dplyr) |
|
53 |
#' library(nestcolor) |
|
54 |
#' |
|
55 |
#' # original ARM value = dose value |
|
56 |
#' arm_mapping <- list( |
|
57 |
#' "A: Drug X" = "150mg QD", |
|
58 |
#' "B: Placebo" = "Placebo", |
|
59 |
#' "C: Combination" = "Combination" |
|
60 |
#' ) |
|
61 |
#' |
|
62 |
#' set.seed(1) |
|
63 |
#' ADSL <- goshawk::rADSL |
|
64 |
#' ADLB <- goshawk::rADLB |
|
65 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
66 |
#' ADLB <- ADLB %>% |
|
67 |
#' dplyr::mutate( |
|
68 |
#' AVISITCD = dplyr::case_when( |
|
69 |
#' AVISIT == "SCREENING" ~ "SCR", |
|
70 |
#' AVISIT == "BASELINE" ~ "BL", |
|
71 |
#' grepl("WEEK", AVISIT) ~ paste("W", stringr::str_extract(AVISIT, "(?<=(WEEK ))[0-9]+")), |
|
72 |
#' TRUE ~ as.character(NA) |
|
73 |
#' ), |
|
74 |
#' AVISITCDN = dplyr::case_when( |
|
75 |
#' AVISITCD == "SCR" ~ -2, |
|
76 |
#' AVISITCD == "BL" ~ 0, |
|
77 |
#' grepl("W", AVISITCD) ~ as.numeric(gsub("[^0-9]*", "", AVISITCD)), |
|
78 |
#' TRUE ~ as.numeric(NA) |
|
79 |
#' ), |
|
80 |
#' AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN), |
|
81 |
#' TRTORD = dplyr::case_when( |
|
82 |
#' ARMCD == "ARM C" ~ 1, |
|
83 |
#' ARMCD == "ARM B" ~ 2, |
|
84 |
#' ARMCD == "ARM A" ~ 3 |
|
85 |
#' ), |
|
86 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
87 |
#' ARM = factor(ARM) %>% reorder(TRTORD), |
|
88 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
89 |
#' ACTARM = factor(ACTARM) %>% reorder(TRTORD), |
|
90 |
#' ANRLO = 50, |
|
91 |
#' ANRHI = 75 |
|
92 |
#' ) %>% |
|
93 |
#' dplyr::rowwise() %>% |
|
94 |
#' dplyr::group_by(PARAMCD) %>% |
|
95 |
#' dplyr::mutate(LBSTRESC = ifelse( |
|
96 |
#' USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
97 |
#' paste("<", round(runif(1, min = 25, max = 30))), LBSTRESC |
|
98 |
#' )) %>% |
|
99 |
#' dplyr::mutate(LBSTRESC = ifelse( |
|
100 |
#' USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
101 |
#' paste(">", round(runif(1, min = 70, max = 75))), LBSTRESC |
|
102 |
#' )) %>% |
|
103 |
#' ungroup() |
|
104 |
#' |
|
105 |
#' attr(ADLB[["ARM"]], "label") <- var_labels[["ARM"]] |
|
106 |
#' attr(ADLB[["ACTARM"]], "label") <- var_labels[["ACTARM"]] |
|
107 |
#' attr(ADLB[["ANRLO"]], "label") <- "Analysis Normal Range Lower Limit" |
|
108 |
#' attr(ADLB[["ANRHI"]], "label") <- "Analysis Normal Range Upper Limit" |
|
109 |
#' |
|
110 |
#' # add LLOQ and ULOQ variables |
|
111 |
#' ALB_LOQS <- goshawk:::h_identify_loq_values(ADLB) |
|
112 |
#' ADLB <- dplyr::left_join(ADLB, ALB_LOQS, by = "PARAM") |
|
113 |
#' |
|
114 |
#' app <- teal::init( |
|
115 |
#' data = teal.data::cdisc_data( |
|
116 |
#' adsl <- teal.data::cdisc_dataset("ADSL", ADSL, code = "ADSL <- goshawk::rADSL"), |
|
117 |
#' teal.data::cdisc_dataset( |
|
118 |
#' "ADLB", |
|
119 |
#' ADLB, |
|
120 |
#' code = " |
|
121 |
#' set.seed(1) |
|
122 |
#' ADLB <- goshawk::rADLB |
|
123 |
#' var_labels <- lapply(ADLB, function(x) attributes(x)$label) |
|
124 |
#' ADLB <- ADLB %>% |
|
125 |
#' dplyr::mutate(AVISITCD = dplyr::case_when( |
|
126 |
#' AVISIT == 'SCREENING' ~ 'SCR', |
|
127 |
#' AVISIT == 'BASELINE' ~ 'BL', |
|
128 |
#' grepl('WEEK', AVISIT) ~ paste('W', stringr::str_extract(AVISIT, '(?<=(WEEK ))[0-9]+')), |
|
129 |
#' TRUE ~ as.character(NA)), |
|
130 |
#' AVISITCDN = dplyr::case_when( |
|
131 |
#' AVISITCD == 'SCR' ~ -2, |
|
132 |
#' AVISITCD == 'BL' ~ 0, |
|
133 |
#' grepl('W', AVISITCD) ~ as.numeric(gsub('[^0-9]*', '', AVISITCD)), |
|
134 |
#' TRUE ~ as.numeric(NA)), |
|
135 |
#' AVISITCD = factor(AVISITCD) %>% reorder(AVISITCDN), |
|
136 |
#' TRTORD = dplyr::case_when( |
|
137 |
#' ARMCD == 'ARM C' ~ 1, |
|
138 |
#' ARMCD == 'ARM B' ~ 2, |
|
139 |
#' ARMCD == 'ARM A' ~ 3), |
|
140 |
#' ARM = as.character(arm_mapping[match(ARM, names(arm_mapping))]), |
|
141 |
#' ARM = factor(ARM) %>% reorder(TRTORD), |
|
142 |
#' ACTARM = as.character(arm_mapping[match(ACTARM, names(arm_mapping))]), |
|
143 |
#' ACTARM = factor(ACTARM) %>% reorder(TRTORD), |
|
144 |
#' ANRLO = 50, |
|
145 |
#' ANRHI = 75) %>% |
|
146 |
#' dplyr::rowwise() %>% |
|
147 |
#' dplyr::group_by(PARAMCD) %>% |
|
148 |
#' dplyr::mutate(LBSTRESC = ifelse( |
|
149 |
#' USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
150 |
#' paste('<', round(runif(1, min = 25, max = 30))), LBSTRESC)) %>% |
|
151 |
#' dplyr::mutate(LBSTRESC = ifelse( |
|
152 |
#' USUBJID %in% sample(USUBJID, 1, replace = TRUE), |
|
153 |
#' paste( '>', round(runif(1, min = 70, max = 75))), LBSTRESC)) %>% |
|
154 |
#' ungroup() |
|
155 |
#' attr(ADLB[['ARM']], 'label') <- var_labels[['ARM']] |
|
156 |
#' attr(ADLB[['ACTARM']], 'label') <- var_labels[['ACTARM']] |
|
157 |
#' attr(ADLB[['ANRLO']], 'label') <- 'Analysis Normal Range Lower Limit' |
|
158 |
#' attr(ADLB[['ANRHI']], 'label') <- 'Analysis Normal Range Upper Limit' |
|
159 |
#' # add LLOQ and ULOQ variables |
|
160 |
#' ALB_LOQS <- goshawk:::h_identify_loq_values(ADLB) |
|
161 |
#' ADLB <- left_join(ADLB, ALB_LOQS, by = 'PARAM')", |
|
162 |
#' vars = list(ADSL = adsl, arm_mapping = arm_mapping) |
|
163 |
#' ), |
|
164 |
#' check = FALSE # to shorten the example check = FALSE, in real scenarios use check = TRUE |
|
165 |
#' ), |
|
166 |
#' modules = teal::modules( |
|
167 |
#' teal.goshawk::tm_g_gh_boxplot( |
|
168 |
#' label = "Box Plot", |
|
169 |
#' dataname = "ADLB", |
|
170 |
#' param_var = "PARAMCD", |
|
171 |
#' param = choices_selected(c("ALT", "CRP", "IGA"), "ALT"), |
|
172 |
#' yaxis_var = choices_selected(c("AVAL", "BASE", "CHG"), "AVAL"), |
|
173 |
#' xaxis_var = choices_selected(c("ACTARM", "ARM", "AVISITCD", "STUDYID"), "ARM"), |
|
174 |
#' facet_var = choices_selected(c("ACTARM", "ARM", "AVISITCD", "SEX"), "AVISITCD"), |
|
175 |
#' trt_group = choices_selected(c("ARM", "ACTARM"), "ARM"), |
|
176 |
#' loq_legend = TRUE, |
|
177 |
#' rotate_xlab = FALSE, |
|
178 |
#' hline_arb = c(60, 55), |
|
179 |
#' hline_arb_color = c("grey", "red"), |
|
180 |
#' hline_arb_label = c("default_hori_A", "default_hori_B"), |
|
181 |
#' hline_vars = c("ANRHI", "ANRLO", "ULOQN", "LLOQN"), |
|
182 |
#' hline_vars_colors = c("pink", "brown", "purple", "black"), |
|
183 |
#' ) |
|
184 |
#' ) |
|
185 |
#' ) |
|
186 |
#' if (interactive()) { |
|
187 |
#' shinyApp(app$ui, app$server) |
|
188 |
#' } |
|
189 |
#' |
|
190 |
tm_g_gh_boxplot <- function(label, |
|
191 |
dataname, |
|
192 |
param_var, |
|
193 |
param, |
|
194 |
yaxis_var = teal.transform::choices_selected(c("AVAL", "CHG"), "AVAL"), |
|
195 |
xaxis_var = teal.transform::choices_selected("AVISITCD", "AVISITCD"), |
|
196 |
facet_var = teal.transform::choices_selected(c("ARM", "ACTARM"), "ARM"), |
|
197 |
trt_group, |
|
198 |
color_manual = NULL, |
|
199 |
shape_manual = NULL, |
|
200 |
facet_ncol = NULL, |
|
201 |
loq_legend = TRUE, |
|
202 |
rotate_xlab = FALSE, |
|
203 |
hline_arb = numeric(0), |
|
204 |
hline_arb_color = "red", |
|
205 |
hline_arb_label = "Horizontal line", |
|
206 |
hline_vars = character(0), |
|
207 |
hline_vars_colors = "green", |
|
208 |
hline_vars_labels = hline_vars, |
|
209 |
plot_height = c(600, 200, 2000), |
|
210 |
plot_width = NULL, |
|
211 |
font_size = c(12, 8, 20), |
|
212 |
dot_size = c(2, 1, 12), |
|
213 |
alpha = c(0.8, 0.0, 1.0), |
|
214 |
pre_output = NULL, |
|
215 |
post_output = NULL) { |
|
216 | ! |
logger::log_info("Initializing tm_g_gh_boxplot") |
217 | ! |
checkmate::assert_string(label) |
218 | ! |
checkmate::assert_string(dataname) |
219 | ! |
checkmate::assert_string(param_var) |
220 | ! |
checkmate::assert_class(param, "choices_selected") |
221 | ! |
checkmate::assert_class(yaxis_var, "choices_selected") |
222 | ! |
checkmate::assert_class(xaxis_var, "choices_selected") |
223 | ! |
checkmate::assert_class(facet_var, "choices_selected") |
224 | ! |
checkmate::assert_class(trt_group, "choices_selected") |
225 | ! |
checkmate::assert_int(facet_ncol, null.ok = TRUE) |
226 | ! |
checkmate::assert_flag(loq_legend) |
227 | ! |
checkmate::assert_flag(rotate_xlab) |
228 | ! |
checkmate::assert_numeric(font_size, len = 3) |
229 | ! |
checkmate::assert_numeric(dot_size, len = 3) |
230 | ! |
checkmate::assert_numeric(alpha, len = 3) |
231 | ! |
validate_line_arb_arg(hline_arb, hline_arb_color, hline_arb_label) |
232 | ! |
validate_line_vars_arg(hline_vars, hline_vars_colors, hline_vars_labels) |
233 | ! |
checkmate::assert_numeric(plot_height, len = 3, any.missing = FALSE, finite = TRUE) |
234 | ! |
checkmate::assert_numeric(plot_height[1], lower = plot_height[2], upper = plot_height[3], .var.name = "plot_height") |
235 | ! |
checkmate::assert_numeric(plot_width, len = 3, any.missing = FALSE, null.ok = TRUE, finite = TRUE) |
236 | ! |
checkmate::assert_numeric( |
237 | ! |
plot_width[1], |
238 | ! |
lower = plot_width[2], upper = plot_width[3], null.ok = TRUE, .var.name = "plot_width" |
239 |
) |
|
240 | ||
241 | ! |
args <- as.list(environment()) |
242 | ||
243 | ! |
module( |
244 | ! |
label = label, |
245 | ! |
datanames = dataname, |
246 | ! |
server = srv_g_boxplot, |
247 | ! |
server_args = list( |
248 | ! |
dataname = dataname, |
249 | ! |
param_var = param_var, |
250 | ! |
trt_group = trt_group, |
251 | ! |
facet_var = facet_var, |
252 | ! |
color_manual = color_manual, |
253 | ! |
shape_manual = shape_manual, |
254 | ! |
plot_height = plot_height, |
255 | ! |
plot_width = plot_width, |
256 | ! |
hline_vars_colors = hline_vars_colors, |
257 | ! |
hline_vars_labels = hline_vars_labels |
258 |
), |
|
259 | ! |
ui = ui_g_boxplot, |
260 | ! |
ui_args = args |
261 |
) |
|
262 |
} |
|
263 | ||
264 |
ui_g_boxplot <- function(id, ...) { |
|
265 | ! |
ns <- NS(id) |
266 | ! |
a <- list(...) |
267 | ||
268 | ! |
teal.widgets::standard_layout( |
269 | ! |
output = div( |
270 | ! |
fluidRow( |
271 | ! |
teal.widgets::plot_with_settings_ui(id = ns("boxplot")) |
272 |
), |
|
273 | ! |
fluidRow(column( |
274 | ! |
width = 12, |
275 | ! |
br(), hr(), |
276 | ! |
h4("Selected Data Points"), |
277 | ! |
DT::dataTableOutput(ns("brush_data")) |
278 |
)), |
|
279 | ! |
fluidRow(column( |
280 | ! |
width = 12, |
281 | ! |
br(), hr(), |
282 | ! |
h4("Descriptive Statistics"), |
283 | ! |
DT::dataTableOutput(ns("table_ui")) |
284 |
)) |
|
285 |
), |
|
286 | ! |
encoding = div( |
287 |
### Reporter |
|
288 | ! |
teal.reporter::simple_reporter_ui(ns("simple_reporter")), |
289 |
### |
|
290 | ! |
templ_ui_dataname(a$dataname), |
291 | ! |
teal.widgets::optionalSelectInput( |
292 | ! |
ns("trt_group"), |
293 | ! |
label = "Select Treatment Variable", |
294 | ! |
choices = a$trt_group$choices, |
295 | ! |
selected = a$trt_group$selected, |
296 | ! |
multiple = FALSE |
297 |
), |
|
298 | ! |
templ_ui_params_vars( |
299 | ! |
ns, |
300 | ! |
xparam_choices = a$param$choices, |
301 | ! |
xparam_selected = a$param$selected, |
302 | ! |
xparam_label = "Select a Biomarker", |
303 | ! |
xchoices = a$xaxis_var$choices, |
304 | ! |
xselected = a$xaxis_var$selected, |
305 | ! |
ychoices = a$yaxis_var$choices, |
306 | ! |
yselected = a$yaxis_var$selected |
307 |
), |
|
308 | ! |
teal.widgets::optionalSelectInput( |
309 | ! |
ns("facet_var"), |
310 | ! |
label = "Facet by", |
311 | ! |
choices = a$facet_var$choices, |
312 | ! |
selected = a$facet_var$selected, |
313 | ! |
multiple = FALSE |
314 |
), |
|
315 | ! |
templ_ui_constraint(ns, label = "Data Constraint"), # required by constr_anl_q |
316 | ! |
if (length(a$hline_vars) > 0) { |
317 | ! |
teal.widgets::optionalSelectInput( |
318 | ! |
ns("hline_vars"), |
319 | ! |
label = "Add Horizontal Range Line(s):", |
320 | ! |
choices = a$hline_vars, |
321 | ! |
selected = NULL, |
322 | ! |
multiple = TRUE |
323 |
) |
|
324 |
}, |
|
325 | ! |
ui_arbitrary_lines(id = ns("hline_arb"), a$hline_arb, a$hline_arb_label, a$hline_arb_color), |
326 | ! |
teal.widgets::panel_group( |
327 | ! |
teal.widgets::panel_item( |
328 | ! |
title = "Plot Aesthetic Settings", |
329 | ! |
toggle_slider_ui( |
330 | ! |
ns("yrange_scale"), |
331 | ! |
label = "Y-Axis Range Zoom", |
332 | ! |
min = -1000000, |
333 | ! |
max = 1000000, |
334 | ! |
value = c(-1000000, 1000000) |
335 |
), |
|
336 | ! |
numericInput(ns("facet_ncol"), "Number of Plots Per Row:", a$facet_ncol, min = 1), |
337 | ! |
checkboxInput(ns("loq_legend"), "Display LoQ Legend", a$loq_legend), |
338 | ! |
checkboxInput(ns("rotate_xlab"), "Rotate X-axis Label", a$rotate_xlab) |
339 |
), |
|
340 | ! |
teal.widgets::panel_item( |
341 | ! |
title = "Plot settings", |
342 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("font_size"), "Font Size", a$font_size, ticks = FALSE), |
343 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("dot_size"), "Dot Size", a$dot_size, ticks = FALSE), |
344 | ! |
teal.widgets::optionalSliderInputValMinMax(ns("alpha"), "Dot Alpha", a$alpha, ticks = FALSE) |
345 |
) |
|
346 |
) |
|
347 |
), |
|
348 | ! |
forms = tagList( |
349 | ! |
teal.widgets::verbatim_popup_ui(ns("warning"), "Show Warnings"), |
350 | ! |
teal.widgets::verbatim_popup_ui(ns("rcode"), "Show R code") |
351 |
), |
|
352 | ! |
pre_output = a$pre_output, |
353 | ! |
post_output = a$post_output |
354 |
) |
|
355 |
} |
|
356 | ||
357 | ||
358 |
srv_g_boxplot <- function(id, |
|
359 |
data, |
|
360 |
reporter, |
|
361 |
filter_panel_api, |
|
362 |
dataname, |
|
363 |
param_var, |
|
364 |
trt_group, |
|
365 |
facet_var, |
|
366 |
color_manual, |
|
367 |
shape_manual, |
|
368 |
plot_height, |
|
369 |
plot_width, |
|
370 |
hline_vars_colors, |
|
371 |
hline_vars_labels) { |
|
372 | ! |
with_reporter <- !missing(reporter) && inherits(reporter, "Reporter") |
373 | ! |
with_filter <- !missing(filter_panel_api) && inherits(filter_panel_api, "FilterPanelAPI") |
374 | ! |
checkmate::assert_class(data, "tdata") |
375 | ||
376 | ! |
moduleServer(id, function(input, output, session) { |
377 |
# reused in all modules |
|
378 | ! |
anl_q_output <- constr_anl_q( |
379 | ! |
session, input, data, dataname, |
380 | ! |
param_id = "xaxis_param", param_var = param_var, trt_group = input$trt_group, min_rows = 2 |
381 |
) |
|
382 | ||
383 | ! |
anl_q <- anl_q_output()$value |
384 | ||
385 |
# update sliders for axes taking constraints into account |
|
386 | ! |
yrange_slider <- toggle_slider_server("yrange_scale") |
387 | ! |
keep_range_slider_updated( |
388 | ! |
session, |
389 | ! |
input, |
390 | ! |
update_slider_fcn = yrange_slider$update_state, |
391 | ! |
id_var = "yaxis_var", |
392 | ! |
id_param_var = "xaxis_param", |
393 | ! |
reactive_ANL = anl_q |
394 |
) |
|
395 | ! |
keep_data_const_opts_updated(session, input, anl_q, "xaxis_param") |
396 | ||
397 | ! |
horizontal_line <- srv_arbitrary_lines("hline_arb") |
398 | ||
399 | ! |
trt_group <- reactive({ |
400 | ! |
input$trt_group |
401 |
}) |
|
402 | ||
403 | ! |
iv_r <- reactive({ |
404 | ! |
iv <- shinyvalidate::InputValidator$new() |
405 | ||
406 | ! |
iv$add_rule("xaxis_param", shinyvalidate::sv_required("Please select a biomarker")) |
407 | ! |
iv$add_rule("trt_group", shinyvalidate::sv_required("Please select a treatment variable")) |
408 | ! |
iv$add_rule("xaxis_var", shinyvalidate::sv_required("Please select an X-Axis variable")) |
409 | ! |
iv$add_rule("xaxis_var", ~ if ((.) %in% c("ACTARM", "ARM") && isTRUE((.) != trt_group())) { |
410 | ! |
sprintf("You can not choose %s as x-axis variable for treatment variable %s.", (.), trt_group()) |
411 |
}) |
|
412 | ! |
iv$add_rule("yaxis_var", shinyvalidate::sv_required("Please select a Y-Axis variable")) |
413 | ||
414 | ! |
iv$add_rule("facet_var", shinyvalidate::sv_optional()) |
415 | ! |
iv$add_rule("facet_var", ~ if ((.) %in% c("ACTARM", "ARM") && isTRUE((.) != trt_group())) { |
416 | ! |
sprintf("You can not choose %s as faceting variable for treatment variable %s.", (.), trt_group()) |
417 |
}) |
|
418 | ||
419 | ! |
iv_facet <- shinyvalidate::InputValidator$new() |
420 | ! |
iv_facet$condition(~ !is.null(input$facet_var)) |
421 | ! |
iv_facet$add_rule("facet_ncol", plots_per_row_validate_rules(required = FALSE)) |
422 | ! |
iv$add_validator(iv_facet) |
423 | ||
424 | ! |
iv$add_validator(horizontal_line()$iv_r()) |
425 | ! |
iv$add_validator(anl_q_output()$iv_r()) |
426 | ! |
iv$enable() |
427 | ! |
iv |
428 |
}) |
|
429 | ||
430 | ! |
create_plot <- reactive({ |
431 | ! |
teal::validate_inputs(iv_r()) |
432 | ||
433 | ! |
req(anl_q()) |
434 |
# nolint start |
|
435 | ! |
param <- input$xaxis_param |
436 | ! |
yaxis <- input$yaxis_var |
437 | ! |
xaxis <- input$xaxis_var |
438 | ! |
facet_var <- `if`(is.null(input$facet_var), "None", input$facet_var) |
439 | ! |
ylim <- yrange_slider$state()$value |
440 | ! |
facet_ncol <- input$facet_ncol |
441 | ||
442 | ! |
alpha <- input$alpha |
443 | ! |
font_size <- input$font_size |
444 | ! |
dot_size <- input$dot_size |
445 | ! |
loq_legend <- input$loq_legend |
446 | ! |
rotate_xlab <- input$rotate_xlab |
447 | ||
448 | ! |
hline_arb <- horizontal_line()$line_arb |
449 | ! |
hline_arb_label <- horizontal_line()$line_arb_label |
450 | ! |
hline_arb_color <- horizontal_line()$line_arb_color |
451 | ||
452 | ! |
hline_vars <- input$hline_vars |
453 | ! |
trt_group <- input$trt_group |
454 |
# nolint end |
|
455 | ||
456 | ! |
validate_has_variable( |
457 | ! |
anl_q()$ANL, |
458 | ! |
yaxis, |
459 | ! |
sprintf("Variable %s is not available in data %s", yaxis, dataname) |
460 |
) |
|
461 | ! |
validate_has_variable( |
462 | ! |
anl_q()$ANL, |
463 | ! |
xaxis, |
464 | ! |
sprintf("Variable %s is not available in data %s", xaxis, dataname) |
465 |
) |
|
466 | ||
467 | ! |
if (!facet_var == "None") { |
468 | ! |
validate_has_variable( |
469 | ! |
anl_q()$ANL, |
470 | ! |
facet_var, |
471 | ! |
sprintf("Variable %s is not available in data %s", facet_var, dataname) |
472 |
) |
|
473 |
} |
|
474 | ||
475 | ! |
anl_q()$qenv %>% teal.code::eval_code( |
476 | ! |
code = bquote({ |
477 | ! |
p <- goshawk::g_boxplot( |
478 | ! |
data = ANL, |
479 | ! |
biomarker = .(param), |
480 | ! |
xaxis_var = .(xaxis), |
481 | ! |
yaxis_var = .(yaxis), |
482 | ! |
hline_arb = .(hline_arb), |
483 | ! |
hline_arb_label = .(hline_arb_label), |
484 | ! |
hline_arb_color = .(hline_arb_color), |
485 | ! |
hline_vars = .(hline_vars), |
486 | ! |
hline_vars_colors = .(hline_vars_colors[seq_along(hline_vars)]), |
487 | ! |
hline_vars_labels = .(hline_vars_labels[seq_along(hline_vars)]), |
488 | ! |
facet_ncol = .(facet_ncol), |
489 | ! |
loq_legend = .(loq_legend), |
490 | ! |
rotate_xlab = .(rotate_xlab), |
491 | ! |
trt_group = .(trt_group), |
492 | ! |
ylim = .(ylim), |
493 | ! |
color_manual = .(color_manual), |
494 | ! |
shape_manual = .(shape_manual), |
495 | ! |
facet_var = .(facet_var), |
496 | ! |
alpha = .(alpha), |
497 | ! |
dot_size = .(dot_size), |
498 | ! |
font_size = .(font_size), |
499 | ! |
unit = .("AVALU") |
500 |
) |
|
501 |
}) |
|
502 |
) |
|
503 |
}) |
|
504 | ||
505 | ! |
create_table <- reactive({ |
506 | ! |
req(iv_r()$is_valid()) |
507 | ! |
req(anl_q()) |
508 | ! |
param <- input$xaxis_param |
509 | ! |
xaxis_var <- input$yaxis_var # nolint |
510 | ! |
font_size <- input$font_size |
511 | ! |
trt_group <- input$trt_group |
512 | ||
513 | ! |
anl_q()$qenv %>% teal.code::eval_code( |
514 | ! |
code = bquote({ |
515 | ! |
tbl <- goshawk::t_summarytable( |
516 | ! |
data = ANL, |
517 | ! |
trt_group = .(trt_group), |
518 | ! |
param_var = .(param_var), |
519 | ! |
param = .(param), |
520 | ! |
xaxis_var = .(xaxis_var), |
521 | ! |
facet_var = .(input$facet_var) |
522 |
) |
|
523 |
}) |
|
524 |
) |
|
525 |
}) |
|
526 | ||
527 | ! |
plot_r <- reactive({ |
528 | ! |
create_plot()[["p"]] |
529 |
}) |
|
530 | ||
531 | ! |
boxplot_data <- teal.widgets::plot_with_settings_srv( |
532 | ! |
id = "boxplot", |
533 | ! |
plot_r = plot_r, |
534 | ! |
height = plot_height, |
535 | ! |
width = plot_width, |
536 | ! |
brushing = TRUE |
537 |
) |
|
538 | ||
539 | ! |
output$table_ui <- DT::renderDataTable({ |
540 | ! |
req(create_table()) |
541 | ! |
tbl <- create_table()[["tbl"]] |
542 | ||
543 | ! |
numeric_cols <- setdiff(names(dplyr::select_if(tbl, is.numeric)), "n") |
544 | ||
545 | ! |
DT::datatable(tbl, rownames = FALSE, options = list(scrollX = TRUE)) %>% |
546 | ! |
DT::formatRound(numeric_cols, 4) |
547 |
}) |
|
548 | ||
549 |
### REPORTER |
|
550 | ! |
if (with_reporter) { |
551 | ! |
card_fun <- function(comment) { |
552 | ! |
card <- teal::TealReportCard$new() |
553 | ! |
card$set_name("Box Plot") |
554 | ! |
card$append_text("Box Plot", "header2") |
555 | ! |
if (with_filter) card$append_fs(filter_panel_api$get_filter_state()) |
556 | ! |
card$append_text("Selected Options", "header3") |
557 | ! |
card$append_text( |
558 | ! |
paste( |
559 | ! |
formatted_data_constraint(input$constraint_var, input$constraint_range_min, input$constraint_range_max), |
560 | ! |
"\nFacet By:", |
561 | ! |
if (length(input$facet_var) != 0) input$facet_var else "None", |
562 | ! |
"\nSelect an X-axis Variable:", |
563 | ! |
input$xaxis_var |
564 |
), |
|
565 | ! |
style = "verbatim" |
566 |
) |
|
567 | ! |
card$append_text("Plot", "header3") |
568 | ! |
card$append_plot(plot_r(), dim = boxplot_data$dim()) |
569 | ! |
if (!comment == "") { |
570 | ! |
card$append_text("Comment", "header3") |
571 | ! |
card$append_text(comment) |
572 |
} |
|
573 | ! |
card$append_src( |
574 | ! |
paste( |
575 | ! |
teal.code::get_code( |
576 | ! |
teal.code::join(create_plot(), create_table()) |
577 |
), |
|
578 | ! |
collapse = "\n" |
579 |
) |
|
580 |
) |
|
581 | ! |
card |
582 |
} |
|
583 | ! |
teal.reporter::simple_reporter_srv("simple_reporter", reporter = reporter, card_fun = card_fun) |
584 |
} |
|
585 |
### |
|
586 | ||
587 |
# highlight plot area |
|
588 | ! |
output$brush_data <- DT::renderDataTable({ |
589 | ! |
boxplot_brush <- boxplot_data$brush() |
590 | ||
591 | ! |
ANL <- isolate(anl_q()$ANL) %>% droplevels() # nolint |
592 | ! |
validate_has_data(ANL, 2) |
593 | ||
594 | ! |
xvar <- isolate(input$xaxis_var) |
595 | ! |
yvar <- isolate(input$yaxis_var) |
596 | ! |
facetv <- isolate(input$facet_var) |
597 | ! |
trt_group <- isolate(input$trt_group) |
598 | ||
599 | ! |
req(all(c(xvar, yvar, facetv, trt_group) %in% names(ANL))) |
600 | ||
601 | ! |
df <- teal.widgets::clean_brushedPoints( |
602 | ! |
dplyr::select( |
603 | ! |
ANL, "USUBJID", dplyr::all_of(c(trt_group, facetv)), |
604 | ! |
"AVISITCD", "PARAMCD", dplyr::all_of(c(xvar, yvar)), "LOQFL" |
605 |
), |
|
606 | ! |
boxplot_brush |
607 |
) |
|
608 | ||
609 | ! |
numeric_cols <- names(dplyr::select_if(df, is.numeric)) |
610 | ||
611 | ! |
DT::datatable(df, rownames = FALSE, options = list(scrollX = TRUE)) %>% |
612 | ! |
DT::formatRound(numeric_cols, 4) |
613 |
}) |
|
614 | ||
615 | ! |
joined_qenvs <- reactive({ |
616 | ! |
req(create_plot(), create_table()) |
617 | ! |
teal.code::join(create_plot(), create_table()) |
618 |
}) |
|
619 | ||
620 | ! |
teal.widgets::verbatim_popup_srv( |
621 | ! |
id = "warning", |
622 | ! |
verbatim_content = reactive(teal.code::get_warnings(joined_qenvs())), |
623 | ! |
title = "Warning", |
624 | ! |
disabled = reactive(is.null(teal.code::get_warnings(joined_qenvs()))) |
625 |
) |
|
626 | ||
627 | ! |
teal.widgets::verbatim_popup_srv( |
628 | ! |
id = "rcode", |
629 | ! |
verbatim_content = reactive(teal.code::get_code(joined_qenvs())), |
630 | ! |
title = "Show R Code for Boxplot" |
631 |
) |
|
632 |
}) |
|
633 |
} |
1 |
#' helper for writing arm mapping and ordering code. |
|
2 |
#' |
|
3 |
#' Provides lines of code for left hand side of arm mapping. user must provide right hand side |
|
4 |
#' |
|
5 |
#' @details SPA configure study specific pre-processing for deploying `goshawk`. writing the code for `ARM` mapping and |
|
6 |
#' ordering is tedious. this function helps to get that started by providing the left hand side of the |
|
7 |
#' mapping and ordering syntax. call the function and then copy and paste the resulting code from the console |
|
8 |
#' into the app.R file. |
|
9 |
#' |
|
10 |
#' @param df_armvar the dataframe and column name containing treatment code. e.g. `ADSL$ARMCD` |
|
11 |
#' @param code controls whether mapping or ordering code is written to console. Valid values: `"M"` and `"O"`. |
|
12 |
#' |
|
13 |
#' @export |
|
14 |
#' |
|
15 |
#' @examples |
|
16 |
#' |
|
17 |
#' ADSL <- goshawk::rADSL |
|
18 |
#' |
|
19 |
#' # get treatment mapping code |
|
20 |
#' maptrt(df_armvar = ADSL$ARMCD, code = "M") |
|
21 |
#' |
|
22 |
#' # get treatment ordering code |
|
23 |
#' maptrt(df_armvar = ADSL$ARMCD, code = "O") |
|
24 |
maptrt <- function(df_armvar, code = c("M", "O")) { |
|
25 | ! |
code <- match.arg(code) |
26 | ||
27 |
# get arm variable |
|
28 | ! |
trtvar <- strsplit(deparse(substitute(df_armvar)), "[$]")[[1]][2] |
29 | ||
30 | ! |
dftrt <- data.frame(unique(df_armvar)) %>% |
31 | ! |
dplyr::mutate(trt_mapping = paste0("\"", unique(df_armvar), "\"", " = \"\",")) %>% |
32 | ! |
dplyr::mutate(trt_ordering = paste0(eval(trtvar), " == \"", unique(df_armvar), "\"", " ~ ,")) |
33 | ||
34 | ! |
if (toupper(code) == "M") { |
35 | ! |
print(unname(dftrt["trt_mapping"]), row.names = FALSE) |
36 | ! |
} else if (toupper(code) == "O") { |
37 | ! |
print(unname(dftrt["trt_ordering"]), row.names = FALSE) |
38 |
} |
|
39 |
} |
1 |
templ_ui_output_datatable <- function(ns) { |
|
2 | ! |
div( |
3 | ! |
teal.widgets::plot_with_settings_ui(id = ns("plot")), |
4 | ! |
br(), hr(), |
5 | ! |
h4("Selected Data Points"), |
6 | ! |
DT::dataTableOutput(ns("brush_data")) |
7 |
) |
|
8 |
} |
|
9 | ||
10 |
templ_ui_dataname <- function(dataname) { |
|
11 | ! |
tags$label(dataname, "Data Settings", class = "text-primary") |
12 |
} |
|
13 | ||
14 |
# UI to create params (biomarker, value of PARAMCD) and vars (column, e.g. AVAL column) select fields for x and y |
|
15 |
templ_ui_params_vars <- function(ns, |
|
16 |
# x |
|
17 |
xparam_choices = NULL, |
|
18 |
xparam_selected = NULL, |
|
19 |
xparam_label = NULL, # biomarker, e.g. ALT |
|
20 |
xchoices = NULL, |
|
21 |
xselected = NULL, |
|
22 |
xvar_label = NULL, # variable, e.g. AVAL |
|
23 |
# y |
|
24 |
yparam_choices = NULL, |
|
25 |
yparam_selected = NULL, |
|
26 |
yparam_label = NULL, # biomarker, e.g. ALT |
|
27 |
ychoices = NULL, |
|
28 |
yselected = NULL, |
|
29 |
yvar_label = NULL, # variable, e.g. AVAL |
|
30 |
multiple = FALSE) { |
|
31 | ! |
if (is.null(xparam_choices) && !is.null(xchoices) && !is.null(yparam_choices)) { |
32 |
# otherwise, xchoices will appear first without any biomarker to select and this looks odd in the UI |
|
33 | ! |
stop( |
34 | ! |
"You have to specify xparam choices rather than yparamchoices |
35 | ! |
if both xvar and yvar should be values for the same biomarker." |
36 |
) |
|
37 |
} |
|
38 | ! |
tagList( |
39 | ! |
if (!is.null(xparam_choices)) { |
40 | ! |
teal.widgets::optionalSelectInput( |
41 | ! |
ns("xaxis_param"), |
42 | ! |
`if`(is.null(xparam_label), "Select an X-Axis Biomarker", xparam_label), |
43 | ! |
xparam_choices, |
44 | ! |
`if`(is.null(xparam_selected), xparam_choices[1], xparam_selected), |
45 | ! |
multiple = FALSE |
46 |
) |
|
47 |
}, |
|
48 | ! |
if (!is.null(xchoices)) { |
49 | ! |
teal.widgets::optionalSelectInput( |
50 | ! |
ns("xaxis_var"), |
51 | ! |
`if`(is.null(xvar_label), "Select an X-Axis Variable", xvar_label), |
52 | ! |
xchoices, xselected, |
53 | ! |
multiple = multiple |
54 |
) |
|
55 |
}, |
|
56 | ! |
if (!is.null(yparam_choices)) { |
57 | ! |
teal.widgets::optionalSelectInput( |
58 | ! |
ns("yaxis_param"), |
59 | ! |
`if`(is.null(yparam_label), "Select an Y-Axis Biomarker", yparam_label), |
60 | ! |
yparam_choices, |
61 | ! |
`if`(is.null(yparam_selected), yparam_choices[1], yparam_selected), |
62 | ! |
multiple = FALSE |
63 |
) |
|
64 |
}, |
|
65 | ! |
if (!is.null(ychoices)) { |
66 | ! |
teal.widgets::optionalSelectInput( |
67 | ! |
ns("yaxis_var"), |
68 | ! |
`if`(is.null(yvar_label), "Select a Y-Axis Variable", yvar_label), |
69 | ! |
ychoices, yselected, |
70 | ! |
multiple = multiple |
71 |
) |
|
72 |
} |
|
73 |
) |
|
74 |
} |
1 |
.onLoad <- function(libname, pkgname) { # nolint |
|
2 | ! |
teal.logger::register_logger(namespace = "teal.goshawk") |
3 |
} |
1 |
#' Include `CSS` files from `/inst/css/` package directory to application header |
|
2 |
#' |
|
3 |
#' `system.file` should not be used to access files in other packages, it does |
|
4 |
#' not work with `devtools`. Therefore, we redefine this method in each package |
|
5 |
#' as needed. Thus, we do not export this method. |
|
6 |
#' |
|
7 |
#' @param pattern (`character`) pattern of files to be included |
|
8 |
#' |
|
9 |
#' @return HTML code that includes `CSS` files |
|
10 |
#' @keywords internal |
|
11 |
include_css_files <- function(pattern = "*") { |
|
12 | ! |
css_files <- list.files( |
13 | ! |
system.file("css", package = "teal.goshawk", mustWork = TRUE), |
14 | ! |
pattern = pattern, full.names = TRUE |
15 |
) |
|
16 | ! |
if (length(css_files) == 0) { |
17 | ! |
return(NULL) |
18 |
} |
|
19 | ! |
return(shiny::singleton(shiny::tags$head(lapply(css_files, shiny::includeCSS)))) |
20 |
} |
|
21 | ||
22 |
plots_per_row_validate_rules <- function(required = TRUE) { |
|
23 | ! |
msg <- "Number of plots per row must be a positive integer" |
24 | ! |
shinyvalidate::compose_rules( |
25 | ! |
if (required) { |
26 | ! |
shinyvalidate::sv_required(msg) |
27 |
} else { |
|
28 | ! |
shinyvalidate::sv_optional() |
29 |
}, |
|
30 | ! |
shinyvalidate::sv_integer(msg), |
31 | ! |
shinyvalidate::sv_gt(0, message_fmt = msg) |
32 |
) |
|
33 |
} |