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 manual_entry(input
, output
, react_values
)
12 upload_data(input
, output
, react_values
)
13 load_samples(input
, output
, react_values
)
14 render_data_table(output
, react_values
)
15 render_plot(input
, output
, react_values
, "Days")
16 render_plot(input
, output
, react_values
, "Weeks")
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 plots for daily and weekly data when the data table is updated.
27 render_plot
<- function(input
, output
, react_values
, time_units
) {
29 datasets
<- react_values$data_table
[
30 which(react_values$data_table
[["Time units"]] == time_units
),
33 data_plot
<- plotly
::plot_ly(type
= "scatter", mode
= "lines")
34 if (nrow(datasets
) > 0) {
35 for (i
in seq_len(nrow(datasets
))) {
36 counts
<- tokenize_counts(datasets
[i
, 3])
37 data_plot
<- plotly
::add_trace(data_plot
,
38 x
= seq_along(counts
) - 1, y
= counts
, name
= datasets
[i
, 1]
44 if (time_units
== "Days") "Daily" else "Weekly", "case counts"
47 data_plot
<- plotly
::layout(data_plot
, title
= plot_title
,
48 xaxis
= list(title
= time_units
), yaxis
= list(title
= "Cases")
51 data_plot
<- plotly
::config(data_plot
, displaylogo
= FALSE,
52 toImageButtonOptions
= list(
53 filename
= paste0("Rnaught_data_", tolower(time_units
), "_plot")
57 output
[[paste0("data_plot_", tolower(time_units
))]] <-
58 plotly
::renderPlotly(data_plot
)
62 # Validate and add manually-entered datasets.
63 manual_entry
<- function(input
, output
, react_values
) {
64 observeEvent(input$data_bulk
, {
65 validate_data(input
, output
, react_values
, "data_area")
69 # Validate and add datasets from a CSV file.
70 upload_data
<- function(input
, output
, react_values
) {
71 observeEvent(input$data_upload
, {
72 validate_data(input
, output
, react_values
, "data_upload")
76 # Validate datasets and update the data table.
77 validate_data
<- function(input
, output
, react_values
, data_source
) {
80 if (data_source
== "data_area") {
81 datasets
<- read.csv(text
= input$data_area
, header
= FALSE, sep
= ",")
82 } else if (data_source
== "data_upload") {
84 file
= input$data_upload$datapath
, header
= FALSE, sep
= ","
88 names
<- trimws(datasets
[, 1])
89 units
<- trimws(datasets
[, 2])
90 counts
<- apply(data.frame(datasets
[, 3:ncol(datasets
)]), 1,
92 row
<- suppressWarnings(as.integer(row
))
93 toString(row
[!is.na(row
) & row
>= 0])
99 # Ensure the dataset names are neither blank nor duplicates.
100 if (anyNA(names
) || any(names
== "")) {
101 warning_text
<- paste0(warning_text
,
102 "Each row must begin with a non-blank dataset name.<br>"
105 if (length(unique(names
)) != length(names
)) {
106 warning_text
<- paste0(warning_text
,
107 "The rows contain duplicate dataset names.<br>"
110 if (any(names
%in% react_values$data_table
[, 1])) {
111 warning_text
<- paste0(warning_text
,
112 "The rows contain dataset names which already exist.<br>"
117 # Ensure the second entry in each row is a time unit equal to
119 if (!all(units
%in% c("Days", "Weeks"))) {
120 warning_text
<- paste0(warning_text
,
121 "The second entry in each row must be either 'Days' or 'Weeks'.<br>"
125 # Ensure the counts in each row have at least one non-negative integer.
126 if (any(counts
== "")) {
127 warning_text
<- paste0(warning_text
,
128 "Each row must contain at least one non-negative integer.<br>"
132 output
[[paste0(data_source
, "_warn")]] <- renderUI(HTML(warning_text
))
134 if (warning_text
== "") {
135 # Add the new datasets to the data table.
136 new_rows
<- data.frame(names
, units
, counts
)
137 colnames(new_rows
) <- c("Name", "Time units", "Case counts")
138 react_values$data_table
<- rbind(react_values$data_table
, new_rows
)
140 # Evaluate all existing estimators on the new datasets and update the
141 # corresponding columns in the estimates table.
142 update_estimates_cols(new_rows
, react_values
)
144 showNotification("Datasets added successfully.", duration
= 3)
147 error
= function(e
) {
148 output
[[paste0(data_source
, "_warn")]] <- renderText(
149 "The input does not match the required format."
155 # Load sample datasets.
156 load_samples
<- function(input
, output
, react_values
) {
157 observeEvent(input$data_samples
, {
162 # COVID-19 Canada, March 2020 (weekly).
163 if (input$covid_canada
) {
164 names
<- c(names
, "COVID-19 Canada 2020/03/03 - 2020/03/31")
165 units
<- c(units
, "Weeks")
166 counts
<- c(counts
, toString(Rnaught
::COVIDCanada
[seq(41, 69, 7), 2]))
168 # COVID-19 Ontario, March 2020 (weekly).
169 if (input$covid_ontario
) {
170 names
<- c(names
, "COVID-19 Ontario 2020/03/03 - 2020/03/31")
171 units
<- c(units
, "Weeks")
173 toString(Rnaught
::COVIDCanadaPT
[seq(10176, 10204, 7), 3])
177 if (length(names
) == 0) {
178 output$data_samples_warn
<- renderText(
179 "At least one sample dataset must be selected."
181 } else if (any(names
%in% react_values$data_table
[, 1])) {
182 output$data_samples_warn
<- renderText(
183 "At least one of the selected dataset names already exist."
186 output$data_samples_warn
<- renderText("")
188 new_rows
<- data.frame(names
, units
, counts
)
189 colnames(new_rows
) <- c("Name", "Time units", "Case counts")
190 react_values$data_table
<- rbind(react_values$data_table
, new_rows
)
192 # Evaluate all existing estimators on the sample datasets and update the
193 # corresponding columns in the estimates table.
194 update_estimates_cols(new_rows
, react_values
)
196 showNotification("Datasets added successfully.", duration
= 3)
201 # Render the data table when new datasets are added.
202 render_data_table
<- function(output
, react_values
) {
204 output$data_table
<- DT
::renderDataTable(
205 react_values$data_table
, rownames
= FALSE
210 # Delete rows in the data table and the corresponding columns in the estimates
212 delete_data
<- function(input
, react_values
) {
213 observeEvent(input$data_delete
, {
214 rows_selected
<- input$data_table_rows_selected
215 react_values$data_table
<- react_values$data_table
[-rows_selected
, ]
216 react_values$estimates_table
<-
217 react_values$estimates_table
[, -(rows_selected
+ 2)]
221 # Export data table as a CSV file.
222 export_data
<- function(output
, react_values
) {
223 output$data_export
<- downloadHandler(
224 filename
= function() {
225 paste0("Rnaught_data_", format(Sys.time(), "%y-%m-%d_%H-%M-%S"), ".csv")
227 content
= function(file
) {
228 write.csv(react_values$data_table
, file
, row.names
= FALSE)
233 # When new datasets are added, evaluate all existing estimators on them and
234 # add new columns to the estimates table.
235 update_estimates_cols
<- function(datasets
, react_values
) {
236 new_cols
<- data.frame(
237 matrix(nrow
= nrow(react_values$estimates_table
), ncol
= nrow(datasets
))
239 colnames(new_cols
) <- datasets
[, 1]
241 if (nrow(new_cols
) > 0) {
242 for (row
in seq_len(nrow(new_cols
))) {
243 estimator
<- react_values$estimators
[[row
]]
244 for (col
in seq_len(ncol(new_cols
))) {
245 new_cols
[row
, col
] <- eval_estimator(estimator
, datasets
[col
, ])
250 react_values$estimates_table
<- cbind(
251 react_values$estimates_table
, new_cols