By: Winston Chang
If your Shiny app contains computations that take a long time to complete, a progress bar can improve the user experience by communicating how far along the computation is, and how much is left. Progress bars were added in Shiny 0.10.2. In Shiny 0.14, they were changed to use the notifications system, although the previous styling can be used (see the Old style progress bars section below).
To see progress bars in action, see this app in the gallery.
Adding a progress indicator
The simplest way to add a progress indicator is to put
withProgress() inside of the
renderXx() function that contains the long-running computation. In this example, we’ll simulate a long computation by creating an empty data frame and then adding one row to it every 0.1 seconds. (Note that this example is written as a single-file app). To run this, you can copy and paste the code into the R console.)
This is what will happen:
withProgress() function is used to start a progress bar, and then the value is incremented with
incProgress(). By default, the range of values for the bar goes from 0 to 1, although this can be changed with the
There are two levels of messages:
message is presented in bold, and the
detail is presented in normal-weight text.
In the example above,
withProgress() is used inside of
renderPlot(), but it could also be used inside of any other
render function, like
renderTable(), or inside of a
It’s possible to nest calls to
withProgress; if you do this, the second-level progress bar will appear directly under the top-level progress bar, and the second-level text will appear under the top-level text. Further levels of nesting will have a similar pattern.
withProgress() function is a convenient interface around a
Progress object. In most cases, it’s simpler and easier to use
withProgress, but in some cases, you may need the greater level of control provided by the
Progress object. Before we delve into a more complex example, we’ll simply convert the example above from using
withProgress to using a
Notice that we need to explicitly create the
progress object and make sure that it closes properly, using
A more complex
In the example below, the
renderTable() calls out to another function,
compute_data(), to do the long-running computation. If we were to just update the progress indicator before and after
compute_data() were called, then it would only be updated at the beginning, when nothing has been done yet, and at the end, when the computation is completed. In some cases, the best we can do may be to set it to a starting value of, say, 0.3, and then move it to 1 at completion. This may be true if, for example, the function is in an external package.
However, if you do have control over the function doing the computation, you may want to modify it to accept either a
Progress object which it will update directly, or to accept a function which it calls each time it does some part of the computation.
In the example below, we’ll take the latter approach. The
compute_data() function accepts an optional
updateProgress function, which it calls periodically as it does the computation. The
updateProgress function is a closure that captures the
Progress object; each time it’s called, it updates the progress indicator.
Again, you can copy and paste this code in your R console to see it in action:
It’s possible to use other constructions for the
updateProgress function that have different behavior. In the example above, each time
updateProgress() is called, the progress bar moves 1/5 of the remaining distance. This tells the user that something is happening, and it’s simple because you don’t need to know ahead of time how many times it’s goingto run. However, it’s not the most accurate representation of progress, since it approaches the end asymptotically, whereas a linear approach would be more accurate.
One alternative is to have the external function call
updateProgress() with a specific value. If, for example, the external function knows that it will iterate over the loop 100 times, it could call
value=0.02, and so on.
Another alternative is to construct a different
updateProgress callback, one which increments by a fixed amount each time. To do this, before you call
compute_data(), you must know how many times it will call
updateProgress() in the loop. Let’s assume that it will be called 20 times. Then
updateProgress could be defined like so:
Each time this version of
updateProgress() is called, it moves the bar 1/20th of the total distance.
Old style progress bars
In Shiny 0.14, the progress bars switched to Shiny’s notification API. However, if you created application that used the old progress bars and had custom styling with CSS, you will need to use the old style output to keep the custom styling. This can be done with by calling
Progress$new() with the argument
style="old". For an example, see the example app.
You can add progress indicators to your app, using the simpler
withProgress() interface, or the
Progress object if you need more control. These progress indicators can provide feedback to the user that will make their experience more satisfying.
If you have questions about this article or would like to discuss ideas presented here, please post on RStudio Community. Our developers monitor these forums and answer questions periodically. See help for more help with all things Shiny.