]> nmode's Git Repositories - Rnaught/blob - inst/app/scripts/data.R
eebd14fd6ba4bd830c68915ffa5436ba85d5690d
[Rnaught] / inst / app / scripts / data.R
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(
5 Name = character(0),
6 `Time units` = character(0),
7 `Case counts` = character(0),
8 check.names = FALSE
9 )
10
11 render_plot(input, output)
12 single_entry(input, output, react_values)
13 bulk_entry(input, output, react_values)
14 render_data(output, react_values)
15 delete_data(input, react_values)
16 export_data(output, react_values)
17 }
18
19 # Convert the input case counts string to an integer vector.
20 tokenize_counts <- function(counts_str) {
21 suppressWarnings(as.integer(unlist(strsplit(trimws(counts_str), ","))))
22 }
23
24 # Render the preview plot for single entry data.
25 render_plot <- function(input, output) {
26 observe({
27 counts <- tokenize_counts(input$data_counts)
28 if (length(counts) > 0 && !anyNA(counts) && all(counts >= 0)) {
29 output$data_plot <- renderPlot(
30 plot(seq_along(counts) - 1, counts, type = "o", pch = 16, col = "black",
31 xlab = input$data_units, ylab = "Cases", cex.lab = 1.5,
32 xlim = c(0, max(length(counts) - 1, 1)), ylim = c(0, max(counts, 1))
33 )
34 )
35 } else {
36 output$data_plot <- renderPlot(
37 plot(NULL, xlim = c(0, 10), ylim = c(0, 10),
38 xlab = input$data_units, ylab = "Cases", cex.lab = 1.5
39 )
40 )
41 }
42 })
43 }
44
45 # Add a single dataset to the existing table.
46 single_entry <- function(input, output, react_values) {
47 observeEvent(input$data_single, {
48 valid <- TRUE
49
50 # Ensure the dataset name is neither blank nor a duplicate.
51 name <- trimws(input$data_name)
52 if (name == "") {
53 output$data_name_warn <- renderText("The dataset name cannot be blank.")
54 valid <- FALSE
55 } else if (name %in% react_values$data_table[, 1]) {
56 output$data_name_warn <- renderText(
57 "There is already a dataset with the specified name."
58 )
59 valid <- FALSE
60 } else {
61 output$data_name_warn <- renderText("")
62 }
63
64 # Ensure the case counts are specified as a comma-separated of one or more
65 # non-negative integers.
66 counts <- tokenize_counts(input$data_counts)
67 if (length(counts) == 0) {
68 output$data_counts_warn <- renderText("Case counts cannot be blank.")
69 valid <- FALSE
70 } else if (anyNA(counts) || any(counts < 0)) {
71 output$data_counts_warn <- renderText(
72 "Case counts can only contain non-negative integers."
73 )
74 valid <- FALSE
75 } else {
76 output$data_counts_warn <- renderText("")
77 }
78
79 if (valid) {
80 # Add the new dataset to the data table.
81 new_row <- data.frame(name, input$data_units, toString(counts))
82 colnames(new_row) <- c("Name", "Time units", "Case counts")
83 react_values$data_table <- rbind(react_values$data_table, new_row)
84
85 # Evaluate all existing estimators on the new dataset and update the
86 # corresponding row in the estimates table.
87 update_estimates_rows(new_row, react_values)
88
89 showNotification("Dataset added successfully.",
90 duration = 3, id = "notify-success"
91 )
92 }
93 })
94 }
95
96 # Add multiple datasets to the existing table.
97 bulk_entry <- function(input, output, react_values) {
98 observeEvent(input$data_bulk, {
99 tryCatch(
100 {
101 datasets <- read.csv(text = input$data_area, header = FALSE, sep = ",")
102
103 names <- trimws(datasets[, 1])
104 units <- trimws(datasets[, 2])
105 counts <- apply(datasets[, 3:ncol(datasets)], 1,
106 function(row) {
107 row <- suppressWarnings(as.integer(row))
108 toString(row[!is.na(row) & row >= 0])
109 }
110 )
111
112 warning_text <- ""
113
114 # Ensure the dataset names are neither blank nor duplicates.
115 if (anyNA(names) || any(names == "")) {
116 warning_text <- paste0(warning_text, sep = "<br>",
117 "Each row must begin with a non-blank dataset name."
118 )
119 } else {
120 if (length(unique(names)) != length(names)) {
121 warning_text <- paste0(warning_text, sep = "<br>",
122 "The rows contain duplicate dataset names."
123 )
124 }
125 if (any(names %in% react_values$data_table[, 1])) {
126 warning_text <- paste0(warning_text, sep = "<br>",
127 "The rows contain dataset names which already exist."
128 )
129 }
130 }
131
132 # Ensure the second entry in each row is a time unit equal to
133 # "Days" or "Weeks".
134 if (!all(units %in% c("Days", "Weeks"))) {
135 warning_text <- paste0(warning_text, sep = "<br>",
136 "The second entry in each row must be either 'Days' or 'Weeks'."
137 )
138 }
139
140 # Ensure the counts in each row have at least one non-negative integer.
141 if (any(counts == "")) {
142 warning_text <- paste0(warning_text, sep = "<br>",
143 "Each row must contain at least one non-negative integer."
144 )
145 }
146
147 output$data_area_warn <- renderUI(HTML(warning_text))
148
149 if (warning_text == "") {
150 # Add the new datasets to the data table.
151 new_rows <- data.frame(names, units, counts)
152 colnames(new_rows) <- c("Name", "Time units", "Case counts")
153 react_values$data_table <- rbind(react_values$data_table, new_rows)
154
155 # Evaluate all existing estimators on the new dataset and update the
156 # corresponding row in the estimates table.
157 update_estimates_rows(new_rows, react_values)
158
159 showNotification("Datasets added successfully.",
160 duration = 3, id = "notify-success"
161 )
162 }
163 },
164 error = function(e) {
165 output$data_area_warn <- renderText(
166 "The input does not match the required format."
167 )
168 }
169 )
170 })
171 }
172
173 # Render the data table when new datasets are added.
174 render_data <- function(output, react_values) {
175 observe({
176 output$data_table <- DT::renderDataTable(react_values$data_table)
177 })
178 }
179
180 # Delete rows in the data table,
181 # and the corresponding rows in the estimates table.
182 delete_data <- function(input, react_values) {
183 observeEvent(input$data_delete, {
184 new_table <- react_values$data_table[-input$data_table_rows_selected, ]
185 if (nrow(new_table) > 0) {
186 rownames(new_table) <- seq_len(nrow(new_table))
187 }
188 react_values$data_table <- new_table
189
190 if (ncol(react_values$estimates_table) == 1) {
191 react_values$estimates_table <- data.frame(
192 Datasets = react_values$data_table[, 1]
193 )
194 } else {
195 react_values$estimates_table <-
196 react_values$estimates_table[-input$data_table_rows_selected, ]
197 }
198 })
199 }
200
201 # Export data table as a CSV file.
202 export_data <- function(output, react_values) {
203 output$data_export <- downloadHandler(
204 filename = function() {
205 paste0("Rnaught_data_", format(Sys.time(), "%y-%m-%d_%H-%M-%S"), ".csv")
206 },
207 content = function(file) {
208 write.csv(react_values$data_table, file, row.names = FALSE)
209 }
210 )
211 }
212
213 # When new datasets are added, evaluate all existing estimators on them and
214 # add new rows to the estimates table.
215 update_estimates_rows <- function(datasets, react_values) {
216 new_rows <- data.frame(
217 matrix(nrow = nrow(datasets), ncol = ncol(react_values$estimates_table))
218 )
219 colnames(new_rows) <- colnames(react_values$estimates_table)
220
221 for (row in seq_len(nrow(datasets))) {
222 new_rows[row, 1] <- datasets[row, 1]
223
224 if (length(react_values$estimators) > 0) {
225 for (col in 2:ncol(react_values$estimates_table)) {
226 new_rows[row, col] <- eval_estimator(
227 react_values$estimators[[col - 1]], datasets[row, ]
228 )
229 }
230 }
231 }
232
233 react_values$estimates_table <- rbind(
234 react_values$estimates_table, new_rows
235 )
236 }