forked from rstudio/shiny
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprogress.R
283 lines (253 loc) · 9.71 KB
/
progress.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#' Reporting progress (object-oriented API)
#'
#' Reports progress to the user during long-running operations.
#'
#' This package exposes two distinct programming APIs for working with
#' progress. \code{\link{withProgress}} and \code{\link{setProgress}}
#' together provide a simple function-based interface, while the
#' \code{Progress} reference class provides an object-oriented API.
#'
#' Instantiating a \code{Progress} object causes a progress panel to be
#' created, and it will be displayed the first time the \code{set}
#' method is called. Calling \code{close} will cause the progress panel
#' to be removed.
#'
#' \strong{Methods}
#' \describe{
#' \item{\code{initialize(session, min = 0, max = 1)}}{
#' Creates a new progress panel (but does not display it).
#' }
#' \item{\code{set(value = NULL, message = NULL, detail = NULL)}}{
#' Updates the progress panel. When called the first time, the
#' progress panel is displayed.
#' }
#' \item{\code{inc(amount = 0.1, message = NULL, detail = NULL)}}{
#' Like \code{set}, this updates the progress panel. The difference is
#' that \code{inc} increases the progress bar by \code{amount}, instead
#' of setting it to a specific value.
#' }
#' \item{\code{close()}}{
#' Removes the progress panel. Future calls to \code{set} and
#' \code{close} will be ignored.
#' }
#' }
#'
#' @param session The Shiny session object, as provided by
#' \code{shinyServer} to the server function.
#' @param min The value that represents the starting point of the
#' progress bar. Must be less tham \code{max}.
#' @param max The value that represents the end of the progress bar.
#' Must be greater than \code{min}.
#' @param message A single-element character vector; the message to be
#' displayed to the user, or \code{NULL} to hide the current message
#' (if any).
#' @param detail A single-element character vector; the detail message
#' to be displayed to the user, or \code{NULL} to hide the current
#' detail message (if any). The detail message will be shown with a
#' de-emphasized appearance relative to \code{message}.
#' @param value A numeric value at which to set
#' the progress bar, relative to \code{min} and \code{max}.
#' \code{NULL} hides the progress bar, if it is currently visible.
#' @param amount Single-element numeric vector; the value at which to set
#' the progress bar, relative to \code{min} and \code{max}.
#' \code{NULL} hides the progress bar, if it is currently visible.
#' @param amount For the \code{inc()} method, a numeric value to increment the
#' progress bar.
#'
#' @examples
#' \dontrun{
#' # server.R
#' shinyServer(function(input, output, session) {
#' output$plot <- renderPlot({
#' progress <- shiny::Progress$new(session, min=1, max=15)
#' on.exit(progress$close())
#'
#' progress$set(message = 'Calculation in progress',
#' detail = 'This may take a while...')
#'
#' for (i in 1:15) {
#' progress$set(value = i)
#' Sys.sleep(0.5)
#' }
#' plot(cars)
#' })
#' })
#' }
#' @seealso \code{\link{withProgress}}
#' @format NULL
#' @usage NULL
#' @export
Progress <- R6Class(
'Progress',
portable = TRUE,
public = list(
initialize = function(session = getDefaultReactiveDomain(), min = 0, max = 1) {
if (!inherits(session, "ShinySession"))
stop("'session' is not a ShinySession object.")
private$session <- session
private$id <- paste(as.character(as.raw(runif(8, min=0, max=255))), collapse='')
private$min <- min
private$max <- max
private$value <- NULL
private$closed <- FALSE
session$sendProgress('open', list(id = private$id))
},
set = function(value = NULL, message = NULL, detail = NULL) {
if (private$closed) {
warning("Attempting to set progress, but progress already closed.")
return()
}
if (is.null(value) || is.na(value)) {
value <- NULL
} else {
# Normalize value to number between 0 and 1
value <- min(1, max(0, (value - private$min) / (private$max - private$min)))
}
private$value <- value
data <- dropNulls(list(
id = private$id,
message = message,
detail = detail,
value = value
))
private$session$sendProgress('update', data)
},
inc = function(amount = 0.1, message = NULL, detail = NULL) {
value <- min(self$getValue() + amount, private$max)
self$set(value, message, detail)
},
getMin = function() private$min,
getMax = function() private$max,
# Return value (not the normalized 0-1 value, but in the original range)
getValue = function() {
private$value * (private$max - private$min) + private$min
},
close = function() {
if (private$closed) {
warning("Attempting to close progress, but progress already closed.")
return()
}
private$session$sendProgress('close', list(id = private$id))
private$closed <- TRUE
}
),
private = list(
session = 'environment',
id = character(0),
min = numeric(0),
max = numeric(0),
value = NULL,
closed = logical(0)
)
)
#' Reporting progress (functional API)
#'
#' Reports progress to the user during long-running operations.
#'
#' This package exposes two distinct programming APIs for working with progress.
#' Using \code{withProgress} with \code{incProgress} or \code{setProgress}
#' provide a simple function-based interface, while the \code{\link{Progress}}
#' reference class provides an object-oriented API.
#'
#' Use \code{withProgress} to wrap the scope of your work; doing so will cause a
#' new progress panel to be created, and it will be displayed the first time
#' \code{incProgress} or \code{setProgress} are called. When \code{withProgress}
#' exits, the corresponding progress panel will be removed.
#'
#' The \code{incProgress} function increments the status bar by a specified
#' amount, whereas the \code{setProgress} function sets it to a specific value,
#' and can also set the text displayed.
#'
#' Generally, \code{withProgress}/\code{incProgress}/\code{setProgress} should
#' be sufficient; the exception is if the work to be done is asynchronous (this
#' is not common) or otherwise cannot be encapsulated by a single scope. In that
#' case, you can use the \code{Progress} reference class.
#'
#' @param session The Shiny session object, as provided by \code{shinyServer} to
#' the server function. The default is to automatically find the session by
#' using the current reactive domain.
#' @param expr The work to be done. This expression should contain calls to
#' \code{setProgress}.
#' @param min The value that represents the starting point of the progress bar.
#' Must be less tham \code{max}. Default is 0.
#' @param max The value that represents the end of the progress bar. Must be
#' greater than \code{min}. Default is 1.
#' @param amount For \code{incProgress}, the amount to increment the status bar.
#' Default is 0.1.
#' @param env The environment in which \code{expr} should be evaluated.
#' @param quoted Whether \code{expr} is a quoted expression (this is not
#' common).
#' @param message A single-element character vector; the message to be displayed
#' to the user, or \code{NULL} to hide the current message (if any).
#' @param detail A single-element character vector; the detail message to be
#' displayed to the user, or \code{NULL} to hide the current detail message
#' (if any). The detail message will be shown with a de-emphasized appearance
#' relative to \code{message}.
#' @param value Single-element numeric vector; the value at which to set the
#' progress bar, relative to \code{min} and \code{max}. \code{NULL} hides the
#' progress bar, if it is currently visible.
#'
#' @examples
#' \dontrun{
#' # server.R
#' shinyServer(function(input, output) {
#' output$plot <- renderPlot({
#' withProgress(message = 'Calculation in progress',
#' detail = 'This may take a while...', value = 0, {
#' for (i in 1:15) {
#' incProgress(1/15)
#' Sys.sleep(0.25)
#' }
#' })
#' plot(cars)
#' })
#' })
#' }
#' @seealso \code{\link{Progress}}
#' @rdname withProgress
#' @export
withProgress <- function(expr, min = 0, max = 1,
value = min + (max - min) * 0.1,
message = NULL, detail = NULL,
session = getDefaultReactiveDomain(),
env = parent.frame(), quoted = FALSE) {
if (!quoted)
expr <- substitute(expr)
if (!inherits(session, "ShinySession"))
stop("'session' is not a ShinySession object.")
p <- Progress$new(session, min = min, max = max)
session$progressStack$push(p)
on.exit({
session$progressStack$pop()
p$close()
})
p$set(value, message, detail)
eval(expr, env)
}
#' @rdname withProgress
#' @export
setProgress <- function(value = NULL, message = NULL, detail = NULL,
session = getDefaultReactiveDomain()) {
if (!inherits(session, "ShinySession"))
stop("'session' is not a ShinySession object.")
if (session$progressStack$size() == 0) {
warning('setProgress was called outside of withProgress; ignoring')
return()
}
session$progressStack$peek()$set(value, message, detail)
invisible()
}
#' @rdname withProgress
#' @export
incProgress <- function(amount = 0.1, message = NULL, detail = NULL,
session = getDefaultReactiveDomain()) {
if (!inherits(session, "ShinySession"))
stop("'session' is not a ShinySession object.")
if (session$progressStack$size() == 0) {
warning('incProgress was called outside of withProgress; ignoring')
return()
}
p <- session$progressStack$peek()
p$inc(amount, message, detail)
invisible()
}