1 # Main logic block for data-related interactions.
2 data_logic
<- function(input
, output
, react_values
) {
3 # Initialize a data frame to hold the datasets.
4 react_values$data_table
<- data.frame(
6 `Time units`
= character(0),
7 `Case counts`
= character(0),
11 render_plot(input
, output
)
12 single_entry(input
, output
, react_values
)
13 manual_bulk_entry(input
, output
, react_values
)
14 upload_data(input
, output
, react_values
)
15 load_samples(input
, output
, react_values
)
16 render_data(output
, react_values
)
17 delete_data(input
, react_values
)
18 export_data(output
, react_values
)
21 # Convert the input case counts string to an integer vector.
22 tokenize_counts
<- function(counts_str
) {
23 suppressWarnings(as.integer(unlist(strsplit(trimws(counts_str
), ","))))
26 # Render the preview plot for single entry data.
27 render_plot
<- function(input
, output
) {
29 counts
<- tokenize_counts(input$data_counts
)
30 if (length(counts
) > 0 && !anyNA(counts
) && all(counts
>= 0)) {
31 output$data_plot
<- renderPlot(
32 plot(seq_along(counts
) - 1, counts
, type
= "o", pch
= 16, col
= "black",
33 xlab
= input$data_units
, ylab
= "Cases", cex.lab
= 1.5,
34 xlim
= c(0, max(length(counts
) - 1, 1)), ylim
= c(0, max(counts
, 1))
38 output$data_plot
<- renderPlot(
39 plot(NULL, xlim
= c(0, 10), ylim
= c(0, 10),
40 xlab
= input$data_units
, ylab
= "Cases", cex.lab
= 1.5
47 # Add a single dataset to the existing table.
48 single_entry
<- function(input
, output
, react_values
) {
49 observeEvent(input$data_single
, {
52 # Ensure the dataset name is neither blank nor a duplicate.
53 name
<- trimws(input$data_name
)
55 output$data_name_warn
<- renderText("The dataset name cannot be blank.")
57 } else if (name
%in% react_values$data_table
[, 1]) {
58 output$data_name_warn
<- renderText(
59 "There is already a dataset with the specified name."
63 output$data_name_warn
<- renderText("")
66 # Ensure the case counts are specified as a comma-separated of one or more
67 # non-negative integers.
68 counts
<- tokenize_counts(input$data_counts
)
69 if (length(counts
) == 0) {
70 output$data_counts_warn
<- renderText("Case counts cannot be blank.")
72 } else if (anyNA(counts
) || any(counts
< 0)) {
73 output$data_counts_warn
<- renderText(
74 "Case counts can only contain non-negative integers."
78 output$data_counts_warn
<- renderText("")
82 # Add the new dataset to the data table.
83 new_row
<- data.frame(name
, input$data_units
, toString(counts
))
84 colnames(new_row
) <- c("Name", "Time units", "Case counts")
85 react_values$data_table
<- rbind(react_values$data_table
, new_row
)
87 # Evaluate all existing estimators on the new dataset and update the
88 # corresponding row in the estimates table.
89 update_estimates_rows(new_row
, react_values
)
91 showNotification("Dataset added successfully.",
92 duration
= 3, id
= "notify-success"
98 manual_bulk_entry
<- function(input
, output
, react_values
) {
99 observeEvent(input$data_bulk
, {
100 validate_bulk_data(input
, output
, react_values
, "data_area")
104 upload_data
<- function(input
, output
, react_values
) {
105 observeEvent(input$data_upload
, {
106 validate_bulk_data(input
, output
, react_values
, "data_upload")
110 validate_bulk_data
<- function(input
, output
, react_values
, data_source
) {
113 if (data_source
== "data_area") {
114 datasets
<- read.csv(text
= input$data_area
, header
= FALSE, sep
= ",")
115 } else if (data_source
== "data_upload") {
116 datasets
<- read.csv(
117 file
= input$data_upload$datapath
, header
= FALSE, sep
= ","
121 names
<- trimws(datasets
[, 1])
122 units
<- trimws(datasets
[, 2])
123 counts
<- apply(data.frame(datasets
[, 3:ncol(datasets
)]), 1,
125 row
<- suppressWarnings(as.integer(row
))
126 toString(row
[!is.na(row
) & row
>= 0])
132 # Ensure the dataset names are neither blank nor duplicates.
133 if (anyNA(names
) || any(names
== "")) {
134 warning_text
<- paste0(warning_text
,
135 "Each row must begin with a non-blank dataset name.<br>"
138 if (length(unique(names
)) != length(names
)) {
139 warning_text
<- paste0(warning_text
,
140 "The rows contain duplicate dataset names.<br>"
143 if (any(names
%in% react_values$data_table
[, 1])) {
144 warning_text
<- paste0(warning_text
,
145 "The rows contain dataset names which already exist.<br>"
150 # Ensure the second entry in each row is a time unit equal to
152 if (!all(units
%in% c("Days", "Weeks"))) {
153 warning_text
<- paste0(warning_text
,
154 "The second entry in each row must be either 'Days' or 'Weeks'.<br>"
158 # Ensure the counts in each row have at least one non-negative integer.
159 if (any(counts
== "")) {
160 warning_text
<- paste0(warning_text
,
161 "Each row must contain at least one non-negative integer.<br>"
165 output
[[paste0(data_source
, "_warn")]] <- renderUI(HTML(warning_text
))
167 if (warning_text
== "") {
168 # Add the new datasets to the data table.
169 new_rows
<- data.frame(names
, units
, counts
)
170 colnames(new_rows
) <- c("Name", "Time units", "Case counts")
171 react_values$data_table
<- rbind(react_values$data_table
, new_rows
)
173 # Evaluate all existing estimators on the new datasets and update the
174 # corresponding rows in the estimates table.
175 update_estimates_rows(new_rows
, react_values
)
177 showNotification("Datasets added successfully.",
178 duration
= 3, id
= "notify-success"
182 error
= function(e
) {
183 output
[[paste0(data_source
, "_warn")]] <- renderText(
184 "The input does not match the required format."
190 # Load sample datasets.
191 load_samples
<- function(input
, output
, react_values
) {
192 observeEvent(input$data_samples
, {
197 # COVID-19 Canada, March 2020 (weekly).
198 if (input$covid_canada
) {
199 names
<- c(names
, "COVID-19 Canada 2020/03/03 - 2020/03/31")
200 units
<- c(units
, "Weeks")
201 counts
<- c(counts
, toString(Rnaught
::COVIDCanada
[seq(41, 69, 7), 2]))
203 # COVID-19 Ontario, March 2020 (weekly).
204 if (input$covid_ontario
) {
205 names
<- c(names
, "COVID-19 Ontario 2020/03/03 - 2020/03/31")
206 units
<- c(units
, "Weeks")
208 toString(Rnaught
::COVIDCanadaPT
[seq(10176, 10204, 7), 3])
212 if (length(names
) == 0) {
213 output$data_samples_warn
<- renderText(
214 "At least one sample dataset must be selected."
216 } else if (any(names
%in% react_values$data_table
[, 1])) {
217 output$data_samples_warn
<- renderText(
218 "At least one of the selected dataset names already exist."
221 output$data_samples_warn
<- renderText("")
223 new_rows
<- data.frame(names
, units
, counts
)
224 colnames(new_rows
) <- c("Name", "Time units", "Case counts")
225 react_values$data_table
<- rbind(react_values$data_table
, new_rows
)
227 # Evaluate all existing estimators on the sample datasets and update the
228 # corresponding rows in the estimates table.
229 update_estimates_rows(new_rows
, react_values
)
231 showNotification("Datasets added successfully.",
232 duration
= 3, id
= "notify-success"
238 # Render the data table when new datasets are added.
239 render_data
<- function(output
, react_values
) {
241 output$data_table
<- DT
::renderDataTable(react_values$data_table
)
245 # Delete rows in the data table,
246 # and the corresponding rows in the estimates table.
247 delete_data
<- function(input
, react_values
) {
248 observeEvent(input$data_delete
, {
249 new_table
<- react_values$data_table
[-input$data_table_rows_selected
, ]
250 if (nrow(new_table
) > 0) {
251 rownames(new_table
) <- seq_len(nrow(new_table
))
253 react_values$data_table
<- new_table
255 if (ncol(react_values$estimates_table
) == 1) {
256 react_values$estimates_table
<- data.frame(
257 Datasets
= react_values$data_table
[, 1]
260 react_values$estimates_table
<-
261 react_values$estimates_table
[-input$data_table_rows_selected
, ]
266 # Export data table as a CSV file.
267 export_data
<- function(output
, react_values
) {
268 output$data_export
<- downloadHandler(
269 filename
= function() {
270 paste0("Rnaught_data_", format(Sys.time(), "%y-%m-%d_%H-%M-%S"), ".csv")
272 content
= function(file
) {
273 write.csv(react_values$data_table
, file
, row.names
= FALSE)
278 # When new datasets are added, evaluate all existing estimators on them and
279 # add new rows to the estimates table.
280 update_estimates_rows
<- function(datasets
, react_values
) {
281 new_rows
<- data.frame(
282 matrix(nrow
= nrow(datasets
), ncol
= ncol(react_values$estimates_table
))
284 colnames(new_rows
) <- colnames(react_values$estimates_table
)
286 for (row
in seq_len(nrow(datasets
))) {
287 new_rows
[row
, 1] <- datasets
[row
, 1]
289 if (length(react_values$estimators
) > 0) {
290 for (col
in 2:ncol(react_values$estimates_table
)) {
291 new_rows
[row
, col
] <- eval_estimator(
292 react_values$estimators
[[col
- 1]], datasets
[row
, ]
298 react_values$estimates_table
<- rbind(
299 react_values$estimates_table
, new_rows