mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 07:58:11 -05:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69c32d4d90 | ||
|
|
36ffebd975 | ||
|
|
deb56539fb | ||
|
|
af8d099b9f | ||
|
|
eed869d321 | ||
|
|
f8f2acf6c3 | ||
|
|
7be9f74827 | ||
|
|
ed77982330 | ||
|
|
e1b47eca90 | ||
|
|
bfa0b2d2bc | ||
|
|
d67783edbd | ||
|
|
77712b6664 | ||
|
|
1633e7faa6 | ||
|
|
2dc5ee5862 | ||
|
|
bbaea23eea | ||
|
|
d112ac7eef | ||
|
|
cf21e987f2 | ||
|
|
dae11765bc | ||
|
|
df30a3c7f4 | ||
|
|
aaa4600597 | ||
|
|
ba1730d26b | ||
|
|
d1b5c812f7 | ||
|
|
5bfe6d1c84 | ||
|
|
9804a794fd | ||
|
|
0344645208 | ||
|
|
f56ad6e787 | ||
|
|
7492db592b | ||
|
|
2e80ecf8a7 | ||
|
|
6993551a44 | ||
|
|
9bff15adfe | ||
|
|
0c24da2358 | ||
|
|
4ee4adb43d | ||
|
|
e33b028348 | ||
|
|
384d9c1841 | ||
|
|
f78fcd6b5f | ||
|
|
711a72989b | ||
|
|
d62a2fc1d5 | ||
|
|
f33f712a3a | ||
|
|
3315b3310b | ||
|
|
c7134b16ed | ||
|
|
f36f710661 | ||
|
|
00ab8681c7 | ||
|
|
4137bbac94 | ||
|
|
750b2ad599 | ||
|
|
511c833fbb | ||
|
|
29063a0c07 | ||
|
|
67909b3557 | ||
|
|
102c12d36c | ||
|
|
dc51651665 | ||
|
|
8b563d6d5f | ||
|
|
eb8b88027e | ||
|
|
a5b7f307ed | ||
|
|
45fca425aa | ||
|
|
a0bd9b5fd7 | ||
|
|
c12e24e3e3 | ||
|
|
d147c5a153 | ||
|
|
7a833456d9 | ||
|
|
306f33dfc4 | ||
|
|
a2745a4060 | ||
|
|
46b68c7b2a | ||
|
|
4264760113 | ||
|
|
42dedbbd9a | ||
|
|
ea99bfdb16 | ||
|
|
2ccb934338 | ||
|
|
367027cfbc | ||
|
|
c4ebd3b6d5 | ||
|
|
5f8cd82a09 | ||
|
|
0ef15fa662 | ||
|
|
c05452af91 | ||
|
|
4c8bafcf9a | ||
|
|
034f30a49a | ||
|
|
0f13075e17 | ||
|
|
ad274a5981 | ||
|
|
fdbcbaec8a | ||
|
|
9c09072ee6 | ||
|
|
0a4ca56da9 | ||
|
|
2b494398f2 | ||
|
|
95585c2264 | ||
|
|
92f9f0da9e | ||
|
|
fe943b5e95 | ||
|
|
3479a4661a | ||
|
|
7ba438cf7c | ||
|
|
c761e9fba0 | ||
|
|
deae31ea4a | ||
|
|
547355a163 | ||
|
|
9be4cb132c | ||
|
|
3e25c9f3f4 | ||
|
|
220c7e9139 | ||
|
|
79a085a9be | ||
|
|
b505c5a9d3 | ||
|
|
03ba660ea1 | ||
|
|
5aeb361f6d | ||
|
|
0e519a4e97 | ||
|
|
4feee00d34 | ||
|
|
ef5e4cdc0a | ||
|
|
67c599f50b | ||
|
|
5af9b61357 | ||
|
|
1d6771b4ed | ||
|
|
c55dc0a58e | ||
|
|
c525d55db8 | ||
|
|
408f66ef80 | ||
|
|
7f73a047a4 | ||
|
|
015bc98d60 | ||
|
|
5cd9ba609a | ||
|
|
c8ed6544db | ||
|
|
1162113d3b | ||
|
|
1612503e7b | ||
|
|
34ba85df3b | ||
|
|
8206e7d2a2 | ||
|
|
3e29672c70 | ||
|
|
f67aaafe4f | ||
|
|
ed704afc07 | ||
|
|
bbbfacb4b2 | ||
|
|
cf16d2e52d | ||
|
|
6268e6e1c9 | ||
|
|
99b8e5b303 | ||
|
|
73446af330 | ||
|
|
a0b917a207 | ||
|
|
53ec7edd06 | ||
|
|
ff804c0ff8 | ||
|
|
9d69ff01b3 | ||
|
|
61831f530f | ||
|
|
6065db1d24 | ||
|
|
270b8415e8 | ||
|
|
1987331a70 | ||
|
|
ab85216b96 | ||
|
|
b5cb78c77e |
11
DESCRIPTION
11
DESCRIPTION
@@ -1,7 +1,7 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.0.0
|
||||
Version: 1.0.3
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
|
||||
@@ -56,7 +56,7 @@ Authors@R: c(
|
||||
)
|
||||
Description: Makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive pre-built widgets make it possible to build
|
||||
outputs and extensive prebuilt widgets make it possible to build
|
||||
beautiful, responsive, and powerful applications with minimal effort.
|
||||
License: GPL-3 | file LICENSE
|
||||
Depends:
|
||||
@@ -98,6 +98,9 @@ Collate:
|
||||
'diagnose.R'
|
||||
'fileupload.R'
|
||||
'graph.R'
|
||||
'reactives.R'
|
||||
'reactive-domains.R'
|
||||
'history.R'
|
||||
'hooks.R'
|
||||
'html-deps.R'
|
||||
'htmltools.R'
|
||||
@@ -129,8 +132,6 @@ Collate:
|
||||
'priorityqueue.R'
|
||||
'progress.R'
|
||||
'react.R'
|
||||
'reactive-domains.R'
|
||||
'reactives.R'
|
||||
'render-plot.R'
|
||||
'render-table.R'
|
||||
'run-url.R'
|
||||
@@ -146,4 +147,4 @@ Collate:
|
||||
'test-export.R'
|
||||
'timer.R'
|
||||
'update-input.R'
|
||||
RoxygenNote: 5.0.1
|
||||
RoxygenNote: 6.0.1
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -12,7 +12,7 @@ these components are included below):
|
||||
- Respond.js, https://github.com/scottjehl/Respond
|
||||
- bootstrap-datepicker, https://github.com/eternicode/bootstrap-datepicker
|
||||
- Font Awesome, https://github.com/FortAwesome/Font-Awesome
|
||||
- selectize.js, https://github.com/brianreavis/selectize.js
|
||||
- selectize.js, https://github.com/selectize/selectize.js
|
||||
- es5-shim, https://github.com/es-shims/es5-shim
|
||||
- ion.rangeSlider, https://github.com/IonDen/ion.rangeSlider
|
||||
- strftime for Javascript, https://github.com/samsonjs/strftime
|
||||
|
||||
@@ -22,6 +22,8 @@ S3method(as.shiny.appobj,list)
|
||||
S3method(as.shiny.appobj,shiny.appobj)
|
||||
S3method(as.tags,shiny.appobj)
|
||||
S3method(as.tags,shiny.render.function)
|
||||
S3method(format,reactiveExpr)
|
||||
S3method(format,reactiveVal)
|
||||
S3method(names,reactivevalues)
|
||||
S3method(print,reactive)
|
||||
S3method(print,shiny.appobj)
|
||||
@@ -84,9 +86,12 @@ export(flowLayout)
|
||||
export(fluidPage)
|
||||
export(fluidRow)
|
||||
export(formatStackTrace)
|
||||
export(freezeReactiveVal)
|
||||
export(freezeReactiveValue)
|
||||
export(getDefaultReactiveDomain)
|
||||
export(getQueryString)
|
||||
export(getShinyOption)
|
||||
export(getUrlHash)
|
||||
export(h1)
|
||||
export(h2)
|
||||
export(h3)
|
||||
@@ -168,6 +173,7 @@ export(reactiveTable)
|
||||
export(reactiveText)
|
||||
export(reactiveTimer)
|
||||
export(reactiveUI)
|
||||
export(reactiveVal)
|
||||
export(reactiveValues)
|
||||
export(reactiveValuesToList)
|
||||
export(registerInputHandler)
|
||||
@@ -211,6 +217,7 @@ export(sidebarLayout)
|
||||
export(sidebarPanel)
|
||||
export(singleton)
|
||||
export(sliderInput)
|
||||
export(snapshotExclude)
|
||||
export(span)
|
||||
export(splitLayout)
|
||||
export(stopApp)
|
||||
|
||||
107
NEWS.md
107
NEWS.md
@@ -1,3 +1,108 @@
|
||||
shiny 1.0.3
|
||||
================
|
||||
|
||||
This is a hotfix release of Shiny. With previous versions of Shiny, when running an application on the newly-released version of R, 3.4.0, it would print a message: `Warning in body(fun) : argument is not a function`. This has no effect on the application, but because the message could be alarming to users, we are releasing a new version of Shiny that fixes this issue.
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1672](https://github.com/rstudio/shiny/issues/1672): When an error occurred while uploading a file, the progress bar did not change colors. ([#1673](https://github.com/rstudio/shiny/pull/1673))
|
||||
|
||||
* Fixed [#1676](https://github.com/rstudio/shiny/issues/1676): On R 3.4.0, running a Shiny application gave a warning: `Warning in body(fun) : argument is not a function`. ([#1677](https://github.com/rstudio/shiny/pull/1677))
|
||||
|
||||
|
||||
shiny 1.0.2
|
||||
================
|
||||
|
||||
This is a hotfix release of Shiny. The primary reason for this release is because the web host for MathJax JavaScript library is scheduled to be shut down in the next few weeks. After it is shut down, Shiny applications that use MathJax will no longer be able to load the MathJax library if they are run with Shiny 1.0.1 and below. (If you don't know whether your application uses MathJax, it probably does not.) For more information about why the MathJax CDN is shutting down, see https://www.mathjax.org/cdn-shutting-down/.
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Added a `shiny:sessioninitialized` Javascript event, which is fired at the end of the initialize method of the Session object. This allows us to listen for this event when we want to get the value of things like `Shiny.user`. ([#1568](https://github.com/rstudio/shiny/pull/1568))
|
||||
|
||||
* Fixed [#1649](https://github.com/rstudio/shiny/issues/1649): allow the `choices` argument in `checkboxGroupInput()` to be `NULL` (or `c()`) to keep backward compatibility with Shiny < 1.0.1. This will result in the same thing as providing `choices = character(0)`. ([#1652](https://github.com/rstudio/shiny/pull/1652))
|
||||
|
||||
* The official URL for accessing MathJax libraries over CDN has been deprecated and will be removed soon. We have switched to a new rstudio.com URL that we will support going forward. ([#1664](https://github.com/rstudio/shiny/pull/1664))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1653](https://github.com/rstudio/shiny/issues/1653): wrong code example in documentation. ([#1658](https://github.com/rstudio/shiny/pull/1658))
|
||||
|
||||
|
||||
shiny 1.0.1
|
||||
================
|
||||
|
||||
This is a maintenance release of Shiny, mostly aimed at fixing bugs and introducing minor features. The most notable additions in this version of Shiny are the introduction of the `reactiveVal()` function (it's like `reactiveValues()`, but it only stores a single value), and that the choices of `radioButtons()` and `checkboxGroupInput()` can now contain HTML content instead of just plain text.
|
||||
|
||||
## Full changelog
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The functions `radioButtons()`, `checkboxGroupInput()` and `selectInput()` (and the corresponding `updateXXX()` functions) no longer accept a `selected` argument whose value is the name of a choice, instead of the value of the choice. This feature had been deprecated since Shiny 0.10 (it printed a warning message, but still tried to match the name to the right choice) and it's now completely unsupported.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `reactiveVal` function, for storing a single value which can be (reactively) read and written. Similar to `reactiveValues`, except that `reactiveVal` just lets you store a single value instead of storing multiple values by name. ([#1614](https://github.com/rstudio/shiny/pull/1614))
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Fixed [#1637](https://github.com/rstudio/shiny/issues/1637): Outputs stay faded on MS Edge. ([#1640](https://github.com/rstudio/shiny/pull/1640))
|
||||
|
||||
* Addressed [#1348](https://github.com/rstudio/shiny/issues/1348) and [#1437](https://github.com/rstudio/shiny/issues/1437) by adding two new arguments to `radioButtons()` and `checkboxGroupInput()`: `choiceNames` (list or vector) and `choiceValues` (list or vector). These can be passed in as an alternative to `choices`, with the added benefit that the elements in `choiceNames` can be arbitrary UI (i.e. anything created by `HTML()` and the `tags()` functions, like icons and images). While the underlying values for each choice (passed in through `choiceValues`) must still be simple text, their visual representation on the app (what the user actually clicks to select a different option) can be any valid HTML element. See `?radioButtons` for a small example. ([#1521](https://github.com/rstudio/shiny/pull/1521))
|
||||
|
||||
* Updated `tools/README.md` with more detailed instructions. ([#1616](https://github.com/rstudio/shiny/pull/1616))
|
||||
|
||||
* Fixed [#1565](https://github.com/rstudio/shiny/issues/1565), which meant that resources with spaces in their names return HTTP 404. ([#1566](https://github.com/rstudio/shiny/pull/1566))
|
||||
|
||||
* Exported `session$user` (if it exists) to the client-side; it's accessible in the Shiny object: `Shiny.user`. ([#1563](https://github.com/rstudio/shiny/pull/1563))
|
||||
|
||||
* Added support for HTML5's `pushState` which allows for pseudo-navigation
|
||||
in shiny apps. For more info, see the documentation (`?updateQueryString` and `?getQueryString`). ([#1447](https://github.com/rstudio/shiny/pull/1447))
|
||||
|
||||
* Fixed [#1121](https://github.com/rstudio/shiny/issues/1121): plot interactions with ggplot2 now support `coord_fixed()`. ([#1525](https://github.com/rstudio/shiny/pull/1525))
|
||||
|
||||
* Added `snapshotExclude` function, which marks an output so that it is not recorded in a test snapshot. ([#1559](https://github.com/rstudio/shiny/pull/1559))
|
||||
|
||||
* Added `shiny:filedownload` JavaScript event, which is triggered when a `downloadButton` or `downloadLink` is clicked. Also, the values of `downloadHandler`s are not recorded in test snapshots, because the values change every time the application is run. ([#1559](https://github.com/rstudio/shiny/pull/1559))
|
||||
|
||||
* Added support for plot interactions with ggplot2 > 2.2.1. ([#1578](https://github.com/rstudio/shiny/pull/1578))
|
||||
|
||||
* Fixed [#1577](https://github.com/rstudio/shiny/issues/1577): Improved `escapeHTML` (util.js) in terms of the order dependency of replacing, XSS risk attack and performance. ([#1579](https://github.com/rstudio/shiny/pull/1579))
|
||||
|
||||
* The `shiny:inputchanged` JavaScript event now includes two new fields, `binding` and `el`, which contain the input binding and DOM element, respectively. Additionally, `Shiny.onInputChange()` now accepts an optional argument, `opts`, which can contain the same fields. ([#1596](https://github.com/rstudio/shiny/pull/1596))
|
||||
|
||||
* The `NS()` function now returns a vectorized function. ([#1613](https://github.com/rstudio/shiny/pull/1613))
|
||||
|
||||
* Fixed [#1617](https://github.com/rstudio/shiny/issues/1617): `fileInput` can have customized text for the button and the placeholder. ([#1619](https://github.com/rstudio/shiny/pull/1619))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fixed [#1511](https://github.com/rstudio/shiny/issues/1511): `fileInput`s did not trigger the `shiny:inputchanged` event on the client. Also removed `shiny:fileuploaded` JavaScript event, because it is no longer needed after this fix. ([#1541](https://github.com/rstudio/shiny/pull/1541), [#1570](https://github.com/rstudio/shiny/pull/1570))
|
||||
|
||||
* Fixed [#1472](https://github.com/rstudio/shiny/issues/1472): With a Progress object, calling `set(value=NULL)` made the progress bar go to 100%. Now it does not change the value of the progress bar. The documentation also incorrectly said that setting the `value` to `NULL` would hide the progress bar. ([#1547](https://github.com/rstudio/shiny/pull/1547))
|
||||
|
||||
* Fixed [#162](https://github.com/rstudio/shiny/issues/162): When a dynamically-generated input changed to a different `inputType`, it might be incorrectly deduplicated. ([#1594](https://github.com/rstudio/shiny/pull/1594))
|
||||
|
||||
* Removed redundant call to `inputs.setInput`. ([#1595](https://github.com/rstudio/shiny/pull/1595))
|
||||
|
||||
* Fixed bug where `dateRangeInput` did not respect `weekstart` argument. ([#1592](https://github.com/rstudio/shiny/pull/1592))
|
||||
|
||||
* Fixed [#1598](https://github.com/rstudio/shiny/issues/1598): `setBookmarkExclude()` did not work properly inside of modules. ([#1599](https://github.com/rstudio/shiny/pull/1599))
|
||||
|
||||
* Fixed [#1605](https://github.com/rstudio/shiny/issues/1605): sliders did not move when clicked on the bar area. ([#1610](https://github.com/rstudio/shiny/pull/1610))
|
||||
|
||||
* Fixed [#1621](https://github.com/rstudio/shiny/issues/1621): if a `reactiveTimer`'s session was closed before the first time that the `reactiveTimer` fired, then the `reactiveTimer` would not get cleared and would keep firing indefinitely. ([#1623](https://github.com/rstudio/shiny/pull/1623))
|
||||
|
||||
* Fixed [#1634](https://github.com/rstudio/shiny/issues/1634): If brushing on a plot causes the plot to redraw, then the redraw could in turn trigger the plot to redraw again and again. This was due to spurious changes in values of floating point numbers. ([#1641](https://github.com/rstudio/shiny/pull/1641))
|
||||
|
||||
### Library updates
|
||||
|
||||
* Closed [#1500](https://github.com/rstudio/shiny/issues/1500): Updated ion.rangeSlider to 2.1.6. ([#1540](https://github.com/rstudio/shiny/pull/1540))
|
||||
|
||||
|
||||
shiny 1.0.0
|
||||
===========
|
||||
|
||||
@@ -7,7 +112,7 @@ Here are some highlights from this release. For more details, see the full chang
|
||||
|
||||
## Support for testing Shiny applications
|
||||
|
||||
Shiny now supports automated testing of applications, with the [shinytest](https://github.com/MangoTheCat/shinytest) package. Shinytest has not yet been released on CRAN, but will be soon. ([#18](https://github.com/rstudio/shiny/issues/18), [#1464](https://github.com/rstudio/shiny/pull/1464))
|
||||
Shiny now supports automated testing of applications, with the [shinytest](https://github.com/rstudio/shinytest) package. Shinytest has not yet been released on CRAN, but will be soon. ([#18](https://github.com/rstudio/shiny/issues/18), [#1464](https://github.com/rstudio/shiny/pull/1464))
|
||||
|
||||
## Debounce/throttle reactives
|
||||
|
||||
|
||||
@@ -493,12 +493,90 @@ restoreInput <- function(id, default) {
|
||||
#' It typically is called from an observer. Note that this will not work in
|
||||
#' Internet Explorer 9 and below.
|
||||
#'
|
||||
#' For \code{mode = "push"}, only three updates are currently allowed:
|
||||
#' \enumerate{
|
||||
#' \item the query string (format: \code{?param1=val1¶m2=val2})
|
||||
#' \item the hash (format: \code{#hash})
|
||||
#' \item both the query string and the hash
|
||||
#' (format: \code{?param1=val1¶m2=val2#hash})
|
||||
#' }
|
||||
#'
|
||||
#' In other words, if \code{mode = "push"}, the \code{queryString} must start
|
||||
#' with either \code{?} or with \code{#}.
|
||||
#'
|
||||
#' A technical curiosity: under the hood, this function is calling the HTML5
|
||||
#' history API (which is where the names for the \code{mode} argument come from).
|
||||
#' When \code{mode = "replace"}, the function called is
|
||||
#' \code{window.history.replaceState(null, null, queryString)}.
|
||||
#' When \code{mode = "push"}, the function called is
|
||||
#' \code{window.history.pushState(null, null, queryString)}.
|
||||
#'
|
||||
#' @param queryString The new query string to show in the location bar.
|
||||
#' @param mode When the query string is updated, should the the current history
|
||||
#' entry be replaced (default), or should a new history entry be pushed onto
|
||||
#' the history stack? The former should only be used in a live bookmarking
|
||||
#' context. The latter is useful if you want to navigate between states using
|
||||
#' the browser's back and forward buttons. See Examples.
|
||||
#' @param session A Shiny session object.
|
||||
#' @seealso \code{\link{enableBookmarking}} for examples.
|
||||
#' @seealso \code{\link{enableBookmarking}}, \code{\link{getQueryString}}
|
||||
#' @examples
|
||||
#' ## Only run these examples in interactive sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ## App 1: Doing "live" bookmarking
|
||||
#' ## Update the browser's location bar every time an input changes.
|
||||
#' ## This should not be used with enableBookmarking("server"),
|
||||
#' ## because that would create a new saved state on disk every time
|
||||
#' ## the user changes an input.
|
||||
#' enableBookmarking("url")
|
||||
#' shinyApp(
|
||||
#' ui = function(req) {
|
||||
#' fluidPage(
|
||||
#' textInput("txt", "Text"),
|
||||
#' checkboxInput("chk", "Checkbox")
|
||||
#' )
|
||||
#' },
|
||||
#' server = function(input, output, session) {
|
||||
#' observe({
|
||||
#' # Trigger this observer every time an input changes
|
||||
#' reactiveValuesToList(input)
|
||||
#' session$doBookmark()
|
||||
#' })
|
||||
#' onBookmarked(function(url) {
|
||||
#' updateQueryString(url)
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' ## App 2: Printing the value of the query string
|
||||
#' ## (Use the back and forward buttons to see how the browser
|
||||
#' ## keeps a record of each state)
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' textInput("txt", "Enter new query string"),
|
||||
#' helpText("Format: ?param1=val1¶m2=val2"),
|
||||
#' actionButton("go", "Update"),
|
||||
#' hr(),
|
||||
#' verbatimTextOutput("query")
|
||||
#' ),
|
||||
#' server = function(input, output, session) {
|
||||
#' observeEvent(input$go, {
|
||||
#' updateQueryString(input$txt, mode = "push")
|
||||
#' })
|
||||
#' output$query <- renderText({
|
||||
#' query <- getQueryString()
|
||||
#' queryText <- paste(names(query), query,
|
||||
#' sep = "=", collapse=", ")
|
||||
#' paste("Your query string is:\n", queryText)
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#' @export
|
||||
updateQueryString <- function(queryString, session = getDefaultReactiveDomain()) {
|
||||
session$updateQueryString(queryString)
|
||||
updateQueryString <- function(queryString, mode = c("replace", "push"),
|
||||
session = getDefaultReactiveDomain()) {
|
||||
mode <- match.arg(mode)
|
||||
session$updateQueryString(queryString, mode)
|
||||
}
|
||||
|
||||
#' Create a button for bookmarking/sharing
|
||||
|
||||
@@ -1453,7 +1453,7 @@ uiOutput <- htmlOutput
|
||||
#' }
|
||||
#'
|
||||
#' @aliases downloadLink
|
||||
#' @seealso downloadHandler
|
||||
#' @seealso \code{\link{downloadHandler}}
|
||||
#' @export
|
||||
downloadButton <- function(outputId,
|
||||
label="Download",
|
||||
|
||||
95
R/history.R
Normal file
95
R/history.R
Normal file
@@ -0,0 +1,95 @@
|
||||
|
||||
#' @include reactive-domains.R
|
||||
NULL
|
||||
|
||||
#' @include reactives.R
|
||||
NULL
|
||||
|
||||
#' Get the query string / hash component from the URL
|
||||
#'
|
||||
#' Two user friendly wrappers for getting the query string and the hash
|
||||
#' component from the app's URL.
|
||||
#'
|
||||
#' These can be particularly useful if you want to display different content
|
||||
#' depending on the values in the query string / hash (e.g. instead of basing
|
||||
#' the conditional on an input or a calculated reactive, you can base it on the
|
||||
#' query string). However, note that, if you're changing the query string / hash
|
||||
#' programatically from within the server code, you must use
|
||||
#' \code{updateQueryString(_yourNewQueryString_, mode = "push")}. The default
|
||||
#' \code{mode} for \code{updateQueryString} is \code{"replace"}, which doesn't
|
||||
#' raise any events, so any observers or reactives that depend on it will
|
||||
#' \emph{not} get triggered. However, if you're changing the query string / hash
|
||||
#' directly by typing directly in the browser and hitting enter, you don't have
|
||||
#' to worry about this.
|
||||
#'
|
||||
#' @param session A Shiny session object.
|
||||
#'
|
||||
#' @return For \code{getQueryString}, a named list. For example, the query
|
||||
#' string \code{?param1=value1¶m2=value2} becomes \code{list(param1 =
|
||||
#' value1, param2 = value2)}. For \code{getUrlHash}, a character vector with
|
||||
#' the hash (including the leading \code{#} symbol).
|
||||
#'
|
||||
#' @seealso \code{\link{updateQueryString}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ## App 1: getQueryString
|
||||
#' ## Printing the value of the query string
|
||||
#' ## (Use the back and forward buttons to see how the browser
|
||||
#' ## keeps a record of each state)
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' textInput("txt", "Enter new query string"),
|
||||
#' helpText("Format: ?param1=val1¶m2=val2"),
|
||||
#' actionButton("go", "Update"),
|
||||
#' hr(),
|
||||
#' verbatimTextOutput("query")
|
||||
#' ),
|
||||
#' server = function(input, output, session) {
|
||||
#' observeEvent(input$go, {
|
||||
#' updateQueryString(input$txt, mode = "push")
|
||||
#' })
|
||||
#' output$query <- renderText({
|
||||
#' query <- getQueryString()
|
||||
#' queryText <- paste(names(query), query,
|
||||
#' sep = "=", collapse=", ")
|
||||
#' paste("Your query string is:\n", queryText)
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' ## App 2: getUrlHash
|
||||
#' ## Printing the value of the URL hash
|
||||
#' ## (Use the back and forward buttons to see how the browser
|
||||
#' ## keeps a record of each state)
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' textInput("txt", "Enter new hash"),
|
||||
#' helpText("Format: #hash"),
|
||||
#' actionButton("go", "Update"),
|
||||
#' hr(),
|
||||
#' verbatimTextOutput("hash")
|
||||
#' ),
|
||||
#' server = function(input, output, session) {
|
||||
#' observeEvent(input$go, {
|
||||
#' updateQueryString(input$txt, mode = "push")
|
||||
#' })
|
||||
#' output$hash <- renderText({
|
||||
#' hash <- getUrlHash()
|
||||
#' paste("Your hash is:\n", hash)
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#' @export
|
||||
getQueryString <- function(session = getDefaultReactiveDomain()) {
|
||||
parseQueryString(session$clientData$url_search)
|
||||
}
|
||||
|
||||
#' @rdname getQueryString
|
||||
#' @export
|
||||
getUrlHash <- function(session = getDefaultReactiveDomain()) {
|
||||
session$clientData$url_hash
|
||||
}
|
||||
@@ -6,9 +6,21 @@
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to show checkboxes for. If elements of the list
|
||||
#' are named then that name rather than the value is displayed to the user.
|
||||
#' are named then that name rather than the value is displayed to the user. If
|
||||
#' this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
#' must not be provided, and vice-versa.
|
||||
#' @param selected The values that should be initially selected, if any.
|
||||
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
|
||||
#' @param choiceNames,choiceValues List of names and values, respectively,
|
||||
#' that are displayed to the user in the app and correspond to the each
|
||||
#' choice (for this reason, \code{choiceNames} and \code{choiceValues}
|
||||
#' must have the same length). If either of these arguments is
|
||||
#' provided, then the other \emph{must} be provided and \code{choices}
|
||||
#' \emph{must not} be provided. The advantage of using both of these over
|
||||
#' a named list for \code{choices} is that \code{choiceNames} allows any
|
||||
#' type of UI object to be passed through (tag objects, icons, HTML code,
|
||||
#' ...), instead of just simple text. See Examples.
|
||||
#'
|
||||
#' @return A list of HTML elements that can be added to a UI definition.
|
||||
#'
|
||||
#' @family input elements
|
||||
@@ -26,26 +38,52 @@
|
||||
#' tableOutput("data")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' server <- function(input, output, session) {
|
||||
#' output$data <- renderTable({
|
||||
#' mtcars[, c("mpg", input$variable), drop = FALSE]
|
||||
#' }, rownames = TRUE)
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' checkboxGroupInput("icons", "Choose icons:",
|
||||
#' choiceNames =
|
||||
#' list(icon("calendar"), icon("bed"),
|
||||
#' icon("cog"), icon("bug")),
|
||||
#' choiceValues =
|
||||
#' list("calendar", "bed", "cog", "bug")
|
||||
#' ),
|
||||
#' textOutput("txt")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output, session) {
|
||||
#' output$txt <- renderText({
|
||||
#' icons <- paste(input$icons, collapse = ", ")
|
||||
#' paste("You chose", icons)
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
checkboxGroupInput <- function(inputId, label, choices, selected = NULL,
|
||||
inline = FALSE, width = NULL) {
|
||||
checkboxGroupInput <- function(inputId, label, choices = NULL, selected = NULL,
|
||||
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) {
|
||||
|
||||
# keep backward compatibility with Shiny < 1.0.1 (see #1649)
|
||||
if (is.null(choices) && is.null(choiceNames) && is.null(choiceValues)) {
|
||||
choices <- character(0)
|
||||
}
|
||||
|
||||
args <- normalizeChoicesArgs(choices, choiceNames, choiceValues)
|
||||
|
||||
selected <- restoreInput(id = inputId, default = selected)
|
||||
|
||||
# resolve names
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
# default value if it's not specified
|
||||
if (!is.null(selected)) selected <- as.character(selected)
|
||||
|
||||
options <- generateOptions(inputId, choices, selected, inline)
|
||||
options <- generateOptions(inputId, selected, inline,
|
||||
'checkbox', args$choiceNames, args$choiceValues)
|
||||
|
||||
divClass <- "form-group shiny-input-checkboxgroup shiny-input-container"
|
||||
if (inline)
|
||||
|
||||
@@ -98,7 +98,7 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
|
||||
class = "input-sm form-control",
|
||||
type = "text",
|
||||
`data-date-language` = language,
|
||||
`data-date-weekstart` = weekstart,
|
||||
`data-date-week-start` = weekstart,
|
||||
`data-date-format` = format,
|
||||
`data-date-start-view` = startview,
|
||||
`data-min-date` = min,
|
||||
@@ -110,7 +110,7 @@ dateRangeInput <- function(inputId, label, start = NULL, end = NULL,
|
||||
class = "input-sm form-control",
|
||||
type = "text",
|
||||
`data-date-language` = language,
|
||||
`data-date-weekstart` = weekstart,
|
||||
`data-date-week-start` = weekstart,
|
||||
`data-date-format` = format,
|
||||
`data-date-start-view` = startview,
|
||||
`data-min-date` = min,
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
#' Internet Explorer 9 and earlier.}
|
||||
#' @param accept A character vector of MIME types; gives the browser a hint of
|
||||
#' what kind of files the server is expecting.
|
||||
#' @param buttonLabel The label used on the button. Can be text or an HTML tag
|
||||
#' object.
|
||||
#' @param placeholder The text to show before a file has been uploaded.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
@@ -70,7 +73,7 @@
|
||||
#' }
|
||||
#' @export
|
||||
fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
|
||||
width = NULL) {
|
||||
width = NULL, buttonLabel = "Browse...", placeholder = "No file selected") {
|
||||
|
||||
restoredValue <- restoreInput(id = inputId, default = NULL)
|
||||
|
||||
@@ -105,12 +108,12 @@ fileInput <- function(inputId, label, multiple = FALSE, accept = NULL,
|
||||
div(class = "input-group",
|
||||
tags$label(class = "input-group-btn",
|
||||
span(class = "btn btn-default btn-file",
|
||||
"Browse...",
|
||||
buttonLabel,
|
||||
inputTag
|
||||
)
|
||||
),
|
||||
tags$input(type = "text", class = "form-control",
|
||||
placeholder = "No file selected", readonly = "readonly"
|
||||
placeholder = placeholder, readonly = "readonly"
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
@@ -11,11 +11,22 @@
|
||||
#'
|
||||
#' @inheritParams textInput
|
||||
#' @param choices List of values to select from (if elements of the list are
|
||||
#' named then that name rather than the value is displayed to the user)
|
||||
#' named then that name rather than the value is displayed to the user). If
|
||||
#' this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
#' must not be provided, and vice-versa.
|
||||
#' @param selected The initially selected value (if not specified then
|
||||
#' defaults to the first value)
|
||||
#' defaults to the first value)
|
||||
#' @param inline If \code{TRUE}, render the choices inline (i.e. horizontally)
|
||||
#' @return A set of radio buttons that can be added to a UI definition.
|
||||
#' @param choiceNames,choiceValues List of names and values, respectively,
|
||||
#' that are displayed to the user in the app and correspond to the each
|
||||
#' choice (for this reason, \code{choiceNames} and \code{choiceValues}
|
||||
#' must have the same length). If either of these arguments is
|
||||
#' provided, then the other \emph{must} be provided and \code{choices}
|
||||
#' \emph{must not} be provided. The advantage of using both of these over
|
||||
#' a named list for \code{choices} is that \code{choiceNames} allows any
|
||||
#' type of UI object to be passed through (tag objects, icons, HTML code,
|
||||
#' ...), instead of just simple text. See Examples.
|
||||
#'
|
||||
#' @family input elements
|
||||
#' @seealso \code{\link{updateRadioButtons}}
|
||||
@@ -47,27 +58,46 @@
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' radioButtons("rb", "Choose one:",
|
||||
#' choiceNames = list(
|
||||
#' icon("calendar"),
|
||||
#' HTML("<p style='color:red;'>Red Text</p>"),
|
||||
#' "Normal text"
|
||||
#' ),
|
||||
#' choiceValues = list(
|
||||
#' "icon", "html", "text"
|
||||
#' )),
|
||||
#' textOutput("txt")
|
||||
#' )
|
||||
#'
|
||||
#' server <- function(input, output) {
|
||||
#' output$txt <- renderText({
|
||||
#' paste("You chose", input$rb)
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#' }
|
||||
#' @export
|
||||
radioButtons <- function(inputId, label, choices, selected = NULL,
|
||||
inline = FALSE, width = NULL) {
|
||||
radioButtons <- function(inputId, label, choices = NULL, selected = NULL,
|
||||
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL) {
|
||||
|
||||
# resolve names
|
||||
choices <- choicesWithNames(choices)
|
||||
args <- normalizeChoicesArgs(choices, choiceNames, choiceValues)
|
||||
|
||||
selected <- restoreInput(id = inputId, default = selected)
|
||||
|
||||
# default value if it's not specified
|
||||
selected <- if (is.null(selected)) choices[[1]] else {
|
||||
validateSelected(selected, choices, inputId)
|
||||
}
|
||||
selected <- if (is.null(selected)) args$choiceValues[[1]] else as.character(selected)
|
||||
|
||||
if (length(selected) > 1) stop("The 'selected' argument must be of length 1")
|
||||
|
||||
options <- generateOptions(inputId, choices, selected, inline, type = 'radio')
|
||||
options <- generateOptions(inputId, selected, inline,
|
||||
'radio', args$choiceNames, args$choiceValues)
|
||||
|
||||
divClass <- "form-group shiny-input-radiogroup shiny-input-container"
|
||||
if (inline)
|
||||
divClass <- paste(divClass, "shiny-input-container-inline")
|
||||
if (inline) divClass <- paste(divClass, "shiny-input-container-inline")
|
||||
|
||||
tags$div(id = inputId,
|
||||
style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#'
|
||||
#' By default, \code{selectInput()} and \code{selectizeInput()} use the
|
||||
#' JavaScript library \pkg{selectize.js}
|
||||
#' (\url{https://github.com/brianreavis/selectize.js}) to instead of the basic
|
||||
#' (\url{https://github.com/selectize/selectize.js}) to instead of the basic
|
||||
#' select input element. To use the standard HTML select input element, use
|
||||
#' \code{selectInput()} with \code{selectize=FALSE}.
|
||||
#'
|
||||
@@ -74,8 +74,8 @@
|
||||
#' }
|
||||
#' @export
|
||||
selectInput <- function(inputId, label, choices, selected = NULL,
|
||||
multiple = FALSE, selectize = TRUE, width = NULL,
|
||||
size = NULL) {
|
||||
multiple = FALSE, selectize = TRUE, width = NULL,
|
||||
size = NULL) {
|
||||
|
||||
selected <- restoreInput(id = inputId, default = selected)
|
||||
|
||||
@@ -85,7 +85,7 @@ selectInput <- function(inputId, label, choices, selected = NULL,
|
||||
# default value if it's not specified
|
||||
if (is.null(selected)) {
|
||||
if (!multiple) selected <- firstChoice(choices)
|
||||
} else selected <- validateSelected(selected, choices, inputId)
|
||||
} else selected <- as.character(selected)
|
||||
|
||||
if (!is.null(size) && selectize) {
|
||||
stop("'size' argument is incompatible with 'selectize=TRUE'.")
|
||||
|
||||
@@ -164,23 +164,21 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
|
||||
`data-grid` = ticks,
|
||||
`data-grid-num` = n_ticks,
|
||||
`data-grid-snap` = FALSE,
|
||||
`data-prettify-separator` = sep,
|
||||
`data-prettify-enabled` = (sep != ""),
|
||||
`data-prefix` = pre,
|
||||
`data-postfix` = post,
|
||||
`data-keyboard` = TRUE,
|
||||
`data-keyboard-step` = step / (max - min) * 100,
|
||||
`data-drag-interval` = dragRange,
|
||||
# This value is only relevant for range sliders; for non-range sliders it
|
||||
# causes problems since ion.RangeSlider 2.1.2 (issue #1605).
|
||||
`data-drag-interval` = if (length(value) > 1) dragRange,
|
||||
# The following are ignored by the ion.rangeSlider, but are used by Shiny.
|
||||
`data-data-type` = dataType,
|
||||
`data-time-format` = timeFormat,
|
||||
`data-timezone` = timezone
|
||||
))
|
||||
|
||||
if (sep == "") {
|
||||
sliderProps$`data-prettify-enabled` <- "0"
|
||||
} else {
|
||||
sliderProps$`data-prettify-separator` <- sep
|
||||
}
|
||||
|
||||
# Replace any TRUE and FALSE with "true" and "false"
|
||||
sliderProps <- lapply(sliderProps, function(x) {
|
||||
if (identical(x, TRUE)) "true"
|
||||
@@ -220,7 +218,7 @@ sliderInput <- function(inputId, label, min, max, value, step = NULL,
|
||||
}
|
||||
|
||||
dep <- list(
|
||||
htmlDependency("ionrangeslider", "2.1.2", c(href="shared/ionrangeslider"),
|
||||
htmlDependency("ionrangeslider", "2.1.6", c(href="shared/ionrangeslider"),
|
||||
script = "js/ion.rangeSlider.min.js",
|
||||
# ion.rangeSlider also needs normalize.css, which is already included in
|
||||
# Bootstrap.
|
||||
|
||||
@@ -2,45 +2,62 @@ controlLabel <- function(controlName, label) {
|
||||
label %AND% tags$label(class = "control-label", `for` = controlName, label)
|
||||
}
|
||||
|
||||
|
||||
# Before shiny 0.9, `selected` refers to names/labels of `choices`; now it
|
||||
# refers to values. Below is a function for backward compatibility. It also
|
||||
# coerces the value to `character`.
|
||||
validateSelected <- function(selected, choices, inputId) {
|
||||
# this line accomplishes two tings:
|
||||
# - coerces selected to character
|
||||
# - drops name, otherwise toJSON() keeps it too
|
||||
selected <- as.character(selected)
|
||||
# if you are using optgroups, you're using shiny > 0.10.0, and you should
|
||||
# already know that `selected` must be a value instead of a label
|
||||
if (needOptgroup(choices)) return(selected)
|
||||
|
||||
if (is.list(choices)) choices <- unlist(choices)
|
||||
|
||||
nms <- names(choices)
|
||||
# labels and values are identical, no need to validate
|
||||
if (identical(nms, unname(choices))) return(selected)
|
||||
# when selected labels instead of values
|
||||
i <- (selected %in% nms) & !(selected %in% choices)
|
||||
if (any(i)) {
|
||||
warnFun <- if (all(i)) {
|
||||
# replace names with values
|
||||
selected <- unname(choices[selected])
|
||||
warning
|
||||
} else stop # stop when it is ambiguous (some labels == values)
|
||||
warnFun("'selected' must be the values instead of names of 'choices' ",
|
||||
"for the input '", inputId, "'")
|
||||
# This function takes in either a list or vector for `choices` (and
|
||||
# `choiceNames` and `choiceValues` are passed in as NULL) OR it takes
|
||||
# in a list or vector for both `choiceNames` and `choiceValues` (and
|
||||
# `choices` is passed as NULL) and returns a list of two elements:
|
||||
# - `choiceNames` is a vector or list that holds the options names
|
||||
# (each element can be arbitrary UI, or simple text)
|
||||
# - `choiceValues` is a vector or list that holds the options values
|
||||
# (each element must be simple text)
|
||||
normalizeChoicesArgs <- function(choices, choiceNames, choiceValues,
|
||||
mustExist = TRUE) {
|
||||
# if-else to check that either choices OR (choiceNames + choiceValues)
|
||||
# were correctly provided
|
||||
if (is.null(choices)) {
|
||||
if (is.null(choiceNames) || is.null(choiceValues)) {
|
||||
if (mustExist) {
|
||||
stop("Please specify a non-empty vector for `choices` (or, ",
|
||||
"alternatively, for both `choiceNames` AND `choiceValues`).")
|
||||
} else {
|
||||
if (is.null(choiceNames) && is.null(choiceValues)) {
|
||||
# this is useful when we call this function from `updateInputOptions()`
|
||||
# in which case, all three `choices`, `choiceNames` and `choiceValues`
|
||||
# may legitimately be NULL
|
||||
return(list(choiceNames = NULL, choiceValues = NULL))
|
||||
} else {
|
||||
stop("One of `choiceNames` or `choiceValues` was set to ",
|
||||
"NULL, but either both or none should be NULL.")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (length(choiceNames) != length(choiceValues)) {
|
||||
stop("`choiceNames` and `choiceValues` must have the same length.")
|
||||
}
|
||||
if (anyNamed(choiceNames) || anyNamed(choiceValues)) {
|
||||
stop("`choiceNames` and `choiceValues` must not be named.")
|
||||
}
|
||||
} else {
|
||||
if (!is.null(choiceNames) || !is.null(choiceValues)) {
|
||||
warning("Using `choices` argument; ignoring `choiceNames` and `choiceValues`.")
|
||||
}
|
||||
choices <- choicesWithNames(choices) # resolve names if not specified
|
||||
choiceNames <- names(choices)
|
||||
choiceValues <- unname(choices)
|
||||
}
|
||||
selected
|
||||
}
|
||||
|
||||
return(list(choiceNames = as.list(choiceNames),
|
||||
choiceValues = as.list(as.character(choiceValues))))
|
||||
}
|
||||
|
||||
# generate options for radio buttons and checkbox groups (type = 'checkbox' or
|
||||
# 'radio')
|
||||
generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox') {
|
||||
generateOptions <- function(inputId, selected, inline, type = 'checkbox',
|
||||
choiceNames, choiceValues,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
# generate a list of <input type=? [checked] />
|
||||
options <- mapply(
|
||||
choices, names(choices),
|
||||
choiceValues, choiceNames,
|
||||
FUN = function(value, name) {
|
||||
inputTag <- tags$input(
|
||||
type = type, name = inputId, value = value
|
||||
@@ -48,14 +65,18 @@ generateOptions <- function(inputId, choices, selected, inline, type = 'checkbox
|
||||
if (value %in% selected)
|
||||
inputTag$attribs$checked <- "checked"
|
||||
|
||||
# in case, the options include UI code other than text
|
||||
# (arbitrary HTML using the tags() function or equivalent)
|
||||
pd <- processDeps(name, session)
|
||||
|
||||
# If inline, there's no wrapper div, and the label needs a class like
|
||||
# checkbox-inline.
|
||||
if (inline) {
|
||||
tags$label(class = paste0(type, "-inline"), inputTag, tags$span(name))
|
||||
tags$label(class = paste0(type, "-inline"), inputTag,
|
||||
tags$span(pd$html, pd$dep))
|
||||
} else {
|
||||
tags$div(class = type,
|
||||
tags$label(inputTag, tags$span(name))
|
||||
)
|
||||
tags$div(class = type, tags$label(inputTag,
|
||||
tags$span(pd$html, pd$dep)))
|
||||
}
|
||||
},
|
||||
SIMPLIFY = FALSE, USE.NAMES = FALSE
|
||||
|
||||
@@ -191,7 +191,7 @@ staticHandler <- function(root) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
path <- req$PATH_INFO
|
||||
path <- URLdecode(req$PATH_INFO)
|
||||
|
||||
if (is.null(path))
|
||||
return(httpResponse(400, content="<h1>Bad Request</h1>"))
|
||||
|
||||
31
R/progress.R
31
R/progress.R
@@ -55,7 +55,6 @@
|
||||
#' 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 style Progress display style. If \code{"notification"} (the default),
|
||||
#' the progress indicator will show using Shiny's notification API. If
|
||||
#' \code{"old"}, use the same HTML and CSS used in Shiny 0.13.2 and below
|
||||
@@ -98,7 +97,6 @@
|
||||
#' @export
|
||||
Progress <- R6Class(
|
||||
'Progress',
|
||||
portable = TRUE,
|
||||
public = list(
|
||||
|
||||
initialize = function(session = getDefaultReactiveDomain(),
|
||||
@@ -112,8 +110,8 @@ Progress <- R6Class(
|
||||
private$id <- createUniqueId(8)
|
||||
private$min <- min
|
||||
private$max <- max
|
||||
private$style <- match.arg(style, choices = c("notification", "old"))
|
||||
private$value <- NULL
|
||||
private$style <- match.arg(style, choices = c("notification", "old"))
|
||||
private$closed <- FALSE
|
||||
|
||||
session$sendProgress('open', list(id = private$id, style = private$style))
|
||||
@@ -125,15 +123,15 @@ Progress <- R6Class(
|
||||
return()
|
||||
}
|
||||
|
||||
if (is.null(value) || is.na(value)) {
|
||||
if (is.null(value) || is.na(value))
|
||||
value <- NULL
|
||||
} else {
|
||||
|
||||
if (!is.null(value)) {
|
||||
private$value <- value
|
||||
# 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,
|
||||
@@ -142,11 +140,14 @@ Progress <- R6Class(
|
||||
style = private$style
|
||||
))
|
||||
|
||||
private$session$sendProgress('update', data)
|
||||
private$session$sendProgress('update', data)
|
||||
},
|
||||
|
||||
inc = function(amount = 0.1, message = NULL, detail = NULL) {
|
||||
value <- min(self$getValue() + amount, private$max)
|
||||
if (is.null(private$value))
|
||||
private$value <- private$min
|
||||
|
||||
value <- min(private$value + amount, private$max)
|
||||
self$set(value, message, detail)
|
||||
},
|
||||
|
||||
@@ -154,10 +155,7 @@ Progress <- R6Class(
|
||||
|
||||
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
|
||||
},
|
||||
getValue = function() private$value,
|
||||
|
||||
close = function() {
|
||||
if (private$closed) {
|
||||
@@ -173,12 +171,12 @@ Progress <- R6Class(
|
||||
),
|
||||
|
||||
private = list(
|
||||
session = 'environment',
|
||||
session = 'ShinySession',
|
||||
id = character(0),
|
||||
min = numeric(0),
|
||||
max = numeric(0),
|
||||
style = character(0),
|
||||
value = NULL,
|
||||
value = numeric(0),
|
||||
closed = logical(0)
|
||||
)
|
||||
)
|
||||
@@ -239,8 +237,7 @@ Progress <- R6Class(
|
||||
#' \code{"old"}, use the same HTML and CSS used in Shiny 0.13.2 and below
|
||||
#' (this is for backward-compatibility).
|
||||
#' @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.
|
||||
#' progress bar, relative to \code{min} and \code{max}.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run examples in interactive R sessions
|
||||
|
||||
282
R/reactives.R
282
R/reactives.R
@@ -38,6 +38,228 @@ Dependents <- R6Class(
|
||||
)
|
||||
|
||||
|
||||
# ReactiveVal ---------------------------------------------------------------
|
||||
|
||||
ReactiveVal <- R6Class(
|
||||
'ReactiveVal',
|
||||
portable = FALSE,
|
||||
private = list(
|
||||
value = NULL,
|
||||
label = NULL,
|
||||
frozen = FALSE,
|
||||
dependents = Dependents$new()
|
||||
),
|
||||
public = list(
|
||||
initialize = function(value, label = NULL) {
|
||||
private$value <- value
|
||||
private$label <- label
|
||||
.graphValueChange(private$label, value)
|
||||
},
|
||||
get = function() {
|
||||
private$dependents$register(depLabel = private$label)
|
||||
|
||||
if (private$frozen)
|
||||
reactiveStop()
|
||||
|
||||
private$value
|
||||
},
|
||||
set = function(value) {
|
||||
if (identical(private$value, value)) {
|
||||
return(invisible(FALSE))
|
||||
}
|
||||
private$value <- value
|
||||
.graphValueChange(private$label, value)
|
||||
private$dependents$invalidate()
|
||||
invisible(TRUE)
|
||||
},
|
||||
freeze = function(session = getDefaultReactiveDomain()) {
|
||||
if (is.null(session)) {
|
||||
stop("Can't freeze a reactiveVal without a reactive domain")
|
||||
}
|
||||
session$onFlushed(function() {
|
||||
self$thaw()
|
||||
})
|
||||
private$frozen <- TRUE
|
||||
},
|
||||
thaw = function() {
|
||||
private$frozen <- FALSE
|
||||
},
|
||||
isFrozen = function() {
|
||||
private$frozen
|
||||
},
|
||||
format = function(...) {
|
||||
# capture.output(print()) is necessary because format() doesn't
|
||||
# necessarily return a character vector, e.g. data.frame.
|
||||
label <- capture.output(print(base::format(private$value, ...)))
|
||||
if (length(label) == 1) {
|
||||
paste0("reactiveVal: ", label)
|
||||
} else {
|
||||
c("reactiveVal:", label)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#' Create a (single) reactive value
|
||||
#'
|
||||
#' The \code{reactiveVal} function is used to construct a "reactive value"
|
||||
#' object. This is an object used for reading and writing a value, like a
|
||||
#' variable, but with special capabilities for reactive programming. When you
|
||||
#' read the value out of a reactiveVal object, the calling reactive expression
|
||||
#' takes a dependency, and when you change the value, it notifies any reactives
|
||||
#' that previously depended on that value.
|
||||
#'
|
||||
#' \code{reactiveVal} is very similar to \code{\link{reactiveValues}}, except
|
||||
#' that the former is for a single reactive value (like a variable), whereas the
|
||||
#' latter lets you conveniently use multiple reactive values by name (like a
|
||||
#' named list of variables). For a one-off reactive value, it's more natural to
|
||||
#' use \code{reactiveVal}. See the Examples section for an illustration.
|
||||
#'
|
||||
#' @param value An optional initial value.
|
||||
#' @param label An optional label, for debugging purposes (see
|
||||
#' \code{\link{showReactLog}}). If missing, a label will be automatically
|
||||
#' created.
|
||||
#'
|
||||
#' @return A function. Call the function with no arguments to (reactively) read
|
||||
#' the value; call the function with a single argument to set the value.
|
||||
#'
|
||||
#' @examples
|
||||
#'
|
||||
#' \dontrun{
|
||||
#'
|
||||
#' # Create the object by calling reactiveVal
|
||||
#' r <- reactiveVal()
|
||||
#'
|
||||
#' # Set the value by calling with an argument
|
||||
#' r(10)
|
||||
#'
|
||||
#' # Read the value by calling without arguments
|
||||
#' r()
|
||||
#'
|
||||
#' }
|
||||
#'
|
||||
#' ## Only run examples in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#'
|
||||
#' ui <- fluidPage(
|
||||
#' actionButton("minus", "-1"),
|
||||
#' actionButton("plus", "+1"),
|
||||
#' br(),
|
||||
#' textOutput("value")
|
||||
#' )
|
||||
#'
|
||||
#' # The comments below show the equivalent logic using reactiveValues()
|
||||
#' server <- function(input, output, session) {
|
||||
#' value <- reactiveVal(0) # rv <- reactiveValues(value = 0)
|
||||
#'
|
||||
#' observeEvent(input$minus, {
|
||||
#' newValue <- value() - 1 # newValue <- rv$value - 1
|
||||
#' value(newValue) # rv$value <- newValue
|
||||
#' })
|
||||
#'
|
||||
#' observeEvent(input$plus, {
|
||||
#' newValue <- value() + 1 # newValue <- rv$value + 1
|
||||
#' value(newValue) # rv$value <- newValue
|
||||
#' })
|
||||
#'
|
||||
#' output$value <- renderText({
|
||||
#' value() # rv$value
|
||||
#' })
|
||||
#' }
|
||||
#'
|
||||
#' shinyApp(ui, server)
|
||||
#'
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
reactiveVal <- function(value = NULL, label = NULL) {
|
||||
if (missing(label)) {
|
||||
call <- sys.call()
|
||||
label <- rvalSrcrefToLabel(attr(call, "srcref", exact = TRUE))
|
||||
}
|
||||
|
||||
rv <- ReactiveVal$new(value, label)
|
||||
structure(
|
||||
function(x) {
|
||||
if (missing(x)) {
|
||||
rv$get()
|
||||
} else {
|
||||
force(x)
|
||||
rv$set(x)
|
||||
}
|
||||
},
|
||||
class = c("reactiveVal", "reactive"),
|
||||
label = label,
|
||||
.impl = rv
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname freezeReactiveValue
|
||||
#' @export
|
||||
freezeReactiveVal <- function(x) {
|
||||
domain <- getDefaultReactiveDomain()
|
||||
if (is.null(domain)) {
|
||||
stop("freezeReactiveVal() must be called when a default reactive domain is active.")
|
||||
}
|
||||
if (!inherits(x, "reactiveVal")) {
|
||||
stop("x must be a reactiveVal object")
|
||||
}
|
||||
|
||||
attr(x, ".impl", exact = TRUE)$freeze(domain)
|
||||
invisible()
|
||||
}
|
||||
|
||||
#' @export
|
||||
format.reactiveVal <- function(x, ...) {
|
||||
attr(x, ".impl", exact = TRUE)$format(...)
|
||||
}
|
||||
|
||||
# Attempts to extract the variable name that the reactiveVal object is being
|
||||
# assigned to (e.g. for `a <- reactiveVal()`, the result should be "a"). This
|
||||
# is a fragile, error-prone operation, so we default to a random label if
|
||||
# necessary.
|
||||
rvalSrcrefToLabel <- function(srcref,
|
||||
defaultLabel = paste0("reactiveVal", createUniqueId(4))) {
|
||||
|
||||
if (is.null(srcref))
|
||||
return(defaultLabel)
|
||||
|
||||
srcfile <- attr(srcref, "srcfile", exact = TRUE)
|
||||
if (is.null(srcfile))
|
||||
return(defaultLabel)
|
||||
|
||||
if (is.null(srcfile$lines))
|
||||
return(defaultLabel)
|
||||
|
||||
lines <- srcfile$lines
|
||||
# When pasting at the Console, srcfile$lines is not split
|
||||
if (length(lines) == 1) {
|
||||
lines <- strsplit(lines, "\n")[[1]]
|
||||
}
|
||||
|
||||
if (length(lines) < srcref[1]) {
|
||||
return(defaultLabel)
|
||||
}
|
||||
|
||||
firstLine <- substring(lines[srcref[1]], srcref[2] - 1)
|
||||
|
||||
m <- regexec("\\s*([^[:space:]]+)\\s*(<-|=)\\s*reactiveVal\\b", firstLine)
|
||||
if (m[[1]][1] == -1) {
|
||||
return(defaultLabel)
|
||||
}
|
||||
|
||||
sym <- regmatches(firstLine, m)[[1]][2]
|
||||
res <- try(parse(text = sym), silent = TRUE)
|
||||
if (inherits(res, "try-error"))
|
||||
return(defaultLabel)
|
||||
|
||||
if (length(res) != 1)
|
||||
return(defaultLabel)
|
||||
|
||||
return(as.character(res))
|
||||
}
|
||||
|
||||
|
||||
# ReactiveValues ------------------------------------------------------------
|
||||
|
||||
ReactiveValues <- R6Class(
|
||||
@@ -397,14 +619,17 @@ str.reactivevalues <- function(object, indent.str = " ", ...) {
|
||||
|
||||
#' Freeze a reactive value
|
||||
#'
|
||||
#' This freezes a reactive value. If the value is accessed while frozen, a
|
||||
#' These functions freeze a \code{\link{reactiveVal}}, or an element of a
|
||||
#' \code{\link{reactiveValues}}. If the value is accessed while frozen, a
|
||||
#' "silent" exception is raised and the operation is stopped. This is the same
|
||||
#' thing that happens if \code{req(FALSE)} is called. The value is thawed
|
||||
#' (un-frozen; accessing it will no longer raise an exception) when the current
|
||||
#' reactive domain is flushed. In a Shiny application, this occurs after all of
|
||||
#' the observers are executed.
|
||||
#'
|
||||
#' @param x A \code{\link{reactiveValues}} object (like \code{input}).
|
||||
#' @param x For \code{freezeReactiveValue}, a \code{\link{reactiveValues}}
|
||||
#' object (like \code{input}); for \code{freezeReactiveVal}, a
|
||||
#' \code{\link{reactiveVal}} object.
|
||||
#' @param name The name of a value in the \code{\link{reactiveValues}} object.
|
||||
#'
|
||||
#' @seealso \code{\link{req}}
|
||||
@@ -446,7 +671,7 @@ str.reactivevalues <- function(object, indent.str = " ", ...) {
|
||||
#' @export
|
||||
freezeReactiveValue <- function(x, name) {
|
||||
domain <- getDefaultReactiveDomain()
|
||||
if (is.null(getDefaultReactiveDomain)) {
|
||||
if (is.null(domain)) {
|
||||
stop("freezeReactiveValue() must be called when a default reactive domain is active.")
|
||||
}
|
||||
|
||||
@@ -461,6 +686,7 @@ Observable <- R6Class(
|
||||
'Observable',
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
.origFunc = 'function',
|
||||
.func = 'function',
|
||||
.label = character(0),
|
||||
.domain = NULL,
|
||||
@@ -490,6 +716,7 @@ Observable <- R6Class(
|
||||
funcLabel <- paste0("<reactive:", label, ">")
|
||||
}
|
||||
|
||||
.origFunc <<- func
|
||||
.func <<- wrapFunctionLabel(func, funcLabel,
|
||||
..stacktraceon = ..stacktraceon)
|
||||
.label <<- label
|
||||
@@ -520,6 +747,10 @@ Observable <- R6Class(
|
||||
else
|
||||
invisible(.value)
|
||||
},
|
||||
format = function() {
|
||||
label <- sprintf('reactive(%s)', paste(deparse(body(.origFunc)), collapse='\n'))
|
||||
strsplit(label, "\n")[[1]]
|
||||
},
|
||||
.updateValue = function() {
|
||||
ctx <- Context$new(.domain, .label, type = 'observable',
|
||||
prevId = .mostRecentCtxId)
|
||||
@@ -629,13 +860,13 @@ reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
|
||||
# Attach a label and a reference to the original user source for debugging
|
||||
srcref <- attr(substitute(x), "srcref", exact = TRUE)
|
||||
if (is.null(label)) {
|
||||
label <- srcrefToLabel(srcref[[1]],
|
||||
label <- rexprSrcrefToLabel(srcref[[1]],
|
||||
sprintf('reactive(%s)', paste(deparse(body(fun)), collapse='\n')))
|
||||
}
|
||||
if (length(srcref) >= 2) attr(label, "srcref") <- srcref[[2]]
|
||||
attr(label, "srcfile") <- srcFileOfRef(srcref[[1]])
|
||||
o <- Observable$new(fun, label, domain, ..stacktraceon = ..stacktraceon)
|
||||
structure(o$getValue, observable = o, class = "reactive")
|
||||
structure(o$getValue, observable = o, class = c("reactiveExpr", "reactive"))
|
||||
}
|
||||
|
||||
# Given the srcref to a reactive expression, attempts to figure out what the
|
||||
@@ -643,7 +874,7 @@ reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL,
|
||||
# scans the line of code that started the reactive block and looks for something
|
||||
# that looks like assignment. If we fail, fall back to a default value (likely
|
||||
# the block of code in the body of the reactive).
|
||||
srcrefToLabel <- function(srcref, defaultLabel) {
|
||||
rexprSrcrefToLabel <- function(srcref, defaultLabel) {
|
||||
if (is.null(srcref))
|
||||
return(defaultLabel)
|
||||
|
||||
@@ -681,19 +912,25 @@ srcrefToLabel <- function(srcref, defaultLabel) {
|
||||
return(as.character(res))
|
||||
}
|
||||
|
||||
#' @export
|
||||
format.reactiveExpr <- function(x, ...) {
|
||||
attr(x, "observable", exact = TRUE)$format()
|
||||
}
|
||||
|
||||
#' @export
|
||||
print.reactive <- function(x, ...) {
|
||||
label <- attr(x, "observable", exact = TRUE)$.label
|
||||
cat(label, "\n")
|
||||
cat(paste(format(x), collapse = "\n"), "\n")
|
||||
}
|
||||
|
||||
#' @export
|
||||
#' @rdname reactive
|
||||
is.reactive <- function(x) inherits(x, "reactive")
|
||||
is.reactive <- function(x) {
|
||||
inherits(x, "reactive")
|
||||
}
|
||||
|
||||
# Return the number of times that a reactive expression or observer has been run
|
||||
execCount <- function(x) {
|
||||
if (is.reactive(x))
|
||||
if (inherits(x, "reactiveExpr"))
|
||||
return(attr(x, "observable", exact = TRUE)$.execCount)
|
||||
else if (inherits(x, 'Observer'))
|
||||
return(x$.execCount)
|
||||
@@ -1151,6 +1388,10 @@ setAutoflush <- local({
|
||||
#' }
|
||||
#' @export
|
||||
reactiveTimer <- function(intervalMs=1000, session = getDefaultReactiveDomain()) {
|
||||
# Need to make sure that session is resolved at creation, not when the
|
||||
# callback below is fired (see #1621).
|
||||
force(session)
|
||||
|
||||
dependents <- Map$new()
|
||||
timerCallbacks$schedule(intervalMs, function() {
|
||||
# Quit if the session is closed
|
||||
@@ -1300,9 +1541,22 @@ coerceToFunc <- function(x) {
|
||||
#' @seealso \code{\link{reactiveFileReader}}
|
||||
#'
|
||||
#' @examples
|
||||
#' # Assume the existence of readTimestamp and readValue functions
|
||||
#' function(input, output, session) {
|
||||
#' data <- reactivePoll(1000, session, readTimestamp, readValue)
|
||||
#'
|
||||
#' data <- reactivePoll(1000, session,
|
||||
#' # This function returns the time that log_file was last modified
|
||||
#' checkFunc = function() {
|
||||
#' if (file.exists(log_file))
|
||||
#' file.info(log_file)$mtime[1]
|
||||
#' else
|
||||
#' ""
|
||||
#' },
|
||||
#' # This function returns the content of log_file
|
||||
#' valueFunc = function() {
|
||||
#' read.csv(log_file)
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' output$dataTable <- renderTable({
|
||||
#' data()
|
||||
#' })
|
||||
@@ -1377,7 +1631,7 @@ reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
|
||||
#' # Cross-session reactive file reader. In this example, all sessions share
|
||||
#' # the same reader, so read.csv only gets executed once no matter how many
|
||||
#' # user sessions are connected.
|
||||
#' fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
|
||||
#' fileData <- reactiveFileReader(1000, NULL, 'data.csv', read.csv)
|
||||
#' function(input, output, session) {
|
||||
#' output$data <- renderTable({
|
||||
#' fileData()
|
||||
@@ -1531,7 +1785,7 @@ maskReactiveContext <- function(expr) {
|
||||
#' invalidations that come from its reactive dependencies; it only invalidates
|
||||
#' in response to the given event.
|
||||
#'
|
||||
#' @section \code{ignoreNULL} and \code{ignoreInit}:
|
||||
#' @section ignoreNULL and ignoreInit:
|
||||
#'
|
||||
#' Both \code{observeEvent} and \code{eventReactive} take an \code{ignoreNULL}
|
||||
#' parameter that affects behavior when the \code{eventExpr} evaluates to
|
||||
|
||||
588
R/render-plot.R
588
R/render-plot.R
@@ -287,17 +287,30 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
# .. ..$ y: NULL
|
||||
# ..$ mapping: Named list()
|
||||
#
|
||||
# For ggplot2, it might be something like:
|
||||
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
|
||||
# str(getGgplotCoordmap(p, 1))
|
||||
# For ggplot2, first you need to define the print.ggplot function from inside
|
||||
# renderPlot, then use it to print the plot:
|
||||
# print.ggplot <- function(x) {
|
||||
# grid::grid.newpage()
|
||||
#
|
||||
# build <- ggplot2::ggplot_build(x)
|
||||
#
|
||||
# gtable <- ggplot2::ggplot_gtable(build)
|
||||
# grid::grid.draw(gtable)
|
||||
#
|
||||
# structure(list(
|
||||
# build = build,
|
||||
# gtable = gtable
|
||||
# ), class = "ggplot_build_gtable")
|
||||
# }
|
||||
#
|
||||
# p <- print(ggplot(mtcars, aes(wt, mpg)) + geom_point())
|
||||
# str(getGgplotCoordmap(p, 1, 72))
|
||||
# List of 1
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars: Named list()
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
@@ -320,8 +333,8 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
# can be up to two of them.
|
||||
# mtc <- mtcars
|
||||
# mtc$am <- factor(mtc$am)
|
||||
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + facet_wrap(~ am)
|
||||
# str(getGgplotCoordmap(p, 1))
|
||||
# p <- print(ggplot(mtc, aes(wt, mpg)) + geom_point() + facet_wrap(~ am))
|
||||
# str(getGgplotCoordmap(p, 1, 72))
|
||||
# List of 2
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
@@ -329,8 +342,6 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 1
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
@@ -354,8 +365,6 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
# ..$ col : int 2
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 2
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
@@ -418,81 +427,191 @@ getPrevPlotCoordmap <- function(width, height) {
|
||||
|
||||
# Given a ggplot_build_gtable object, return a coordmap for it.
|
||||
getGgplotCoordmap <- function(p, pixelratio, res) {
|
||||
# Structure of ggplot objects changed after 2.1.0
|
||||
new_ggplot <- (utils::packageVersion("ggplot2") > "2.1.0")
|
||||
|
||||
if (!inherits(p, "ggplot_build_gtable"))
|
||||
return(NULL)
|
||||
|
||||
tryCatch({
|
||||
# Get info from built ggplot object
|
||||
info <- find_panel_info(p$build)
|
||||
|
||||
# Get ranges from gtable - it's possible for this to return more elements than
|
||||
# info, because it calculates positions even for panels that aren't present.
|
||||
# This can happen with facet_wrap.
|
||||
ranges <- find_panel_ranges(p$gtable, pixelratio, res)
|
||||
|
||||
for (i in seq_along(info)) {
|
||||
info[[i]]$range <- ranges[[i]]
|
||||
}
|
||||
|
||||
return(info)
|
||||
|
||||
}, error = function(e) {
|
||||
# If there was an error extracting info from the ggplot object, just return
|
||||
# a list with the error message.
|
||||
return(structure(list(), error = e$message))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
find_panel_info <- function(b) {
|
||||
# Structure of ggplot objects changed after 2.1.0. After 2.2.1, there was a
|
||||
# an API for extracting the necessary information.
|
||||
ggplot_ver <- utils::packageVersion("ggplot2")
|
||||
|
||||
if (ggplot_ver > "2.2.1") {
|
||||
find_panel_info_api(b)
|
||||
} else if (ggplot_ver > "2.1.0") {
|
||||
find_panel_info_non_api(b, ggplot_format = "new")
|
||||
} else {
|
||||
find_panel_info_non_api(b, ggplot_format = "old")
|
||||
}
|
||||
}
|
||||
|
||||
# This is for ggplot2>2.2.1, after an API was introduced for extracting
|
||||
# information about the plot object.
|
||||
find_panel_info_api <- function(b) {
|
||||
# Workaround for check NOTE, until ggplot2 >2.2.1 is released
|
||||
colon_colon <- `::`
|
||||
# Given a built ggplot object, return x and y domains (data space coords) for
|
||||
# each panel.
|
||||
find_panel_info <- function(b) {
|
||||
if (new_ggplot) {
|
||||
layout <- b$layout$panel_layout
|
||||
} else {
|
||||
layout <- b$panel$layout
|
||||
}
|
||||
# Convert factor to numbers
|
||||
layout$PANEL <- as.integer(as.character(layout$PANEL))
|
||||
layout <- colon_colon("ggplot2", "summarise_layout")(b)
|
||||
coord <- colon_colon("ggplot2", "summarise_coord")(b)
|
||||
layers <- colon_colon("ggplot2", "summarise_layers")(b)
|
||||
|
||||
# Names of facets
|
||||
facet_vars <- NULL
|
||||
if (new_ggplot) {
|
||||
facet <- b$layout$facet
|
||||
if (inherits(facet, "FacetGrid")) {
|
||||
facet_vars <- vapply(c(facet$params$cols, facet$params$rows), as.character, character(1))
|
||||
} else if (inherits(facet, "FacetWrap")) {
|
||||
facet_vars <- vapply(facet$params$facets, as.character, character(1))
|
||||
}
|
||||
} else {
|
||||
facet <- b$plot$facet
|
||||
if (inherits(facet, "grid")) {
|
||||
facet_vars <- vapply(c(facet$cols, facet$rows), as.character, character(1))
|
||||
} else if (inherits(facet, "wrap")) {
|
||||
facet_vars <- vapply(facet$facets, as.character, character(1))
|
||||
# Given x and y scale objects and a coord object, return a list that has
|
||||
# the bases of log transformations for x and y, or NULL if it's not a
|
||||
# log transform.
|
||||
get_log_bases <- function(xscale, yscale, coord) {
|
||||
# Given a transform object, find the log base; if the transform object is
|
||||
# NULL, or if it's not a log transform, return NA.
|
||||
get_log_base <- function(trans) {
|
||||
if (!is.null(trans) && grepl("^log-", trans$name)) {
|
||||
environment(trans$transform)$base
|
||||
} else {
|
||||
NA_real_
|
||||
}
|
||||
}
|
||||
|
||||
# Iterate over each row in the layout data frame
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
# Slice out one row
|
||||
l <- layout[i, ]
|
||||
|
||||
scale_x <- l$SCALE_X
|
||||
scale_y <- l$SCALE_Y
|
||||
|
||||
mapping <- find_plot_mappings(b)
|
||||
|
||||
# For each of the faceting variables, get the value of that variable in
|
||||
# the current panel. Default to empty _named_ list so that it's sent as a
|
||||
# JSON object, not array.
|
||||
panel_vars <- list(a = NULL)[0]
|
||||
for (i in seq_along(facet_vars)) {
|
||||
var_name <- facet_vars[[i]]
|
||||
vname <- paste0("panelvar", i)
|
||||
|
||||
mapping[[vname]] <- var_name
|
||||
panel_vars[[vname]] <- l[[var_name]]
|
||||
}
|
||||
|
||||
list(
|
||||
panel = l$PANEL,
|
||||
row = l$ROW,
|
||||
col = l$COL,
|
||||
panel_vars = panel_vars,
|
||||
scale_x = scale_x,
|
||||
scale_y = scale_x,
|
||||
log = check_log_scales(b, scale_x, scale_y),
|
||||
domain = find_panel_domain(b, l$PANEL, scale_x, scale_y),
|
||||
mapping = mapping
|
||||
)
|
||||
})
|
||||
# First look for log base in scale, then coord; otherwise NULL.
|
||||
list(
|
||||
x = get_log_base(xscale$trans) %OR% coord$xlog %OR% NULL,
|
||||
y = get_log_base(yscale$trans) %OR% coord$ylog %OR% NULL
|
||||
)
|
||||
}
|
||||
|
||||
# Given x/y min/max, and the x/y scale objects, create a list that
|
||||
# represents the domain. Note that the x/y min/max should be taken from
|
||||
# the layout summary table, not the scale objects.
|
||||
get_domain <- function(xmin, xmax, ymin, ymax, xscale, yscale) {
|
||||
is_reverse <- function(scale) {
|
||||
identical(scale$trans$name, "reverse")
|
||||
}
|
||||
|
||||
domain <- list(
|
||||
left = xmin,
|
||||
right = xmax,
|
||||
bottom = ymin,
|
||||
top = ymax
|
||||
)
|
||||
|
||||
if (is_reverse(xscale)) {
|
||||
domain$left <- -domain$left
|
||||
domain$right <- -domain$right
|
||||
}
|
||||
if (is_reverse(yscale)) {
|
||||
domain$top <- -domain$top
|
||||
domain$bottom <- -domain$bottom
|
||||
}
|
||||
|
||||
domain
|
||||
}
|
||||
|
||||
# Rename the items in vars to have names like panelvar1, panelvar2.
|
||||
rename_panel_vars <- function(vars) {
|
||||
for (i in seq_along(vars)) {
|
||||
names(vars)[i] <- paste0("panelvar", i)
|
||||
}
|
||||
vars
|
||||
}
|
||||
|
||||
get_mappings <- function(layers, layout, coord) {
|
||||
# For simplicity, we'll just use the mapping from the first layer of the
|
||||
# ggplot object. The original uses quoted expressions; convert to
|
||||
# character.
|
||||
mapping <- layers$mapping[[1]]
|
||||
# lapply'ing as.character results in unexpected behavior for expressions
|
||||
# like `wt/2`; deparse handles it correctly.
|
||||
mapping <- lapply(mapping, deparse)
|
||||
|
||||
# If either x or y is not present, give it a NULL entry.
|
||||
mapping <- mergeVectors(list(x = NULL, y = NULL), mapping)
|
||||
|
||||
# The names (not values) of panel vars are the same across all panels,
|
||||
# so just look at the first one. Also, the order of panel vars needs
|
||||
# to be reversed.
|
||||
vars <- rev(layout$vars[[1]])
|
||||
for (i in seq_along(vars)) {
|
||||
mapping[[paste0("panelvar", i)]] <- names(vars)[i]
|
||||
}
|
||||
|
||||
if (isTRUE(coord$flip)) {
|
||||
mapping[c("x", "y")] <- mapping[c("y", "x")]
|
||||
}
|
||||
|
||||
mapping
|
||||
}
|
||||
|
||||
# Mapping is constant across all panels, so get it here and reuse later.
|
||||
mapping <- get_mappings(layers, layout, coord)
|
||||
|
||||
# If coord_flip is used, these need to be swapped
|
||||
flip_xy <- function(layout) {
|
||||
l <- layout
|
||||
l$xscale <- layout$yscale
|
||||
l$yscale <- layout$xscale
|
||||
l$xmin <- layout$ymin
|
||||
l$xmax <- layout$ymax
|
||||
l$ymin <- layout$xmin
|
||||
l$ymax <- layout$xmax
|
||||
l
|
||||
}
|
||||
if (coord$flip) {
|
||||
layout <- flip_xy(layout)
|
||||
}
|
||||
|
||||
# Iterate over each row in the layout data frame
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
# Slice out one row, use it as a list. The (former) list-cols are still
|
||||
# in lists, so we need to unwrap them.
|
||||
l <- as.list(layout[i, ])
|
||||
l$vars <- l$vars[[1]]
|
||||
l$xscale <- l$xscale[[1]]
|
||||
l$yscale <- l$yscale[[1]]
|
||||
|
||||
list(
|
||||
panel = as.numeric(l$panel),
|
||||
row = l$row,
|
||||
col = l$col,
|
||||
# Rename panel vars. They must also be in reversed order.
|
||||
panel_vars = rename_panel_vars(rev(l$vars)),
|
||||
log = get_log_bases(l$xscale, l$yscale, coord),
|
||||
domain = get_domain(l$xmin, l$xmax, l$ymin, l$ymax, l$xscale, l$yscale),
|
||||
mapping = mapping
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
# This is for ggplot2<=2.2.1, before an API was introduced for extracting
|
||||
# information about the plot object. The "old" format was used before 2.1.0.
|
||||
# The "new" format was used after 2.1.0, up to 2.2.1. The reason these two
|
||||
# formats are mixed together in a single function is historical, and it's not
|
||||
# worthwhile to separate them at this point.
|
||||
find_panel_info_non_api <- function(b, ggplot_format) {
|
||||
# Given a single range object (representing the data domain) from a built
|
||||
# ggplot object, return the domain.
|
||||
find_panel_domain <- function(b, panel_num, scalex_num = 1, scaley_num = 1) {
|
||||
if (new_ggplot) {
|
||||
if (ggplot_format == "new") {
|
||||
range <- b$layout$panel_ranges[[panel_num]]
|
||||
} else {
|
||||
range <- b$panel$ranges[[panel_num]]
|
||||
@@ -505,7 +624,7 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
|
||||
)
|
||||
|
||||
# Check for reversed scales
|
||||
if (new_ggplot) {
|
||||
if (ggplot_format == "new") {
|
||||
xscale <- b$layout$panel_scales$x[[scalex_num]]
|
||||
yscale <- b$layout$panel_scales$y[[scaley_num]]
|
||||
} else {
|
||||
@@ -546,7 +665,7 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
|
||||
y_names <- character(0)
|
||||
|
||||
# Continuous scales have a trans; discrete ones don't
|
||||
if (new_ggplot) {
|
||||
if (ggplot_format == "new") {
|
||||
if (!is.null(b$layout$panel_scales$x[[scalex_num]]$trans))
|
||||
x_names <- b$layout$panel_scales$x[[scalex_num]]$trans$name
|
||||
if (!is.null(b$layout$panel_scales$y[[scaley_num]]$trans))
|
||||
@@ -620,129 +739,220 @@ getGgplotCoordmap <- function(p, pixelratio, res) {
|
||||
mappings
|
||||
}
|
||||
|
||||
# Given a gtable object, return the x and y ranges (in pixel dimensions)
|
||||
find_panel_ranges <- function(g, pixelratio) {
|
||||
# Given a vector of unit objects, return logical vector indicating which ones
|
||||
# are "null" units. These units use the remaining available width/height --
|
||||
# that is, the space not occupied by elements that have an absolute size.
|
||||
is_null_unit <- function(x) {
|
||||
# A vector of units can be either a list of individual units (a unit.list
|
||||
# object), each with their own set of attributes, or an atomic vector with
|
||||
# one set of attributes. ggplot2 switched from the former (in version
|
||||
# 1.0.1) to the latter. We need to make sure that we get the correct
|
||||
# result in both cases.
|
||||
if (inherits(x, "unit.list")) {
|
||||
# For ggplot2 <= 1.0.1
|
||||
vapply(x, FUN.VALUE = logical(1), function(u) {
|
||||
isTRUE(attr(u, "unit", exact = TRUE) == "null")
|
||||
})
|
||||
} else {
|
||||
# For later versions of ggplot2
|
||||
attr(x, "unit", exact = TRUE) == "null"
|
||||
}
|
||||
if (ggplot_format == "new") {
|
||||
layout <- b$layout$panel_layout
|
||||
} else {
|
||||
layout <- b$panel$layout
|
||||
}
|
||||
# Convert factor to numbers
|
||||
layout$PANEL <- as.integer(as.character(layout$PANEL))
|
||||
|
||||
# Names of facets
|
||||
facet_vars <- NULL
|
||||
if (ggplot_format == "new") {
|
||||
facet <- b$layout$facet
|
||||
if (inherits(facet, "FacetGrid")) {
|
||||
facet_vars <- vapply(c(facet$params$cols, facet$params$rows), as.character, character(1))
|
||||
} else if (inherits(facet, "FacetWrap")) {
|
||||
facet_vars <- vapply(facet$params$facets, as.character, character(1))
|
||||
}
|
||||
|
||||
# Workaround for a bug in the quartz device. If you have a 400x400 image and
|
||||
# run `convertWidth(unit(1, "npc"), "native")`, the result will depend on
|
||||
# res setting of the device. If res=72, then it returns 400 (as expected),
|
||||
# but if, e.g., res=96, it will return 300, which is incorrect.
|
||||
devScaleFactor <- 1
|
||||
if (grepl("quartz", names(grDevices::dev.cur()), fixed = TRUE)) {
|
||||
devScaleFactor <- res / 72
|
||||
} else {
|
||||
facet <- b$plot$facet
|
||||
if (inherits(facet, "grid")) {
|
||||
facet_vars <- vapply(c(facet$cols, facet$rows), as.character, character(1))
|
||||
} else if (inherits(facet, "wrap")) {
|
||||
facet_vars <- vapply(facet$facets, as.character, character(1))
|
||||
}
|
||||
|
||||
# Convert a unit (or vector of units) to a numeric vector of pixel sizes
|
||||
h_px <- function(x) {
|
||||
devScaleFactor * grid::convertHeight(x, "native", valueOnly = TRUE)
|
||||
}
|
||||
w_px <- function(x) {
|
||||
devScaleFactor * grid::convertWidth(x, "native", valueOnly = TRUE)
|
||||
}
|
||||
|
||||
# Given a vector of relative sizes (in grid units), and a function for
|
||||
# converting grid units to numeric pixels, return a numeric vector of
|
||||
# pixel sizes.
|
||||
find_px_sizes <- function(rel_sizes, unit_to_px) {
|
||||
# Total pixels (in height or width)
|
||||
total_px <- unit_to_px(grid::unit(1, "npc"))
|
||||
# Calculate size of all panel(s) together. Panels (and only panels) have
|
||||
# null size.
|
||||
null_idx <- is_null_unit(rel_sizes)
|
||||
# All the absolute heights. At this point, null heights are 0. We need to
|
||||
# calculate them separately and add them in later.
|
||||
px_sizes <- unit_to_px(rel_sizes)
|
||||
# Total size for panels is image size minus absolute (non-panel) elements
|
||||
panel_px_total <- total_px - sum(px_sizes)
|
||||
# Divide up the total panel size up into the panels (scaled by size)
|
||||
panel_sizes_rel <- as.numeric(rel_sizes[null_idx])
|
||||
panel_sizes_rel <- panel_sizes_rel / sum(panel_sizes_rel)
|
||||
px_sizes[null_idx] <- panel_px_total * panel_sizes_rel
|
||||
abs(px_sizes)
|
||||
}
|
||||
|
||||
px_heights <- find_px_sizes(g$heights, h_px)
|
||||
px_widths <- find_px_sizes(g$widths, w_px)
|
||||
|
||||
# Convert to absolute pixel positions
|
||||
x_pos <- cumsum(px_widths)
|
||||
y_pos <- cumsum(px_heights)
|
||||
|
||||
# Match up the pixel dimensions to panels
|
||||
layout <- g$layout
|
||||
# For panels:
|
||||
# * For facet_wrap, they'll be named "panel-1", "panel-2", etc.
|
||||
# * For no facet or facet_grid, they'll just be named "panel". For
|
||||
# facet_grid, we need to re-order the layout table. Assume that panel
|
||||
# numbers go from left to right, then next row.
|
||||
# Assign a number to each panel, corresponding to PANEl in the built ggplot
|
||||
# object.
|
||||
layout <- layout[grepl("^panel", layout$name), ]
|
||||
layout <- layout[order(layout$t, layout$l), ]
|
||||
layout$panel <- seq_len(nrow(layout))
|
||||
|
||||
# When using a HiDPI client on a Linux server, the pixel
|
||||
# dimensions are doubled, so we have to divide the dimensions by
|
||||
# `pixelratio`. When a HiDPI client is used on a Mac server (with
|
||||
# the quartz device), the pixel dimensions _aren't_ doubled, even though
|
||||
# the image has double size. In the latter case we don't have to scale the
|
||||
# numbers down.
|
||||
pix_ratio <- 1
|
||||
if (!grepl("^quartz", names(grDevices::dev.cur()))) {
|
||||
pix_ratio <- pixelratio
|
||||
}
|
||||
|
||||
# Return list of lists, where each inner list has left, right, top, bottom
|
||||
# values for a panel
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
p <- layout[i, , drop = FALSE]
|
||||
list(
|
||||
left = x_pos[p$l - 1] / pix_ratio,
|
||||
right = x_pos[p$r] / pix_ratio,
|
||||
bottom = y_pos[p$b] / pix_ratio,
|
||||
top = y_pos[p$t - 1] / pix_ratio
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# Iterate over each row in the layout data frame
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
# Slice out one row
|
||||
l <- layout[i, ]
|
||||
|
||||
tryCatch({
|
||||
# Get info from built ggplot object
|
||||
info <- find_panel_info(p$build)
|
||||
scale_x <- l$SCALE_X
|
||||
scale_y <- l$SCALE_Y
|
||||
|
||||
# Get ranges from gtable - it's possible for this to return more elements than
|
||||
# info, because it calculates positions even for panels that aren't present.
|
||||
# This can happen with facet_wrap.
|
||||
ranges <- find_panel_ranges(p$gtable, pixelratio)
|
||||
mapping <- find_plot_mappings(b)
|
||||
|
||||
for (i in seq_along(info)) {
|
||||
info[[i]]$range <- ranges[[i]]
|
||||
# For each of the faceting variables, get the value of that variable in
|
||||
# the current panel. Default to empty _named_ list so that it's sent as a
|
||||
# JSON object, not array.
|
||||
panel_vars <- list(a = NULL)[0]
|
||||
for (i in seq_along(facet_vars)) {
|
||||
var_name <- facet_vars[[i]]
|
||||
vname <- paste0("panelvar", i)
|
||||
|
||||
mapping[[vname]] <- var_name
|
||||
panel_vars[[vname]] <- l[[var_name]]
|
||||
}
|
||||
|
||||
return(info)
|
||||
|
||||
}, error = function(e) {
|
||||
# If there was an error extracting info from the ggplot object, just return
|
||||
# a list with the error message.
|
||||
return(structure(list(), error = e$message))
|
||||
list(
|
||||
panel = l$PANEL,
|
||||
row = l$ROW,
|
||||
col = l$COL,
|
||||
panel_vars = panel_vars,
|
||||
scale_x = scale_x,
|
||||
scale_y = scale_x,
|
||||
log = check_log_scales(b, scale_x, scale_y),
|
||||
domain = find_panel_domain(b, l$PANEL, scale_x, scale_y),
|
||||
mapping = mapping
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
# Given a gtable object, return the x and y ranges (in pixel dimensions)
|
||||
find_panel_ranges <- function(g, pixelratio, res) {
|
||||
# Given a vector of unit objects, return logical vector indicating which ones
|
||||
# are "null" units. These units use the remaining available width/height --
|
||||
# that is, the space not occupied by elements that have an absolute size.
|
||||
is_null_unit <- function(x) {
|
||||
# A vector of units can be either a list of individual units (a unit.list
|
||||
# object), each with their own set of attributes, or an atomic vector with
|
||||
# one set of attributes. ggplot2 switched from the former (in version
|
||||
# 1.0.1) to the latter. We need to make sure that we get the correct
|
||||
# result in both cases.
|
||||
if (inherits(x, "unit.list")) {
|
||||
# For ggplot2 <= 1.0.1
|
||||
vapply(x, FUN.VALUE = logical(1), function(u) {
|
||||
isTRUE(attr(u, "unit", exact = TRUE) == "null")
|
||||
})
|
||||
} else {
|
||||
# For later versions of ggplot2
|
||||
attr(x, "unit", exact = TRUE) == "null"
|
||||
}
|
||||
}
|
||||
|
||||
# Workaround for a bug in the quartz device. If you have a 400x400 image and
|
||||
# run `convertWidth(unit(1, "npc"), "native")`, the result will depend on
|
||||
# res setting of the device. If res=72, then it returns 400 (as expected),
|
||||
# but if, e.g., res=96, it will return 300, which is incorrect.
|
||||
devScaleFactor <- 1
|
||||
if (grepl("quartz", names(grDevices::dev.cur()), fixed = TRUE)) {
|
||||
devScaleFactor <- res / 72
|
||||
}
|
||||
|
||||
# Convert a unit (or vector of units) to a numeric vector of pixel sizes
|
||||
h_px <- function(x) {
|
||||
devScaleFactor * grid::convertHeight(x, "native", valueOnly = TRUE)
|
||||
}
|
||||
w_px <- function(x) {
|
||||
devScaleFactor * grid::convertWidth(x, "native", valueOnly = TRUE)
|
||||
}
|
||||
|
||||
# Given a vector of relative sizes (in grid units), and a function for
|
||||
# converting grid units to numeric pixels, return a list with: known pixel
|
||||
# dimensions, scalable dimensions, and the overall space for the scalable
|
||||
# objects.
|
||||
find_size_info <- function(rel_sizes, unit_to_px) {
|
||||
# Total pixels (in height or width)
|
||||
total_px <- unit_to_px(grid::unit(1, "npc"))
|
||||
# Calculate size of all panel(s) together. Panels (and only panels) have
|
||||
# null size.
|
||||
null_idx <- is_null_unit(rel_sizes)
|
||||
|
||||
# All the absolute heights. At this point, null heights are 0. We need to
|
||||
# calculate them separately and add them in later.
|
||||
px_sizes <- unit_to_px(rel_sizes)
|
||||
# Mark the null heights as NA.
|
||||
px_sizes[null_idx] <- NA_real_
|
||||
|
||||
# The plotting panels all are 'null' units.
|
||||
null_sizes <- rep(NA_real_, length(rel_sizes))
|
||||
null_sizes[null_idx] <- as.numeric(rel_sizes[null_idx])
|
||||
|
||||
# Total size allocated for panels is the total image size minus absolute
|
||||
# (non-panel) elements.
|
||||
panel_px_total <- total_px - sum(px_sizes, na.rm = TRUE)
|
||||
|
||||
# Size of a 1null unit
|
||||
null_px <- abs(panel_px_total / sum(null_sizes, na.rm = TRUE))
|
||||
|
||||
# This returned list contains:
|
||||
# * px_sizes: A vector of known pixel dimensions. The values that were
|
||||
# null units will be assigned NA. The null units are ones that scale
|
||||
# when the plotting area is resized.
|
||||
# * null_sizes: A vector of the null units. All others will be assigned
|
||||
# NA. The null units often are 1, but they may be any value, especially
|
||||
# when using coord_fixed.
|
||||
# * null_px: The size (in pixels) of a 1null unit.
|
||||
# * null_px_scaled: The size (in pixels) of a 1null unit when scaled to
|
||||
# fit a smaller dimension (used for plots with coord_fixed).
|
||||
list(
|
||||
px_sizes = abs(px_sizes),
|
||||
null_sizes = null_sizes,
|
||||
null_px = null_px,
|
||||
null_px_scaled = null_px
|
||||
)
|
||||
}
|
||||
|
||||
# Given a size_info, return absolute pixel positions
|
||||
size_info_to_px <- function(info) {
|
||||
px_sizes <- info$px_sizes
|
||||
|
||||
null_idx <- !is.na(info$null_sizes)
|
||||
px_sizes[null_idx] <- info$null_sizes[null_idx] * info$null_px_scaled
|
||||
|
||||
# If this direction is scaled down because of coord_fixed, we need to add an
|
||||
# offset so that the pixel locations are centered.
|
||||
offset <- (info$null_px - info$null_px_scaled) *
|
||||
sum(info$null_sizes, na.rm = TRUE) / 2
|
||||
|
||||
# Get absolute pixel positions
|
||||
cumsum(px_sizes) + offset
|
||||
}
|
||||
|
||||
heights_info <- find_size_info(g$heights, h_px)
|
||||
widths_info <- find_size_info(g$widths, w_px)
|
||||
|
||||
if (g$respect) {
|
||||
# This is a plot with coord_fixed. The grid 'respect' option means to use
|
||||
# the same pixel value for 1null, for width and height. We want the
|
||||
# smaller of the two values -- that's what makes the plot fit in the
|
||||
# viewport.
|
||||
null_px_min <- min(heights_info$null_px, widths_info$null_px)
|
||||
heights_info$null_px_scaled <- null_px_min
|
||||
widths_info$null_px_scaled <- null_px_min
|
||||
}
|
||||
|
||||
# Convert to absolute pixel positions
|
||||
y_pos <- size_info_to_px(heights_info)
|
||||
x_pos <- size_info_to_px(widths_info)
|
||||
|
||||
# Match up the pixel dimensions to panels
|
||||
layout <- g$layout
|
||||
# For panels:
|
||||
# * For facet_wrap, they'll be named "panel-1", "panel-2", etc.
|
||||
# * For no facet or facet_grid, they'll just be named "panel". For
|
||||
# facet_grid, we need to re-order the layout table. Assume that panel
|
||||
# numbers go from left to right, then next row.
|
||||
# Assign a number to each panel, corresponding to PANEl in the built ggplot
|
||||
# object.
|
||||
layout <- layout[grepl("^panel", layout$name), ]
|
||||
layout <- layout[order(layout$t, layout$l), ]
|
||||
layout$panel <- seq_len(nrow(layout))
|
||||
|
||||
# When using a HiDPI client on a Linux server, the pixel
|
||||
# dimensions are doubled, so we have to divide the dimensions by
|
||||
# `pixelratio`. When a HiDPI client is used on a Mac server (with
|
||||
# the quartz device), the pixel dimensions _aren't_ doubled, even though
|
||||
# the image has double size. In the latter case we don't have to scale the
|
||||
# numbers down.
|
||||
pix_ratio <- 1
|
||||
if (!grepl("^quartz", names(grDevices::dev.cur()))) {
|
||||
pix_ratio <- pixelratio
|
||||
}
|
||||
|
||||
# Return list of lists, where each inner list has left, right, top, bottom
|
||||
# values for a panel
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
p <- layout[i, , drop = FALSE]
|
||||
list(
|
||||
left = x_pos[p$l - 1] / pix_ratio,
|
||||
right = x_pos[p$r] / pix_ratio,
|
||||
bottom = y_pos[p$b] / pix_ratio,
|
||||
top = y_pos[p$t - 1] / pix_ratio
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -370,9 +370,9 @@ argsForServerFunc <- function(serverFunc, session) {
|
||||
}
|
||||
|
||||
getEffectiveBody <- function(func) {
|
||||
# Note: NULL values are OK. isS4(NULL) returns FALSE, body(NULL)
|
||||
# returns NULL.
|
||||
if (isS4(func) && class(func) == "functionWithTrace")
|
||||
if (is.null(func))
|
||||
NULL
|
||||
else if (isS4(func) && class(func) == "functionWithTrace")
|
||||
body(func@original)
|
||||
else
|
||||
body(func)
|
||||
@@ -561,7 +561,7 @@ runApp <- function(appDir=getwd(),
|
||||
}, add = TRUE)
|
||||
|
||||
if (.globals$running) {
|
||||
stop("Can't call `runApp()` from within `runApp()`. If your ,",
|
||||
stop("Can't call `runApp()` from within `runApp()`. If your ",
|
||||
"application code contains `runApp()`, please remove it.")
|
||||
}
|
||||
.globals$running <- TRUE
|
||||
|
||||
94
R/shiny.R
94
R/shiny.R
@@ -5,7 +5,7 @@ NULL
|
||||
#'
|
||||
#' Shiny makes it incredibly easy to build interactive web applications with R.
|
||||
#' Automatic "reactive" binding between inputs and outputs and extensive
|
||||
#' pre-built widgets make it possible to build beautiful, responsive, and
|
||||
#' prebuilt widgets make it possible to build beautiful, responsive, and
|
||||
#' powerful applications with minimal effort.
|
||||
#'
|
||||
#' The Shiny tutorial at \url{http://shiny.rstudio.com/tutorial/} explains
|
||||
@@ -201,12 +201,13 @@ workerId <- local({
|
||||
#' }
|
||||
#' \item{\code{singletons} - for internal use}
|
||||
#' \item{\code{url_protocol}, \code{url_hostname}, \code{url_port},
|
||||
#' \code{url_pathname}, \code{url_search}, and \code{url_hash_initial}
|
||||
#' can be used to get the components of the URL that was requested by the
|
||||
#' browser to load the Shiny app page. These values are from the
|
||||
#' browser's perspective, so neither HTTP proxies nor Shiny Server will
|
||||
#' affect these values. The \code{url_search} value may be used with
|
||||
#' \code{\link{parseQueryString}} to access query string parameters.
|
||||
#' \code{url_pathname}, \code{url_search}, \code{url_hash_initial}
|
||||
#' and \code{url_hash} can be used to get the components of the URL
|
||||
#' that was requested by the browser to load the Shiny app page.
|
||||
#' These values are from the browser's perspective, so neither HTTP
|
||||
#' proxies nor Shiny Server will affect these values. The
|
||||
#' \code{url_search} value may be used with \code{\link{parseQueryString}}
|
||||
#' to access query string parameters.
|
||||
#' }
|
||||
#' }
|
||||
#' \code{clientData} also contains information about each output.
|
||||
@@ -374,12 +375,24 @@ NULL
|
||||
#' @seealso \url{http://shiny.rstudio.com/articles/modules.html}
|
||||
#' @export
|
||||
NS <- function(namespace, id = NULL) {
|
||||
if (length(namespace) == 0)
|
||||
ns_prefix <- character(0)
|
||||
else
|
||||
ns_prefix <- paste(namespace, collapse = ns.sep)
|
||||
|
||||
f <- function(id) {
|
||||
if (length(id) == 0)
|
||||
return(ns_prefix)
|
||||
if (length(ns_prefix) == 0)
|
||||
return(id)
|
||||
|
||||
paste(ns_prefix, id, sep = ns.sep)
|
||||
}
|
||||
|
||||
if (missing(id)) {
|
||||
function(id) {
|
||||
paste(c(namespace, id), collapse = ns.sep)
|
||||
}
|
||||
f
|
||||
} else {
|
||||
paste(c(namespace, id), collapse = ns.sep)
|
||||
f(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,6 +428,7 @@ ShinySession <- R6Class(
|
||||
restoreCallbacks = 'Callbacks',
|
||||
restoredCallbacks = 'Callbacks',
|
||||
bookmarkExclude = character(0), # Names of inputs to exclude from bookmarking
|
||||
getBookmarkExcludeFuns = list(),
|
||||
|
||||
testMode = FALSE, # Are we running in test mode?
|
||||
testExportExprs = list(),
|
||||
@@ -579,6 +593,16 @@ ShinySession <- R6Class(
|
||||
}) # withReactiveDomain
|
||||
},
|
||||
|
||||
# Modules (scopes) call this to register a function that returns a vector
|
||||
# of names to exclude from bookmarking. The function should return
|
||||
# something like c("scope1-x", "scope1-y"). This doesn't use a Callback
|
||||
# object because the return values of the functions are needed, but
|
||||
# Callback$invoke() discards return values.
|
||||
registerBookmarkExclude = function(fun) {
|
||||
len <- length(private$getBookmarkExcludeFuns) + 1
|
||||
private$getBookmarkExcludeFuns[[len]] <- fun
|
||||
},
|
||||
|
||||
# Save output values and errors. This is only used for testing mode.
|
||||
storeOutputValues = function(values = NULL) {
|
||||
private$outputValues <- mergeVectors(private$outputValues, values)
|
||||
@@ -628,6 +652,12 @@ ShinySession <- R6Class(
|
||||
values$output <- private$outputValues[items]
|
||||
}
|
||||
|
||||
# Filter out those outputs that have the snapshotExclude attribute.
|
||||
exclude_idx <- vapply(names(values$output), function(name) {
|
||||
isTRUE(attr(private$.outputs[[name]], "snapshotExclude", TRUE))
|
||||
}, logical(1))
|
||||
values$output <- values$output[!exclude_idx]
|
||||
|
||||
values$output <- sortByName(values$output)
|
||||
}
|
||||
|
||||
@@ -757,7 +787,8 @@ ShinySession <- R6Class(
|
||||
private$sendMessage(
|
||||
config = list(
|
||||
workerId = workerId(),
|
||||
sessionId = self$token
|
||||
sessionId = self$token,
|
||||
user = self$user
|
||||
)
|
||||
)
|
||||
},
|
||||
@@ -825,7 +856,7 @@ ShinySession <- R6Class(
|
||||
if (anyUnnamed(dots))
|
||||
stop("exportTestValues: all arguments must be named.")
|
||||
|
||||
names(dots) <- vapply(names(dots), ns, character(1))
|
||||
names(dots) <- ns(names(dots))
|
||||
|
||||
do.call(
|
||||
.subset2(self, "exportTestValues"),
|
||||
@@ -939,6 +970,12 @@ ShinySession <- R6Class(
|
||||
restoredCallbacks$invoke(scopeState)
|
||||
})
|
||||
|
||||
# Returns the excluded names with the scope's ns prefix on them.
|
||||
private$registerBookmarkExclude(function() {
|
||||
excluded <- scope$getBookmarkExclude()
|
||||
ns(excluded)
|
||||
})
|
||||
|
||||
scope
|
||||
},
|
||||
ns = function(id) {
|
||||
@@ -1022,6 +1059,10 @@ ShinySession <- R6Class(
|
||||
}
|
||||
|
||||
if (is.function(func)) {
|
||||
# Extract any output attributes attached to the render function. These
|
||||
# will be attached to the observer after it's created.
|
||||
outputAttrs <- attr(func, "outputAttrs", TRUE)
|
||||
|
||||
funcFormals <- formals(func)
|
||||
# ..stacktraceon matches with the top-level ..stacktraceoff.., because
|
||||
# the observer we set up below has ..stacktraceon=FALSE
|
||||
@@ -1099,6 +1140,12 @@ ShinySession <- R6Class(
|
||||
private$invalidatedOutputValues$set(name, value)
|
||||
}, suspended=private$shouldSuspend(name), label=label)
|
||||
|
||||
# If any output attributes were added to the render function attach
|
||||
# them to observer.
|
||||
lapply(names(outputAttrs), function(name) {
|
||||
attr(obs, name) <- outputAttrs[[name]]
|
||||
})
|
||||
|
||||
obs$onInvalidate(function() {
|
||||
self$showProgress(name)
|
||||
})
|
||||
@@ -1260,8 +1307,12 @@ ShinySession <- R6Class(
|
||||
private$bookmarkExclude <- names
|
||||
},
|
||||
getBookmarkExclude = function() {
|
||||
private$bookmarkExclude
|
||||
scopedExcludes <- lapply(private$getBookmarkExcludeFuns, function(f) f())
|
||||
scopedExcludes <- unlist(scopedExcludes)
|
||||
|
||||
c(private$bookmarkExclude, scopedExcludes)
|
||||
},
|
||||
|
||||
onBookmark = function(fun) {
|
||||
if (!is.function(fun) || length(fun) != 1) {
|
||||
stop("`fun` must be a function that takes one argument")
|
||||
@@ -1402,8 +1453,9 @@ ShinySession <- R6Class(
|
||||
)
|
||||
)
|
||||
},
|
||||
updateQueryString = function(queryString) {
|
||||
private$sendMessage(updateQueryString = list(queryString = queryString))
|
||||
updateQueryString = function(queryString, mode) {
|
||||
private$sendMessage(updateQueryString = list(
|
||||
queryString = queryString, mode = mode))
|
||||
},
|
||||
resetBrush = function(brushId) {
|
||||
private$sendMessage(
|
||||
@@ -1810,6 +1862,16 @@ outputOptions <- function(x, name, ...) {
|
||||
}
|
||||
|
||||
|
||||
#' Mark an output to be excluded from test snapshots
|
||||
#'
|
||||
#' @param x A reactive which will be assigned to an output.
|
||||
#'
|
||||
#' @export
|
||||
snapshotExclude <- function(x) {
|
||||
markOutputAttrs(x, snapshotExclude = TRUE)
|
||||
}
|
||||
|
||||
|
||||
#' Add callbacks for Shiny session events
|
||||
#'
|
||||
#' These functions are for registering callbacks on Shiny session events.
|
||||
|
||||
@@ -14,7 +14,7 @@ NULL
|
||||
#' # now we can just write "static" content without withMathJax()
|
||||
#' div("more math here $$\\sqrt{2}$$")
|
||||
withMathJax <- function(...) {
|
||||
path <- 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
|
||||
path <- 'https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
|
||||
tagList(
|
||||
tags$head(
|
||||
singleton(tags$script(src = path, type = 'text/javascript'))
|
||||
@@ -45,7 +45,6 @@ renderPage <- function(ui, connection, showcase=0, testMode=FALSE) {
|
||||
shiny_deps <- list(
|
||||
htmlDependency("json2", "2014.02.04", c(href="shared"), script = "json2-min.js"),
|
||||
htmlDependency("jquery", "1.12.4", c(href="shared"), script = "jquery.min.js"),
|
||||
htmlDependency("babel-polyfill", "6.7.2", c(href="shared"), script = "babel-polyfill.min.js"),
|
||||
htmlDependency("shiny", utils::packageVersion("shiny"), c(href="shared"),
|
||||
script = if (getOption("shiny.minified", TRUE)) "shiny.min.js" else "shiny.js",
|
||||
stylesheet = "shiny.css")
|
||||
|
||||
@@ -88,6 +88,26 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
useRenderFunction(x, inline = inline)
|
||||
}
|
||||
|
||||
|
||||
#' Mark a render function with attributes that will be used by the output
|
||||
#'
|
||||
#' @inheritParams markRenderFunction
|
||||
#' @param snapshotExclude If TRUE, exclude the output from test snapshots.
|
||||
#'
|
||||
#' @keywords internal
|
||||
markOutputAttrs <- function(renderFunc, snapshotExclude = NULL) {
|
||||
# Add the outputAttrs attribute if necessary
|
||||
if (is.null(attr(renderFunc, "outputAttrs", TRUE))) {
|
||||
attr(renderFunc, "outputAttrs") <- list()
|
||||
}
|
||||
|
||||
if (!is.null(snapshotExclude)) {
|
||||
attr(renderFunc, "outputAttrs")$snapshotExclude <- snapshotExclude
|
||||
}
|
||||
|
||||
renderFunc
|
||||
}
|
||||
|
||||
#' Image file output
|
||||
#'
|
||||
#' Renders a reactive image that is suitable for assigning to an \code{output}
|
||||
@@ -410,7 +430,9 @@ downloadHandler <- function(filename, content, contentType=NA, outputArgs=list()
|
||||
renderFunc <- function(shinysession, name, ...) {
|
||||
shinysession$registerDownload(name, filename, contentType, content)
|
||||
}
|
||||
markRenderFunction(downloadButton, renderFunc, outputArgs = outputArgs)
|
||||
snapshotExclude(
|
||||
markRenderFunction(downloadButton, renderFunc, outputArgs = outputArgs)
|
||||
)
|
||||
}
|
||||
|
||||
#' Table output with the JavaScript library DataTables
|
||||
|
||||
@@ -18,7 +18,8 @@ licenseLink <- function(licenseName) {
|
||||
"Artistic-2.0" = "http://www.r-project.org/Licenses/Artistic-2.0",
|
||||
"BSD_2_clause" = "http://www.r-project.org/Licenses/BSD_2_clause",
|
||||
"BSD_3_clause" = "http://www.r-project.org/Licenses/BSD_3_clause",
|
||||
"MIT" = "http://www.r-project.org/Licenses/MIT")
|
||||
"MIT" = "http://www.r-project.org/Licenses/MIT",
|
||||
"CC-BY-SA-4.0" = "https://www.r-project.org/Licenses/CC-BY-SA-4.0")
|
||||
if (exists(licenseName, where = licenses)) {
|
||||
tags$a(href=licenses[[licenseName]], licenseName)
|
||||
} else {
|
||||
|
||||
@@ -452,16 +452,18 @@ updateSliderInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
|
||||
|
||||
updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, inline = FALSE,
|
||||
type = 'checkbox') {
|
||||
if (!is.null(choices))
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, session$ns(inputId))
|
||||
selected = NULL, inline = FALSE, type = NULL,
|
||||
choiceNames = NULL, choiceValues = NULL) {
|
||||
if (is.null(type)) stop("Please specify the type ('checkbox' or 'radio')")
|
||||
|
||||
options <- if (!is.null(choices)) {
|
||||
args <- normalizeChoicesArgs(choices, choiceNames, choiceValues, mustExist = FALSE)
|
||||
|
||||
if (!is.null(selected)) selected <- as.character(selected)
|
||||
|
||||
options <- if (!is.null(args$choiceValues)) {
|
||||
format(tagList(
|
||||
generateOptions(session$ns(inputId), choices, selected, inline, type = type)
|
||||
generateOptions(session$ns(inputId), selected, inline, type,
|
||||
args$choiceNames, args$choiceValues)
|
||||
))
|
||||
}
|
||||
|
||||
@@ -510,9 +512,10 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
choices = NULL, selected = NULL,
|
||||
inline = FALSE) {
|
||||
updateInputOptions(session, inputId, label, choices, selected, inline)
|
||||
choices = NULL, selected = NULL, inline = FALSE,
|
||||
choiceNames = NULL, choiceValues = NULL) {
|
||||
updateInputOptions(session, inputId, label, choices, selected,
|
||||
inline, "checkbox", choiceNames, choiceValues)
|
||||
}
|
||||
|
||||
|
||||
@@ -552,10 +555,15 @@ updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
#' }
|
||||
#' @export
|
||||
updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, inline = FALSE) {
|
||||
selected = NULL, inline = FALSE,
|
||||
choiceNames = NULL, choiceValues = NULL) {
|
||||
# you must select at least one radio button
|
||||
if (is.null(selected) && !is.null(choices)) selected <- choices[[1]]
|
||||
updateInputOptions(session, inputId, label, choices, selected, inline, type = 'radio')
|
||||
if (is.null(selected)) {
|
||||
if (!is.null(choices)) selected <- choices[[1]]
|
||||
else if (!is.null(choiceValues)) selected <- choiceValues[[1]]
|
||||
}
|
||||
updateInputOptions(session, inputId, label, choices, selected,
|
||||
inline, 'radio', choiceNames, choiceValues)
|
||||
}
|
||||
|
||||
|
||||
@@ -601,8 +609,7 @@ updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
|
||||
updateSelectInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL) {
|
||||
choices <- if (!is.null(choices)) choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
if (!is.null(selected)) selected <- as.character(selected)
|
||||
options <- if (!is.null(choices)) selectOptions(choices, selected)
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
session$sendInputMessage(inputId, message)
|
||||
|
||||
@@ -14,9 +14,9 @@ For an introduction and examples, visit the [Shiny Dev Center](http://shiny.rstu
|
||||
* Shiny applications are automatically "live" in the same way that spreadsheets are live. Outputs change instantly as users modify inputs, without requiring a reload of the browser.
|
||||
* Shiny user interfaces can be built entirely using R, or can be written directly in HTML, CSS, and JavaScript for more flexibility.
|
||||
* Works in any R environment (Console R, Rgui for Windows or Mac, ESS, StatET, RStudio, etc.).
|
||||
* Attractive default UI theme based on [Bootstrap](http://getbootstrap.com/2.3.2/).
|
||||
* Attractive default UI theme based on [Bootstrap](http://getbootstrap.com/).
|
||||
* A highly customizable slider widget with built-in support for animation.
|
||||
* Pre-built output widgets for displaying plots, tables, and printed output of R objects.
|
||||
* Prebuilt output widgets for displaying plots, tables, and printed output of R objects.
|
||||
* Fast bidirectional communication between the web browser and R using the [httpuv](https://github.com/rstudio/httpuv) package.
|
||||
* Uses a [reactive](http://en.wikipedia.org/wiki/Reactive_programming) programming model that eliminates messy event handling code, so you can focus on the code that really matters.
|
||||
* Develop and redistribute your own Shiny widgets that other developers can easily drop into their own applications (coming soon!).
|
||||
|
||||
@@ -17,7 +17,7 @@ fluidPage(
|
||||
),
|
||||
|
||||
# Show a summary of the dataset and an HTML table with the
|
||||
# requested number of observations
|
||||
# requested number of observations
|
||||
mainPanel(
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ function(input, output) {
|
||||
#
|
||||
# 1) It is only called when the inputs it depends on changes
|
||||
# 2) The computation and result are shared by all the callers
|
||||
# (it only executes a single time)
|
||||
# (it only executes a single time)
|
||||
#
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
|
||||
@@ -22,7 +22,7 @@ fluidPage(
|
||||
|
||||
|
||||
# Show the caption, a summary of the dataset and an HTML
|
||||
# table with the requested number of observations
|
||||
# table with the requested number of observations
|
||||
mainPanel(
|
||||
h3(textOutput("caption", container = span)),
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ fluidPage(
|
||||
checkboxInput("outliers", "Show outliers", FALSE)
|
||||
),
|
||||
|
||||
# Show the caption and plot of the requested variable against
|
||||
# mpg
|
||||
# Show the caption and plot of the requested variable against
|
||||
# mpg
|
||||
mainPanel(
|
||||
h3(textOutput("caption")),
|
||||
|
||||
|
||||
@@ -23,16 +23,16 @@ fluidPage(
|
||||
min = 1, max = 1000, value = c(200,500)),
|
||||
|
||||
# Provide a custom currency format for value display,
|
||||
# with basic animation
|
||||
# with basic animation
|
||||
sliderInput("format", "Custom Format:",
|
||||
min = 0, max = 10000, value = 0, step = 2500,
|
||||
pre = "$", sep = ",", animate=TRUE),
|
||||
|
||||
# Animation with custom interval (in ms) to control speed,
|
||||
# plus looping
|
||||
# plus looping
|
||||
sliderInput("animation", "Looping Animation:", 1, 2000, 1,
|
||||
step = 10, animate=
|
||||
animationOptions(interval=300, loop=TRUE))
|
||||
step = 10, animate =
|
||||
animationOptions(interval=300, loop=TRUE))
|
||||
),
|
||||
|
||||
# Show a table summarizing the values entered
|
||||
|
||||
@@ -14,7 +14,7 @@ function(input, output) {
|
||||
if (is.null(inFile))
|
||||
return(NULL)
|
||||
|
||||
read.csv(inFile$datapath, header=input$header, sep=input$sep,
|
||||
quote=input$quote)
|
||||
read.csv(inFile$datapath, header=input$header, sep=input$sep,
|
||||
quote=input$quote)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ fluidPage(
|
||||
sidebarPanel(
|
||||
fileInput('file1', 'Choose CSV File',
|
||||
accept=c('text/csv',
|
||||
'text/comma-separated-values,text/plain',
|
||||
'.csv')),
|
||||
'text/comma-separated-values,text/plain',
|
||||
'.csv')),
|
||||
tags$hr(),
|
||||
checkboxInput('header', 'Header', TRUE),
|
||||
radioButtons('sep', 'Separator',
|
||||
|
||||
@@ -12,8 +12,8 @@ function(input, output) {
|
||||
|
||||
output$downloadData <- downloadHandler(
|
||||
filename = function() {
|
||||
paste(input$dataset, '.csv', sep='')
|
||||
},
|
||||
paste(input$dataset, '.csv', sep='')
|
||||
},
|
||||
content = function(file) {
|
||||
write.csv(datasetInput(), file)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ sd_section("UI Inputs",
|
||||
"updateTabsetPanel",
|
||||
"updateTextInput",
|
||||
"updateTextAreaInput",
|
||||
"updateQueryString"
|
||||
"updateQueryString",
|
||||
"getQueryString"
|
||||
)
|
||||
)
|
||||
sd_section("UI Outputs",
|
||||
@@ -121,6 +122,7 @@ sd_section("Reactive programming",
|
||||
"reactive",
|
||||
"observe",
|
||||
"observeEvent",
|
||||
"reactiveVal",
|
||||
"reactiveValues",
|
||||
"reactiveValuesToList",
|
||||
"is.reactivevalues",
|
||||
@@ -191,6 +193,8 @@ sd_section("Utility functions",
|
||||
"parseQueryString",
|
||||
"plotPNG",
|
||||
"exportTestValues",
|
||||
"snapshotExclude",
|
||||
"markOutputAttrs",
|
||||
"repeatable",
|
||||
"shinyDeprecated",
|
||||
"serverInfo",
|
||||
|
||||
3
inst/www/shared/babel-polyfill.min.js
vendored
3
inst/www/shared/babel-polyfill.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -141,6 +141,7 @@
|
||||
line-height: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
overflow: hidden;
|
||||
outline: none !important;
|
||||
z-index: -9999 !important;
|
||||
background: none !important;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Ion.RangeSlider
|
||||
// version 2.1.2 Build: 350
|
||||
// © Denis Ineshin, 2015
|
||||
// Ion.RangeSlider
|
||||
// version 2.1.6 Build: 369
|
||||
// © Denis Ineshin, 2016
|
||||
// https://github.com/IonDen
|
||||
//
|
||||
// Project page: http://ionden.com/a/plugins/ion.rangeSlider/en.html
|
||||
@@ -10,7 +10,17 @@
|
||||
// http://ionden.com/a/plugins/licence-en.html
|
||||
// =====================================================================================================================
|
||||
|
||||
;(function ($, document, window, navigator, undefined) {
|
||||
;(function(factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define(["jquery"], function (jQuery) {
|
||||
return factory(jQuery, document, window, navigator);
|
||||
});
|
||||
} else if (typeof exports === "object") {
|
||||
factory(require("jquery"), document, window, navigator);
|
||||
} else {
|
||||
factory(jQuery, document, window, navigator);
|
||||
}
|
||||
} (function ($, document, window, navigator, undefined) {
|
||||
"use strict";
|
||||
|
||||
// =================================================================================================================
|
||||
@@ -146,7 +156,7 @@
|
||||
* @constructor
|
||||
*/
|
||||
var IonRangeSlider = function (input, options, plugin_count) {
|
||||
this.VERSION = "2.1.2";
|
||||
this.VERSION = "2.1.6";
|
||||
this.input = input;
|
||||
this.plugin_count = plugin_count;
|
||||
this.current_plugin = 0;
|
||||
@@ -161,12 +171,15 @@
|
||||
this.no_diapason = false;
|
||||
this.is_key = false;
|
||||
this.is_update = false;
|
||||
this.is_first_update = true;
|
||||
this.is_start = true;
|
||||
this.is_finish = false;
|
||||
this.is_active = false;
|
||||
this.is_resize = false;
|
||||
this.is_click = false;
|
||||
|
||||
options = options || {};
|
||||
|
||||
// cache for links to all DOM elements
|
||||
this.$cache = {
|
||||
win: $(window),
|
||||
@@ -318,6 +331,11 @@
|
||||
};
|
||||
|
||||
|
||||
// check if base element is input
|
||||
if ($inp[0].nodeName !== "INPUT") {
|
||||
console && console.warn && console.warn("Base element should be <input>!", $inp[0]);
|
||||
}
|
||||
|
||||
|
||||
// config from data-attributes extends js config
|
||||
config_from_data = {
|
||||
@@ -375,16 +393,15 @@
|
||||
|
||||
for (prop in config_from_data) {
|
||||
if (config_from_data.hasOwnProperty(prop)) {
|
||||
if (!config_from_data[prop] && config_from_data[prop] !== 0) {
|
||||
if (config_from_data[prop] === undefined || config_from_data[prop] === "") {
|
||||
delete config_from_data[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// input value extends default config
|
||||
if (val) {
|
||||
if (val !== undefined && val !== "") {
|
||||
val = val.split(config_from_data.input_values_separator || options.input_values_separator || ";");
|
||||
|
||||
if (val[0] && val[0] == +val[0]) {
|
||||
@@ -416,6 +433,7 @@
|
||||
|
||||
|
||||
// validate config, to be sure that all data types are correct
|
||||
this.update_check = {};
|
||||
this.validate();
|
||||
|
||||
|
||||
@@ -447,7 +465,7 @@
|
||||
/**
|
||||
* Starts or updates the plugin instance
|
||||
*
|
||||
* @param is_update {boolean}
|
||||
* @param [is_update] {boolean}
|
||||
*/
|
||||
init: function (is_update) {
|
||||
this.no_diapason = false;
|
||||
@@ -734,7 +752,6 @@
|
||||
|
||||
// callbacks call
|
||||
if ($.contains(this.$cache.cont[0], e.target) || this.dragging) {
|
||||
this.is_finish = true;
|
||||
this.callOnFinish();
|
||||
}
|
||||
|
||||
@@ -761,7 +778,7 @@
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
target = this.target;
|
||||
target = this.target || "from";
|
||||
}
|
||||
|
||||
this.current_plugin = this.plugin_count;
|
||||
@@ -948,6 +965,12 @@
|
||||
this.calcPointerPercent();
|
||||
var handle_x = this.getHandleX();
|
||||
|
||||
|
||||
if (this.target === "both") {
|
||||
this.coords.p_gap = 0;
|
||||
handle_x = this.getHandleX();
|
||||
}
|
||||
|
||||
if (this.target === "click") {
|
||||
this.coords.p_gap = this.coords.p_handle / 2;
|
||||
handle_x = this.getHandleX();
|
||||
@@ -1035,7 +1058,7 @@
|
||||
break;
|
||||
}
|
||||
|
||||
handle_x = this.toFixed(handle_x + (this.coords.p_handle * 0.1));
|
||||
handle_x = this.toFixed(handle_x + (this.coords.p_handle * 0.001));
|
||||
|
||||
this.coords.p_from_real = this.convertToRealPercent(handle_x) - this.coords.p_gap_left;
|
||||
this.coords.p_from_real = this.calcWithStep(this.coords.p_from_real);
|
||||
@@ -1313,13 +1336,6 @@
|
||||
this.$cache.s_single[0].style.left = this.coords.p_single_fake + "%";
|
||||
|
||||
this.$cache.single[0].style.left = this.labels.p_single_left + "%";
|
||||
|
||||
if (this.options.values.length) {
|
||||
this.$cache.input.prop("value", this.result.from_value);
|
||||
} else {
|
||||
this.$cache.input.prop("value", this.result.from);
|
||||
}
|
||||
this.$cache.input.data("from", this.result.from);
|
||||
} else {
|
||||
this.$cache.s_from[0].style.left = this.coords.p_from_fake + "%";
|
||||
this.$cache.s_to[0].style.left = this.coords.p_to_fake + "%";
|
||||
@@ -1332,18 +1348,13 @@
|
||||
}
|
||||
|
||||
this.$cache.single[0].style.left = this.labels.p_single_left + "%";
|
||||
|
||||
if (this.options.values.length) {
|
||||
this.$cache.input.prop("value", this.result.from_value + this.options.input_values_separator + this.result.to_value);
|
||||
} else {
|
||||
this.$cache.input.prop("value", this.result.from + this.options.input_values_separator + this.result.to);
|
||||
}
|
||||
this.$cache.input.data("from", this.result.from);
|
||||
this.$cache.input.data("to", this.result.to);
|
||||
}
|
||||
|
||||
this.writeToInput();
|
||||
|
||||
if ((this.old_from !== this.result.from || this.old_to !== this.result.to) && !this.is_start) {
|
||||
this.$cache.input.trigger("change");
|
||||
this.$cache.input.trigger("input");
|
||||
}
|
||||
|
||||
this.old_from = this.result.from;
|
||||
@@ -1353,9 +1364,10 @@
|
||||
if (!this.is_resize && !this.is_update && !this.is_start && !this.is_finish) {
|
||||
this.callOnChange();
|
||||
}
|
||||
if (this.is_key || this.is_click) {
|
||||
if (this.is_key || this.is_click || this.is_first_update) {
|
||||
this.is_key = false;
|
||||
this.is_click = false;
|
||||
this.is_first_update = false;
|
||||
this.callOnFinish();
|
||||
}
|
||||
|
||||
@@ -1467,6 +1479,8 @@
|
||||
this.$cache.from[0].style.visibility = "visible";
|
||||
} else if (this.target === "to") {
|
||||
this.$cache.to[0].style.visibility = "visible";
|
||||
} else if (!this.target) {
|
||||
this.$cache.from[0].style.visibility = "visible";
|
||||
}
|
||||
this.$cache.single[0].style.visibility = "hidden";
|
||||
max = to_left;
|
||||
@@ -1561,25 +1575,57 @@
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Write values to input element
|
||||
*/
|
||||
writeToInput: function () {
|
||||
if (this.options.type === "single") {
|
||||
if (this.options.values.length) {
|
||||
this.$cache.input.prop("value", this.result.from_value);
|
||||
} else {
|
||||
this.$cache.input.prop("value", this.result.from);
|
||||
}
|
||||
this.$cache.input.data("from", this.result.from);
|
||||
} else {
|
||||
if (this.options.values.length) {
|
||||
this.$cache.input.prop("value", this.result.from_value + this.options.input_values_separator + this.result.to_value);
|
||||
} else {
|
||||
this.$cache.input.prop("value", this.result.from + this.options.input_values_separator + this.result.to);
|
||||
}
|
||||
this.$cache.input.data("from", this.result.from);
|
||||
this.$cache.input.data("to", this.result.to);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
// =============================================================================================================
|
||||
// Callbacks
|
||||
|
||||
callOnStart: function () {
|
||||
this.writeToInput();
|
||||
|
||||
if (this.options.onStart && typeof this.options.onStart === "function") {
|
||||
this.options.onStart(this.result);
|
||||
}
|
||||
},
|
||||
callOnChange: function () {
|
||||
this.writeToInput();
|
||||
|
||||
if (this.options.onChange && typeof this.options.onChange === "function") {
|
||||
this.options.onChange(this.result);
|
||||
}
|
||||
},
|
||||
callOnFinish: function () {
|
||||
this.writeToInput();
|
||||
|
||||
if (this.options.onFinish && typeof this.options.onFinish === "function") {
|
||||
this.options.onFinish(this.result);
|
||||
}
|
||||
},
|
||||
callOnUpdate: function () {
|
||||
this.writeToInput();
|
||||
|
||||
if (this.options.onUpdate && typeof this.options.onUpdate === "function") {
|
||||
this.options.onUpdate(this.result);
|
||||
}
|
||||
@@ -1587,6 +1633,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
// =============================================================================================================
|
||||
// Service methods
|
||||
|
||||
@@ -1796,7 +1843,7 @@
|
||||
},
|
||||
|
||||
toFixed: function (num) {
|
||||
num = num.toFixed(9);
|
||||
num = num.toFixed(20);
|
||||
return +num;
|
||||
},
|
||||
|
||||
@@ -1884,32 +1931,37 @@
|
||||
o.from = o.min;
|
||||
}
|
||||
|
||||
if (typeof o.to !== "number" || isNaN(o.from)) {
|
||||
if (typeof o.to !== "number" || isNaN(o.to)) {
|
||||
o.to = o.max;
|
||||
}
|
||||
|
||||
if (o.type === "single") {
|
||||
|
||||
if (o.from < o.min) {
|
||||
o.from = o.min;
|
||||
}
|
||||
|
||||
if (o.from > o.max) {
|
||||
o.from = o.max;
|
||||
}
|
||||
if (o.from < o.min) o.from = o.min;
|
||||
if (o.from > o.max) o.from = o.max;
|
||||
|
||||
} else {
|
||||
|
||||
if (o.from < o.min || o.from > o.max) {
|
||||
o.from = o.min;
|
||||
}
|
||||
if (o.to > o.max || o.to < o.min) {
|
||||
o.to = o.max;
|
||||
}
|
||||
if (o.from > o.to) {
|
||||
o.from = o.to;
|
||||
if (o.from < o.min) o.from = o.min;
|
||||
if (o.from > o.max) o.from = o.max;
|
||||
|
||||
if (o.to < o.min) o.to = o.min;
|
||||
if (o.to > o.max) o.to = o.max;
|
||||
|
||||
if (this.update_check.from) {
|
||||
|
||||
if (this.update_check.from !== o.from) {
|
||||
if (o.from > o.to) o.from = o.to;
|
||||
}
|
||||
if (this.update_check.to !== o.to) {
|
||||
if (o.to < o.from) o.to = o.from;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (o.from > o.to) o.from = o.to;
|
||||
if (o.to < o.from) o.to = o.from;
|
||||
|
||||
}
|
||||
|
||||
if (typeof o.step !== "number" || isNaN(o.step) || !o.step || o.step < 0) {
|
||||
@@ -2167,7 +2219,10 @@
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
label = this.$cache.grid_labels[i][0];
|
||||
label.style.marginLeft = -this.coords.big_x[i] + "%";
|
||||
|
||||
if (this.coords.big_x[i] !== Number.POSITIVE_INFINITY) {
|
||||
label.style.marginLeft = -this.coords.big_x[i] + "%";
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2229,6 +2284,8 @@
|
||||
|
||||
this.options.from = this.result.from;
|
||||
this.options.to = this.result.to;
|
||||
this.update_check.from = this.result.from;
|
||||
this.update_check.to = this.result.to;
|
||||
|
||||
this.options = $.extend(this.options, options);
|
||||
this.validate();
|
||||
@@ -2306,4 +2363,4 @@
|
||||
};
|
||||
}());
|
||||
|
||||
} (jQuery, document, window, navigator));
|
||||
}));
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,6 +6,7 @@
|
||||
|
||||
.shiny-code {
|
||||
background-color: white;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.shiny-code code {
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
var app = document.getElementById("showcase-app-container");
|
||||
$(app).animate({
|
||||
width: appWidth + "px",
|
||||
zoom: zoom
|
||||
zoom: (zoom*100) + "%"
|
||||
}, animate ? animateMs : 0);
|
||||
};
|
||||
|
||||
|
||||
@@ -95,6 +95,13 @@ pre.shiny-text-output.noplaceholder:empty {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/* Work around MS Edge transition bug (issue #1637) */
|
||||
@supports (-ms-ime-align:auto) {
|
||||
.shiny-bound-output {
|
||||
transition: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.recalculating {
|
||||
opacity: 0.3;
|
||||
transition: opacity 250ms ease 500ms;
|
||||
|
||||
@@ -10,6 +10,13 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
|
||||
var exports = window.Shiny = window.Shiny || {};
|
||||
|
||||
var origPushState = window.history.pushState;
|
||||
window.history.pushState = function () {
|
||||
var result = origPushState.apply(this, arguments);
|
||||
$(document).trigger("pushstate");
|
||||
return result;
|
||||
};
|
||||
|
||||
$(document).on('submit', 'form:not([action])', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
@@ -18,7 +25,18 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
// Source file: ../srcjs/utils.js
|
||||
|
||||
function escapeHTML(str) {
|
||||
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/");
|
||||
var escaped = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"/": "/"
|
||||
};
|
||||
|
||||
return str.replace(/[&<>'"\/]/g, function (m) {
|
||||
return escaped[m];
|
||||
});
|
||||
}
|
||||
|
||||
function randomId() {
|
||||
@@ -60,6 +78,18 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
}return str;
|
||||
}
|
||||
|
||||
// Round to a specified number of significant digits.
|
||||
function roundSignif(x) {
|
||||
var digits = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
|
||||
|
||||
if (digits < 1) throw "Significant digits must be at least 1.";
|
||||
|
||||
// This converts to a string and back to a number, which is inelegant, but
|
||||
// is less prone to FP rounding error than an alternate method which used
|
||||
// Math.round().
|
||||
return parseFloat(x.toPrecision(digits));
|
||||
}
|
||||
|
||||
// Take a string with format "YYYY-MM-DD" and return a Date object.
|
||||
// IE8 and QTWebKit don't support YYYY-MM-DD, but they support YYYY/MM/DD
|
||||
function parseDate(dateString) {
|
||||
@@ -200,6 +230,16 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
return val.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g, '\\$1');
|
||||
};
|
||||
|
||||
// Maps a function over an object, preserving keys. Like the mapValues
|
||||
// function from lodash.
|
||||
function mapValues(obj, f) {
|
||||
var newObj = {};
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) newObj[key] = f(obj[key]);
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Source file: ../srcjs/browser.js
|
||||
|
||||
@@ -428,6 +468,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
var self = this;
|
||||
|
||||
this.pendingData[name] = value;
|
||||
|
||||
if (!this.timerId && !this.reentrant) {
|
||||
this.timerId = setTimeout(function () {
|
||||
self.reentrant = true;
|
||||
@@ -449,55 +490,78 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
|
||||
var InputNoResendDecorator = function InputNoResendDecorator(target, initialValues) {
|
||||
this.target = target;
|
||||
this.lastSentValues = initialValues || {};
|
||||
this.lastSentValues = this.reset(initialValues);
|
||||
};
|
||||
(function () {
|
||||
this.setInput = function (name, value) {
|
||||
// Note that opts is not passed to setInput at this stage of the input
|
||||
// decorator stack. If in the future this setInput keeps track of opts, it
|
||||
// would be best not to store the `el`, because that could prevent it from
|
||||
// being GC'd.
|
||||
var _splitInputNameType = splitInputNameType(name);
|
||||
|
||||
var inputName = _splitInputNameType.name;
|
||||
var inputType = _splitInputNameType.inputType;
|
||||
|
||||
var jsonValue = JSON.stringify(value);
|
||||
if (this.lastSentValues[name] === jsonValue) return;
|
||||
this.lastSentValues[name] = jsonValue;
|
||||
|
||||
if (this.lastSentValues[inputName] && this.lastSentValues[inputName].jsonValue === jsonValue && this.lastSentValues[inputName].inputType === inputType) {
|
||||
return;
|
||||
}
|
||||
this.lastSentValues[inputName] = { jsonValue: jsonValue, inputType: inputType };
|
||||
this.target.setInput(name, value);
|
||||
};
|
||||
this.reset = function (values) {
|
||||
values = values || {};
|
||||
var strValues = {};
|
||||
$.each(values, function (key, value) {
|
||||
strValues[key] = JSON.stringify(value);
|
||||
});
|
||||
this.lastSentValues = strValues;
|
||||
this.reset = function () {
|
||||
var values = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
||||
|
||||
// Given an object with flat name-value format:
|
||||
// { x: "abc", "y.shiny.number": 123 }
|
||||
// Create an object in cache format and save it:
|
||||
// { x: { jsonValue: '"abc"', inputType: "" },
|
||||
// y: { jsonValue: "123", inputType: "shiny.number" } }
|
||||
var cacheValues = {};
|
||||
|
||||
for (var inputName in values) {
|
||||
if (values.hasOwnProperty(inputName)) {
|
||||
var _splitInputNameType2 = splitInputNameType(inputName);
|
||||
|
||||
var name = _splitInputNameType2.name;
|
||||
var inputType = _splitInputNameType2.inputType;
|
||||
|
||||
cacheValues[name] = {
|
||||
jsonValue: JSON.stringify(values[inputName]),
|
||||
inputType: inputType
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.lastSentValues = cacheValues;
|
||||
};
|
||||
}).call(InputNoResendDecorator.prototype);
|
||||
|
||||
var InputDeferDecorator = function InputDeferDecorator(target) {
|
||||
this.target = target;
|
||||
this.pendingInput = {};
|
||||
};
|
||||
(function () {
|
||||
this.setInput = function (name, value) {
|
||||
if (/^\./.test(name)) this.target.setInput(name, value);else this.pendingInput[name] = value;
|
||||
};
|
||||
this.submit = function () {
|
||||
for (var name in this.pendingInput) {
|
||||
if (this.pendingInput.hasOwnProperty(name)) this.target.setInput(name, this.pendingInput[name]);
|
||||
}
|
||||
};
|
||||
}).call(InputDeferDecorator.prototype);
|
||||
|
||||
var InputEventDecorator = function InputEventDecorator(target) {
|
||||
this.target = target;
|
||||
};
|
||||
(function () {
|
||||
this.setInput = function (name, value, immediate) {
|
||||
this.setInput = function (name, value, opts) {
|
||||
var evt = jQuery.Event("shiny:inputchanged");
|
||||
var name2 = name.split(':');
|
||||
evt.name = name2[0];
|
||||
evt.inputType = name2.length > 1 ? name2[1] : '';
|
||||
|
||||
var input = splitInputNameType(name);
|
||||
evt.name = input.name;
|
||||
evt.inputType = input.inputType;
|
||||
evt.value = value;
|
||||
evt.binding = opts.binding;
|
||||
evt.el = opts.el;
|
||||
|
||||
$(document).trigger(evt);
|
||||
|
||||
if (!evt.isDefaultPrevented()) {
|
||||
name = evt.name;
|
||||
if (evt.inputType !== '') name += ':' + evt.inputType;
|
||||
this.target.setInput(name, evt.value, immediate);
|
||||
|
||||
// opts aren't passed along to lower levels in the input decorator
|
||||
// stack.
|
||||
this.target.setInput(name, evt.value);
|
||||
}
|
||||
};
|
||||
}).call(InputEventDecorator.prototype);
|
||||
@@ -507,9 +571,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
this.inputRatePolicies = {};
|
||||
};
|
||||
(function () {
|
||||
this.setInput = function (name, value, immediate) {
|
||||
this.setInput = function (name, value, opts) {
|
||||
this.$ensureInit(name);
|
||||
if (immediate) this.inputRatePolicies[name].immediateCall(name, value, immediate);else this.inputRatePolicies[name].normalCall(name, value, immediate);
|
||||
|
||||
if (opts.immediate) this.inputRatePolicies[name].immediateCall(name, value, opts);else this.inputRatePolicies[name].normalCall(name, value, opts);
|
||||
};
|
||||
this.setRatePolicy = function (name, mode, millis) {
|
||||
if (mode === 'direct') {
|
||||
@@ -523,11 +588,59 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
this.$ensureInit = function (name) {
|
||||
if (!(name in this.inputRatePolicies)) this.setRatePolicy(name, 'direct');
|
||||
};
|
||||
this.$doSetInput = function (name, value) {
|
||||
this.target.setInput(name, value);
|
||||
this.$doSetInput = function (name, value, opts) {
|
||||
this.target.setInput(name, value, opts);
|
||||
};
|
||||
}).call(InputRateDecorator.prototype);
|
||||
|
||||
var InputDeferDecorator = function InputDeferDecorator(target) {
|
||||
this.target = target;
|
||||
this.pendingInput = {};
|
||||
};
|
||||
(function () {
|
||||
this.setInput = function (name, value, opts) {
|
||||
if (/^\./.test(name)) this.target.setInput(name, value, opts);else this.pendingInput[name] = { value: value, opts: opts };
|
||||
};
|
||||
this.submit = function () {
|
||||
for (var name in this.pendingInput) {
|
||||
if (this.pendingInput.hasOwnProperty(name)) {
|
||||
var input = this.pendingInput[name];
|
||||
this.target.setInput(name, input.value, input.opts);
|
||||
}
|
||||
}
|
||||
};
|
||||
}).call(InputDeferDecorator.prototype);
|
||||
|
||||
var InputValidateDecorator = function InputValidateDecorator(target) {
|
||||
this.target = target;
|
||||
};
|
||||
(function () {
|
||||
this.setInput = function (name, value, opts) {
|
||||
if (!name) throw "Can't set input with empty name.";
|
||||
|
||||
opts = addDefaultInputOpts(opts);
|
||||
|
||||
this.target.setInput(name, value, opts);
|
||||
};
|
||||
}).call(InputValidateDecorator.prototype);
|
||||
|
||||
// Merge opts with defaults, and return a new object.
|
||||
function addDefaultInputOpts(opts) {
|
||||
return $.extend({
|
||||
immediate: false,
|
||||
binding: null,
|
||||
el: null
|
||||
}, opts);
|
||||
}
|
||||
|
||||
function splitInputNameType(name) {
|
||||
var name2 = name.split(':');
|
||||
return {
|
||||
name: name2[0],
|
||||
inputType: name2.length > 1 ? name2[1] : ''
|
||||
};
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Source file: ../srcjs/shinyapp.js
|
||||
|
||||
@@ -537,6 +650,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
// Cached input values
|
||||
this.$inputValues = {};
|
||||
|
||||
// Input values at initialization (and reconnect)
|
||||
this.$initialInput = {};
|
||||
|
||||
// Output bindings
|
||||
this.$bindings = {};
|
||||
|
||||
@@ -559,11 +675,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
this.connect = function (initialInput) {
|
||||
if (this.$socket) throw "Connect was already called on this application object";
|
||||
|
||||
$.extend(initialInput, {
|
||||
// IE8 and IE9 have some limitations with data URIs
|
||||
".clientdata_allowDataUriScheme": typeof WebSocket !== 'undefined'
|
||||
});
|
||||
|
||||
this.$socket = this.createSocket();
|
||||
this.$initialInput = initialInput;
|
||||
$.extend(this.$inputValues, initialInput);
|
||||
@@ -1128,7 +1239,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
});
|
||||
|
||||
addMessageHandler('config', function (message) {
|
||||
this.config = message;
|
||||
this.config = { workerId: message.workerId, sessionId: message.sessionId };
|
||||
if (message.user) exports.user = message.user;
|
||||
$(document).trigger('shiny:sessioninitialized');
|
||||
});
|
||||
|
||||
addMessageHandler('busy', function (message) {
|
||||
@@ -1183,7 +1296,46 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
});
|
||||
|
||||
addMessageHandler('updateQueryString', function (message) {
|
||||
window.history.replaceState(null, null, message.queryString);
|
||||
|
||||
// leave the bookmarking code intact
|
||||
if (message.mode === "replace") {
|
||||
window.history.replaceState(null, null, message.queryString);
|
||||
return;
|
||||
}
|
||||
|
||||
var what = null;
|
||||
if (message.queryString.charAt(0) === "#") what = "hash";else if (message.queryString.charAt(0) === "?") what = "query";else throw "The 'query' string must start with either '?' " + "(to update the query string) or with '#' (to " + "update the hash).";
|
||||
|
||||
var path = window.location.pathname;
|
||||
var oldQS = window.location.search;
|
||||
var oldHash = window.location.hash;
|
||||
|
||||
/* Barbara -- December 2016
|
||||
Note: we could check if the new QS and/or hash are different
|
||||
from the old one(s) and, if not, we could choose not to push
|
||||
a new state (whether or not we would replace it is moot/
|
||||
inconsequential). However, I think that it is better to
|
||||
interpret each call to `updateQueryString` as representing
|
||||
new state (even if the message.queryString is the same), so
|
||||
that check isn't even performed as of right now.
|
||||
*/
|
||||
|
||||
var relURL = path;
|
||||
if (what === "query") relURL += message.queryString;else relURL += oldQS + message.queryString; // leave old QS if it exists
|
||||
window.history.pushState(null, null, relURL);
|
||||
|
||||
// for the case when message.queryString has both a query string
|
||||
// and a hash (`what = "hash"` allows us to trigger the
|
||||
// hashchange event)
|
||||
if (message.queryString.indexOf("#") !== -1) what = "hash";
|
||||
|
||||
// for the case when there was a hash before, but there isn't
|
||||
// any hash now (e.g. for when only the query string is updated)
|
||||
if (window.location.hash !== oldHash) what = "hash";
|
||||
|
||||
// This event needs to be triggered manually because pushState() never
|
||||
// causes a hashchange event to be fired,
|
||||
if (what === "hash") $(document).trigger("hashchange");
|
||||
});
|
||||
|
||||
addMessageHandler("resetBrush", function (message) {
|
||||
@@ -1260,13 +1412,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
if (typeof message.detail !== 'undefined') {
|
||||
$progress.find('.progress-detail').text(message.detail);
|
||||
}
|
||||
if (typeof message.value !== 'undefined') {
|
||||
if (message.value !== null) {
|
||||
$progress.find('.progress').show();
|
||||
$progress.find('.progress-bar').width(message.value * 100 + '%');
|
||||
} else {
|
||||
$progress.find('.progress').hide();
|
||||
}
|
||||
if (typeof message.value !== 'undefined' && message.value !== null) {
|
||||
$progress.find('.progress').show();
|
||||
$progress.find('.progress-bar').width(message.value * 100 + '%');
|
||||
}
|
||||
} else if (message.style === "old") {
|
||||
// For old-style (Shiny <=0.13.2) progress indicators.
|
||||
@@ -1278,13 +1426,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
if (typeof message.detail !== 'undefined') {
|
||||
$progress.find('.progress-detail').text(message.detail);
|
||||
}
|
||||
if (typeof message.value !== 'undefined') {
|
||||
if (message.value !== null) {
|
||||
$progress.find('.progress').show();
|
||||
$progress.find('.bar').width(message.value * 100 + '%');
|
||||
} else {
|
||||
$progress.find('.progress').hide();
|
||||
}
|
||||
if (typeof message.value !== 'undefined' && message.value !== null) {
|
||||
$progress.find('.progress').show();
|
||||
$progress.find('.bar').width(message.value * 100 + '%');
|
||||
}
|
||||
|
||||
$progress.fadeIn();
|
||||
@@ -2840,6 +2984,11 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
// For reversed scales, the min and max can be reversed, so use findBox
|
||||
// to ensure correct order.
|
||||
state.boundsData = coordmap.findBox(minData, maxData);
|
||||
// Round to 14 significant digits to avoid spurious changes in FP values
|
||||
// (#1634).
|
||||
state.boundsData = mapValues(state.boundsData, function (val) {
|
||||
return roundSignif(val, 14);
|
||||
});
|
||||
|
||||
// We also need to attach the data bounds and panel as data attributes, so
|
||||
// that if the image is re-sent, we can grab the data bounds to create a new
|
||||
@@ -3323,6 +3472,14 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
});
|
||||
outputBindings.register(downloadLinkOutputBinding, 'shiny.downloadLink');
|
||||
|
||||
// Trigger shiny:filedownload event whenever a downloadButton/Link is clicked
|
||||
$(document).on('click.shinyDownloadLink', 'a.shiny-download-link', function (e) {
|
||||
var evt = jQuery.Event('shiny:filedownload');
|
||||
evt.name = this.id;
|
||||
evt.href = this.href;
|
||||
$(document).trigger(evt);
|
||||
});
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Source file: ../srcjs/output_binding_datatable.js
|
||||
|
||||
@@ -4699,9 +4856,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
};
|
||||
}).call(IE8FileUploader.prototype);
|
||||
|
||||
var FileUploader = function FileUploader(shinyapp, id, files) {
|
||||
var FileUploader = function FileUploader(shinyapp, id, files, el) {
|
||||
this.shinyapp = shinyapp;
|
||||
this.id = id;
|
||||
this.el = el;
|
||||
FileProcessor.call(this, files);
|
||||
};
|
||||
$.extend(FileUploader.prototype, FileProcessor.prototype);
|
||||
@@ -4771,6 +4929,26 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
};
|
||||
this.onComplete = function () {
|
||||
var self = this;
|
||||
|
||||
var fileInfo = $.map(this.files, function (file, i) {
|
||||
return {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type
|
||||
};
|
||||
});
|
||||
|
||||
// Trigger shiny:inputchanged. Unlike a normal shiny:inputchanged event,
|
||||
// it's not possible to modify the information before the values get
|
||||
// sent to the server.
|
||||
var evt = jQuery.Event("shiny:inputchanged");
|
||||
evt.name = this.id;
|
||||
evt.value = fileInfo;
|
||||
evt.binding = fileInputBinding;
|
||||
evt.el = this.el;
|
||||
evt.inputType = 'shiny.fileupload';
|
||||
$(document).trigger(evt);
|
||||
|
||||
this.makeRequest('uploadEnd', [this.jobId, this.id], function (response) {
|
||||
self.$setActive(false);
|
||||
self.onProgress(null, 1);
|
||||
@@ -4779,18 +4957,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
self.onError(error);
|
||||
});
|
||||
this.$bar().text('Finishing upload');
|
||||
|
||||
// Trigger event when all files are finished uploading.
|
||||
var evt = jQuery.Event("shiny:fileuploaded");
|
||||
evt.name = this.id;
|
||||
evt.files = $.map(this.files, function (file, i) {
|
||||
return {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type
|
||||
};
|
||||
});
|
||||
$(document).trigger(evt);
|
||||
};
|
||||
this.onError = function (message) {
|
||||
this.$setError(message || '');
|
||||
@@ -4813,7 +4979,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
this.$container().css('visibility', visible ? 'visible' : 'hidden');
|
||||
};
|
||||
this.$setError = function (error) {
|
||||
this.$bar().toggleClass('bar-danger', error !== null);
|
||||
this.$bar().toggleClass('progress-bar-danger', error !== null);
|
||||
if (error !== null) {
|
||||
this.onProgress(null, 1);
|
||||
this.$bar().text(error);
|
||||
@@ -4857,7 +5023,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
/*jshint nonew:false */
|
||||
new IE8FileUploader(exports.shinyapp, id, evt.target);
|
||||
} else {
|
||||
$el.data('currentUploader', new FileUploader(exports.shinyapp, id, files));
|
||||
$el.data('currentUploader', new FileUploader(exports.shinyapp, id, files, evt.target));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5003,19 +5169,27 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
var inputsRate = new InputRateDecorator(inputsEvent);
|
||||
var inputsDefer = new InputDeferDecorator(inputsEvent);
|
||||
|
||||
// By default, use rate decorator
|
||||
var inputs = inputsRate;
|
||||
$('input[type="submit"], button[type="submit"]').each(function () {
|
||||
var inputs;
|
||||
if ($('input[type="submit"], button[type="submit"]').length > 0) {
|
||||
// If there is a submit button on the page, use defer decorator
|
||||
inputs = inputsDefer;
|
||||
$(this).click(function (event) {
|
||||
event.preventDefault();
|
||||
inputsDefer.submit();
|
||||
});
|
||||
});
|
||||
|
||||
exports.onInputChange = function (name, value) {
|
||||
inputs.setInput(name, value);
|
||||
$('input[type="submit"], button[type="submit"]').each(function () {
|
||||
$(this).click(function (event) {
|
||||
event.preventDefault();
|
||||
inputsDefer.submit();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// By default, use rate decorator
|
||||
inputs = inputsRate;
|
||||
}
|
||||
|
||||
inputs = new InputValidateDecorator(inputs);
|
||||
|
||||
exports.onInputChange = function (name, value, opts) {
|
||||
opts = addDefaultInputOpts(opts);
|
||||
inputs.setInput(name, value, opts);
|
||||
};
|
||||
|
||||
var boundInputs = {};
|
||||
@@ -5026,7 +5200,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
var value = binding.getValue(el);
|
||||
var type = binding.getType(el);
|
||||
if (type) id = id + ":" + type;
|
||||
inputs.setInput(id, value, !allowDeferred);
|
||||
|
||||
var opts = { immediate: !allowDeferred, binding: binding, el: el };
|
||||
inputs.setInput(id, value, opts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5035,7 +5211,7 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
|
||||
var bindings = inputBindings.getBindings();
|
||||
|
||||
var currentValues = {};
|
||||
var inputItems = {};
|
||||
|
||||
for (var i = 0; i < bindings.length; i++) {
|
||||
var binding = bindings[i].binding;
|
||||
@@ -5049,7 +5225,14 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
|
||||
var type = binding.getType(el);
|
||||
var effectiveId = type ? id + ":" + type : id;
|
||||
currentValues[effectiveId] = binding.getValue(el);
|
||||
inputItems[effectiveId] = {
|
||||
value: binding.getValue(el),
|
||||
opts: {
|
||||
immediate: true,
|
||||
binding: binding,
|
||||
el: el
|
||||
}
|
||||
};
|
||||
|
||||
/*jshint loopfunc:true*/
|
||||
var thisCallback = function () {
|
||||
@@ -5078,14 +5261,10 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
binding: binding,
|
||||
bindingType: 'input'
|
||||
});
|
||||
|
||||
if (shinyapp.isConnected()) {
|
||||
valueChangeCallback(binding, el, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentValues;
|
||||
return inputItems;
|
||||
}
|
||||
|
||||
function unbindInputs() {
|
||||
@@ -5125,12 +5304,11 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
unbindOutputs(scope, includeSelf);
|
||||
}
|
||||
exports.bindAll = function (scope) {
|
||||
// _bindAll alone returns initial values, it doesn't send them to the
|
||||
// server. export.bindAll needs to send the values to the server, so we
|
||||
// wrap _bindAll in a closure that does that.
|
||||
var currentValues = _bindAll(scope);
|
||||
$.each(currentValues, function (name, value) {
|
||||
inputs.setInput(name, value);
|
||||
// _bindAll returns input values; it doesn't send them to the server.
|
||||
// export.bindAll needs to send the values to the server.
|
||||
var currentInputItems = _bindAll(scope);
|
||||
$.each(currentInputItems, function (name, item) {
|
||||
inputs.setInput(name, item.value, item.opts);
|
||||
});
|
||||
|
||||
// Not sure if the iframe stuff is an intrinsic part of bindAll, but bindAll
|
||||
@@ -5173,7 +5351,16 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
// Initialize all input objects in the document, before binding
|
||||
initializeInputs(document);
|
||||
|
||||
var initialValues = _bindAll(document);
|
||||
// The input values returned by _bindAll() each have a structure like this:
|
||||
// { value: 123, opts: { ... } }
|
||||
// We want to only keep the value. This is because when the initialValues is
|
||||
// passed to ShinyApp.connect(), the ShinyApp object stores the
|
||||
// initialValues object for the duration of the session, and the opts may
|
||||
// have a reference to the DOM element, which would prevent it from being
|
||||
// GC'd.
|
||||
var initialValues = mapValues(_bindAll(document), function (x) {
|
||||
return x.value;
|
||||
});
|
||||
|
||||
// The server needs to know the size of each image and plot output element,
|
||||
// in case it is auto-sizing
|
||||
@@ -5327,12 +5514,28 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
initialValues['.clientdata_url_hostname'] = window.location.hostname;
|
||||
initialValues['.clientdata_url_port'] = window.location.port;
|
||||
initialValues['.clientdata_url_pathname'] = window.location.pathname;
|
||||
|
||||
// Send initial URL search (query string) and update it if it changes
|
||||
initialValues['.clientdata_url_search'] = window.location.search;
|
||||
|
||||
$(window).on('pushstate', function (e) {
|
||||
inputs.setInput('.clientdata_url_search', window.location.search);
|
||||
});
|
||||
|
||||
$(window).on('popstate', function (e) {
|
||||
inputs.setInput('.clientdata_url_search', window.location.search);
|
||||
});
|
||||
|
||||
// This is only the initial value of the hash. The hash can change, but
|
||||
// a reactive version of this isn't sent because w atching for changes can
|
||||
// a reactive version of this isn't sent because watching for changes can
|
||||
// require polling on some browsers. The JQuery hashchange plugin can be
|
||||
// used if this capability is important.
|
||||
initialValues['.clientdata_url_hash_initial'] = window.location.hash;
|
||||
initialValues['.clientdata_url_hash'] = window.location.hash;
|
||||
|
||||
$(window).on('hashchange', function (e) {
|
||||
inputs.setInput('.clientdata_url_hash', location.hash);
|
||||
});
|
||||
|
||||
// The server needs to know what singletons were rendered as part of
|
||||
// the page loading
|
||||
@@ -5347,6 +5550,9 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol
|
||||
}
|
||||
});
|
||||
|
||||
// IE8 and IE9 have some limitations with data URIs
|
||||
initialValues['.clientdata_allowDataUriScheme'] = typeof WebSocket !== 'undefined';
|
||||
|
||||
// We've collected all the initial values--start the server process!
|
||||
inputsNoResend.reset(initialValues);
|
||||
shinyapp.connect(initialValues);
|
||||
|
||||
File diff suppressed because one or more lines are too long
8
inst/www/shared/shiny.min.js
vendored
8
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -41,4 +41,3 @@ into a namespaced one, by combining them with \code{ns.sep} in between.
|
||||
\url{http://shiny.rstudio.com/articles/modules.html}
|
||||
}
|
||||
\keyword{datasets}
|
||||
|
||||
|
||||
@@ -24,8 +24,7 @@ detail message (if any). The detail message will be shown with a
|
||||
de-emphasized appearance relative to \code{message}.}
|
||||
|
||||
\item{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.}
|
||||
the progress bar, relative to \code{min} and \code{max}.}
|
||||
|
||||
\item{style}{Progress display style. If \code{"notification"} (the default),
|
||||
the progress indicator will show using Shiny's notification API. If
|
||||
@@ -112,4 +111,3 @@ shinyApp(ui, server)
|
||||
\code{\link{withProgress}}
|
||||
}
|
||||
\keyword{datasets}
|
||||
|
||||
|
||||
@@ -80,4 +80,3 @@ specify \code{0} for \code{top}, \code{left}, \code{right}, and \code{bottom}
|
||||
rather than the more obvious \code{width = "100\%"} and \code{height =
|
||||
"100\%"}.
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ shinyApp(ui, server)
|
||||
\seealso{
|
||||
\code{\link{observeEvent}} and \code{\link{eventReactive}}
|
||||
|
||||
Other input.elements: \code{\link{checkboxGroupInput}},
|
||||
Other input elements: \code{\link{checkboxGroupInput}},
|
||||
\code{\link{checkboxInput}}, \code{\link{dateInput}},
|
||||
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
|
||||
\code{\link{numericInput}}, \code{\link{passwordInput}},
|
||||
@@ -64,4 +64,3 @@ Other input.elements: \code{\link{checkboxGroupInput}},
|
||||
\code{\link{sliderInput}}, \code{\link{submitButton}},
|
||||
\code{\link{textAreaInput}}, \code{\link{textInput}}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,4 +32,3 @@ addResourcePath('datasets', system.file('data', package='datasets'))
|
||||
\seealso{
|
||||
\code{\link{singleton}}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,4 +27,3 @@ output.
|
||||
registerInputHandler
|
||||
}
|
||||
\keyword{internal}
|
||||
|
||||
|
||||
@@ -70,4 +70,3 @@ shinyApp(ui, server)
|
||||
\seealso{
|
||||
\code{\link{enableBookmarking}} for more examples.
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,3 @@ It isn't necessary to call this function if you use
|
||||
\code{\link{pageWithSidebar}}, and \code{\link{navbarPage}}, because they
|
||||
already include the Bootstrap web dependencies.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/bootstrap.R
|
||||
\name{bootstrapPage}
|
||||
\alias{basicPage}
|
||||
\alias{bootstrapPage}
|
||||
\alias{basicPage}
|
||||
\title{Create a Bootstrap page}
|
||||
\usage{
|
||||
bootstrapPage(..., title = NULL, responsive = NULL, theme = NULL)
|
||||
@@ -41,4 +41,3 @@ The \code{basicPage} function is deprecated, you should use the
|
||||
\seealso{
|
||||
\code{\link{fluidPage}}, \code{\link{fixedPage}}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,4 +49,3 @@ This generates an object representing brushing options, to be passed as the
|
||||
\code{brush} argument of \code{\link{imageOutput}} or
|
||||
\code{\link{plotOutput}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -69,4 +69,3 @@ using just the x or y variable, whichever is appropriate.
|
||||
\seealso{
|
||||
\code{\link{plotOutput}} for example usage.
|
||||
}
|
||||
|
||||
|
||||
@@ -29,4 +29,3 @@ modules are easier to reuse and easier to reason about. See the article at
|
||||
\seealso{
|
||||
\url{http://shiny.rstudio.com/articles/modules.html}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
\alias{checkboxGroupInput}
|
||||
\title{Checkbox Group Input Control}
|
||||
\usage{
|
||||
checkboxGroupInput(inputId, label, choices, selected = NULL, inline = FALSE,
|
||||
width = NULL)
|
||||
checkboxGroupInput(inputId, label, choices = NULL, selected = NULL,
|
||||
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{The \code{input} slot that will be used to access the value.}
|
||||
@@ -13,7 +13,9 @@ checkboxGroupInput(inputId, label, choices, selected = NULL, inline = FALSE,
|
||||
\item{label}{Display label for the control, or \code{NULL} for no label.}
|
||||
|
||||
\item{choices}{List of values to show checkboxes for. If elements of the list
|
||||
are named then that name rather than the value is displayed to the user.}
|
||||
are named then that name rather than the value is displayed to the user. If
|
||||
this argument is provided, then \code{choiceNames} and \code{choiceValues}
|
||||
must not be provided, and vice-versa.}
|
||||
|
||||
\item{selected}{The values that should be initially selected, if any.}
|
||||
|
||||
@@ -21,6 +23,16 @@ are named then that name rather than the value is displayed to the user.}
|
||||
|
||||
\item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
see \code{\link{validateCssUnit}}.}
|
||||
|
||||
\item{choiceNames, choiceValues}{List of names and values, respectively,
|
||||
that are displayed to the user in the app and correspond to the each
|
||||
choice (for this reason, \code{choiceNames} and \code{choiceValues}
|
||||
must have the same length). If either of these arguments is
|
||||
provided, then the other \emph{must} be provided and \code{choices}
|
||||
\emph{must not} be provided. The advantage of using both of these over
|
||||
a named list for \code{choices} is that \code{choiceNames} allows any
|
||||
type of UI object to be passed through (tag objects, icons, HTML code,
|
||||
...), instead of just simple text. See Examples.}
|
||||
}
|
||||
\value{
|
||||
A list of HTML elements that can be added to a UI definition.
|
||||
@@ -42,19 +54,39 @@ ui <- fluidPage(
|
||||
tableOutput("data")
|
||||
)
|
||||
|
||||
server <- function(input, output) {
|
||||
server <- function(input, output, session) {
|
||||
output$data <- renderTable({
|
||||
mtcars[, c("mpg", input$variable), drop = FALSE]
|
||||
}, rownames = TRUE)
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
|
||||
ui <- fluidPage(
|
||||
checkboxGroupInput("icons", "Choose icons:",
|
||||
choiceNames =
|
||||
list(icon("calendar"), icon("bed"),
|
||||
icon("cog"), icon("bug")),
|
||||
choiceValues =
|
||||
list("calendar", "bed", "cog", "bug")
|
||||
),
|
||||
textOutput("txt")
|
||||
)
|
||||
|
||||
server <- function(input, output, session) {
|
||||
output$txt <- renderText({
|
||||
icons <- paste(input$icons, collapse = ", ")
|
||||
paste("You chose", icons)
|
||||
})
|
||||
}
|
||||
|
||||
shinyApp(ui, server)
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{checkboxInput}}, \code{\link{updateCheckboxGroupInput}}
|
||||
|
||||
Other input.elements: \code{\link{actionButton}},
|
||||
Other input elements: \code{\link{actionButton}},
|
||||
\code{\link{checkboxInput}}, \code{\link{dateInput}},
|
||||
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
|
||||
\code{\link{numericInput}}, \code{\link{passwordInput}},
|
||||
@@ -62,4 +94,3 @@ Other input.elements: \code{\link{actionButton}},
|
||||
\code{\link{sliderInput}}, \code{\link{submitButton}},
|
||||
\code{\link{textAreaInput}}, \code{\link{textInput}}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ shinyApp(ui, server)
|
||||
\seealso{
|
||||
\code{\link{checkboxGroupInput}}, \code{\link{updateCheckboxInput}}
|
||||
|
||||
Other input.elements: \code{\link{actionButton}},
|
||||
Other input elements: \code{\link{actionButton}},
|
||||
\code{\link{checkboxGroupInput}},
|
||||
\code{\link{dateInput}}, \code{\link{dateRangeInput}},
|
||||
\code{\link{fileInput}}, \code{\link{numericInput}},
|
||||
@@ -48,4 +48,3 @@ Other input.elements: \code{\link{actionButton}},
|
||||
\code{\link{submitButton}}, \code{\link{textAreaInput}},
|
||||
\code{\link{textInput}}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,4 +19,3 @@ This generates an object representing click options, to be passed as the
|
||||
\code{click} argument of \code{\link{imageOutput}} or
|
||||
\code{\link{plotOutput}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -64,4 +64,3 @@ shinyApp(ui, server = function(input, output) { })
|
||||
\seealso{
|
||||
\code{\link{fluidRow}}, \code{\link{fixedRow}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -56,4 +56,3 @@ sidebarPanel(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,3 @@ served over Shiny's HTTP server. This function works by using
|
||||
\code{\link{addResourcePath}} to map the HTML dependency's directory to a
|
||||
URL.
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ shinyApp(ui, server = function(input, output) { })
|
||||
\seealso{
|
||||
\code{\link{dateRangeInput}}, \code{\link{updateDateInput}}
|
||||
|
||||
Other input.elements: \code{\link{actionButton}},
|
||||
Other input elements: \code{\link{actionButton}},
|
||||
\code{\link{checkboxGroupInput}},
|
||||
\code{\link{checkboxInput}},
|
||||
\code{\link{dateRangeInput}}, \code{\link{fileInput}},
|
||||
@@ -106,4 +106,3 @@ Other input.elements: \code{\link{actionButton}},
|
||||
\code{\link{sliderInput}}, \code{\link{submitButton}},
|
||||
\code{\link{textAreaInput}}, \code{\link{textInput}}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ shinyApp(ui, server = function(input, output) { })
|
||||
\seealso{
|
||||
\code{\link{dateInput}}, \code{\link{updateDateRangeInput}}
|
||||
|
||||
Other input.elements: \code{\link{actionButton}},
|
||||
Other input elements: \code{\link{actionButton}},
|
||||
\code{\link{checkboxGroupInput}},
|
||||
\code{\link{checkboxInput}}, \code{\link{dateInput}},
|
||||
\code{\link{fileInput}}, \code{\link{numericInput}},
|
||||
@@ -123,4 +123,3 @@ Other input.elements: \code{\link{actionButton}},
|
||||
\code{\link{submitButton}}, \code{\link{textAreaInput}},
|
||||
\code{\link{textInput}}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,4 +23,3 @@ This generates an object representing dobule-click options, to be passed as
|
||||
the \code{dblclick} argument of \code{\link{imageOutput}} or
|
||||
\code{\link{plotOutput}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ window.
|
||||
time each subsequent event is considered is already after the time window
|
||||
has expired.
|
||||
}
|
||||
|
||||
\examples{
|
||||
## Only run examples in interactive R sessions
|
||||
if (interactive()) {
|
||||
@@ -119,4 +120,3 @@ shinyApp(ui, server)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
\name{domains}
|
||||
\alias{domains}
|
||||
\alias{getDefaultReactiveDomain}
|
||||
\alias{onReactiveDomainEnded}
|
||||
\alias{withReactiveDomain}
|
||||
\alias{onReactiveDomainEnded}
|
||||
\alias{domains}
|
||||
\title{Reactive domains}
|
||||
\usage{
|
||||
getDefaultReactiveDomain()
|
||||
@@ -51,4 +52,3 @@ as a convenience function for registering callbacks. If the reactive domain
|
||||
is \code{NULL} and \code{failIfNull} is \code{FALSE}, then the callback will
|
||||
never be invoked.
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
\name{downloadButton}
|
||||
\alias{downloadButton}
|
||||
\alias{downloadLink}
|
||||
\alias{downloadLink}
|
||||
\title{Create a download button or link}
|
||||
\usage{
|
||||
downloadButton(outputId, label = "Download", class = NULL, ...)
|
||||
@@ -43,6 +44,5 @@ downloadLink('downloadData', 'Download')
|
||||
|
||||
}
|
||||
\seealso{
|
||||
downloadHandler
|
||||
\code{\link{downloadHandler}}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,4 +61,3 @@ server <- function(input, output) {
|
||||
shinyApp(ui, server)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -228,4 +228,3 @@ shinyApp(ui, server)
|
||||
|
||||
Also see \code{\link{updateQueryString}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -68,4 +68,3 @@ shinyApp(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,4 +58,3 @@ tripleA <- renderTriple({
|
||||
isolate(tripleA())
|
||||
# "text, text, text"
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
\alias{fileInput}
|
||||
\title{File Upload Control}
|
||||
\usage{
|
||||
fileInput(inputId, label, multiple = FALSE, accept = NULL, width = NULL)
|
||||
fileInput(inputId, label, multiple = FALSE, accept = NULL, width = NULL,
|
||||
buttonLabel = "Browse...", placeholder = "No file selected")
|
||||
}
|
||||
\arguments{
|
||||
\item{inputId}{The \code{input} slot that will be used to access the value.}
|
||||
@@ -20,6 +21,11 @@ what kind of files the server is expecting.}
|
||||
|
||||
\item{width}{The width of the input, e.g. \code{'400px'}, or \code{'100\%'};
|
||||
see \code{\link{validateCssUnit}}.}
|
||||
|
||||
\item{buttonLabel}{The label used on the button. Can be text or an HTML tag
|
||||
object.}
|
||||
|
||||
\item{placeholder}{The text to show before a file has been uploaded.}
|
||||
}
|
||||
\description{
|
||||
Create a file upload control that can be used to upload one or more files.
|
||||
@@ -84,7 +90,7 @@ shinyApp(ui, server)
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
Other input.elements: \code{\link{actionButton}},
|
||||
Other input elements: \code{\link{actionButton}},
|
||||
\code{\link{checkboxGroupInput}},
|
||||
\code{\link{checkboxInput}}, \code{\link{dateInput}},
|
||||
\code{\link{dateRangeInput}}, \code{\link{numericInput}},
|
||||
@@ -93,4 +99,3 @@ Other input.elements: \code{\link{actionButton}},
|
||||
\code{\link{submitButton}}, \code{\link{textAreaInput}},
|
||||
\code{\link{textInput}}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,4 +81,3 @@ fillPage(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/bootstrap-layout.R
|
||||
\name{fillRow}
|
||||
\alias{fillCol}
|
||||
\alias{fillRow}
|
||||
\alias{fillCol}
|
||||
\title{Flex Box-based row/column layouts}
|
||||
\usage{
|
||||
fillRow(..., flex = 1, width = "100\%", height = "100\%")
|
||||
@@ -75,4 +75,3 @@ shinyApp(ui, server)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,4 +68,3 @@ shinyApp(ui, server = function(input, output) { })
|
||||
\seealso{
|
||||
\code{\link{column}}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,3 @@ shinyApp(ui, server = function(input, output) { })
|
||||
\seealso{
|
||||
\code{\link{verticalLayout}}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,4 +102,3 @@ shinyApp(ui, server = function(input, output) { })
|
||||
\seealso{
|
||||
\code{\link{column}}, \code{\link{sidebarLayout}}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/reactives.R
|
||||
\name{freezeReactiveValue}
|
||||
\name{freezeReactiveVal}
|
||||
\alias{freezeReactiveVal}
|
||||
\alias{freezeReactiveValue}
|
||||
\title{Freeze a reactive value}
|
||||
\usage{
|
||||
freezeReactiveVal(x)
|
||||
|
||||
freezeReactiveValue(x, name)
|
||||
}
|
||||
\arguments{
|
||||
\item{x}{A \code{\link{reactiveValues}} object (like \code{input}).}
|
||||
\item{x}{For \code{freezeReactiveValue}, a \code{\link{reactiveValues}}
|
||||
object (like \code{input}); for \code{freezeReactiveVal}, a
|
||||
\code{\link{reactiveVal}} object.}
|
||||
|
||||
\item{name}{The name of a value in the \code{\link{reactiveValues}} object.}
|
||||
}
|
||||
\description{
|
||||
This freezes a reactive value. If the value is accessed while frozen, a
|
||||
These functions freeze a \code{\link{reactiveVal}}, or an element of a
|
||||
\code{\link{reactiveValues}}. If the value is accessed while frozen, a
|
||||
"silent" exception is raised and the operation is stopped. This is the same
|
||||
thing that happens if \code{req(FALSE)} is called. The value is thawed
|
||||
(un-frozen; accessing it will no longer raise an exception) when the current
|
||||
@@ -58,4 +64,3 @@ shinyApp(ui, server)
|
||||
\seealso{
|
||||
\code{\link{req}}
|
||||
}
|
||||
|
||||
|
||||
93
man/getQueryString.Rd
Normal file
93
man/getQueryString.Rd
Normal file
@@ -0,0 +1,93 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/history.R
|
||||
\name{getQueryString}
|
||||
\alias{getQueryString}
|
||||
\alias{getUrlHash}
|
||||
\title{Get the query string / hash component from the URL}
|
||||
\usage{
|
||||
getQueryString(session = getDefaultReactiveDomain())
|
||||
|
||||
getUrlHash(session = getDefaultReactiveDomain())
|
||||
}
|
||||
\arguments{
|
||||
\item{session}{A Shiny session object.}
|
||||
}
|
||||
\value{
|
||||
For \code{getQueryString}, a named list. For example, the query
|
||||
string \code{?param1=value1¶m2=value2} becomes \code{list(param1 =
|
||||
value1, param2 = value2)}. For \code{getUrlHash}, a character vector with
|
||||
the hash (including the leading \code{#} symbol).
|
||||
}
|
||||
\description{
|
||||
Two user friendly wrappers for getting the query string and the hash
|
||||
component from the app's URL.
|
||||
}
|
||||
\details{
|
||||
These can be particularly useful if you want to display different content
|
||||
depending on the values in the query string / hash (e.g. instead of basing
|
||||
the conditional on an input or a calculated reactive, you can base it on the
|
||||
query string). However, note that, if you're changing the query string / hash
|
||||
programatically from within the server code, you must use
|
||||
\code{updateQueryString(_yourNewQueryString_, mode = "push")}. The default
|
||||
\code{mode} for \code{updateQueryString} is \code{"replace"}, which doesn't
|
||||
raise any events, so any observers or reactives that depend on it will
|
||||
\emph{not} get triggered. However, if you're changing the query string / hash
|
||||
directly by typing directly in the browser and hitting enter, you don't have
|
||||
to worry about this.
|
||||
}
|
||||
\examples{
|
||||
## Only run this example in interactive R sessions
|
||||
if (interactive()) {
|
||||
|
||||
## App 1: getQueryString
|
||||
## Printing the value of the query string
|
||||
## (Use the back and forward buttons to see how the browser
|
||||
## keeps a record of each state)
|
||||
shinyApp(
|
||||
ui = fluidPage(
|
||||
textInput("txt", "Enter new query string"),
|
||||
helpText("Format: ?param1=val1¶m2=val2"),
|
||||
actionButton("go", "Update"),
|
||||
hr(),
|
||||
verbatimTextOutput("query")
|
||||
),
|
||||
server = function(input, output, session) {
|
||||
observeEvent(input$go, {
|
||||
updateQueryString(input$txt, mode = "push")
|
||||
})
|
||||
output$query <- renderText({
|
||||
query <- getQueryString()
|
||||
queryText <- paste(names(query), query,
|
||||
sep = "=", collapse=", ")
|
||||
paste("Your query string is:\\n", queryText)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
## App 2: getUrlHash
|
||||
## Printing the value of the URL hash
|
||||
## (Use the back and forward buttons to see how the browser
|
||||
## keeps a record of each state)
|
||||
shinyApp(
|
||||
ui = fluidPage(
|
||||
textInput("txt", "Enter new hash"),
|
||||
helpText("Format: #hash"),
|
||||
actionButton("go", "Update"),
|
||||
hr(),
|
||||
verbatimTextOutput("hash")
|
||||
),
|
||||
server = function(input, output, session) {
|
||||
observeEvent(input$go, {
|
||||
updateQueryString(input$txt, mode = "push")
|
||||
})
|
||||
output$hash <- renderText({
|
||||
hash <- getUrlHash()
|
||||
paste("Your hash is:\\n", hash)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\code{\link{updateQueryString}}
|
||||
}
|
||||
@@ -21,4 +21,3 @@ Create a header panel containing an application title.
|
||||
\examples{
|
||||
headerPanel("Hello Shiny!")
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,3 @@ helpText("Note: while the data view will show only",
|
||||
"the specified number of observations, the",
|
||||
"summary will be based on the full dataset.")
|
||||
}
|
||||
|
||||
|
||||
@@ -33,4 +33,3 @@ This generates an object representing hovering options, to be passed as the
|
||||
\code{hover} argument of \code{\link{imageOutput}} or
|
||||
\code{\link{plotOutput}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -42,4 +42,3 @@ tags$ul(
|
||||
htmlOutput("summary", container = tags$li, class = "custom-li-output")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -47,4 +47,3 @@ For lists of available icons, see
|
||||
\href{http://fontawesome.io/icons/}{http://fontawesome.io/icons/} and
|
||||
\href{http://getbootstrap.com/components/#glyphicons}{http://getbootstrap.com/components/#glyphicons}.
|
||||
}
|
||||
|
||||
|
||||
@@ -13,4 +13,3 @@ inputPanel(...)
|
||||
A \code{\link{flowLayout}} with a grey border and light grey background,
|
||||
suitable for wrapping inputs.
|
||||
}
|
||||
|
||||
|
||||
@@ -86,4 +86,3 @@ shinyApp(ui, server)
|
||||
\seealso{
|
||||
\code{\link{removeUI}}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,4 +40,3 @@ function named \code{func} in the current environment.
|
||||
Wraps \code{\link{exprToFunction}}; see that method's documentation
|
||||
for more documentation and examples.
|
||||
}
|
||||
|
||||
|
||||
@@ -63,4 +63,3 @@ shinyApp(ui, server)
|
||||
\seealso{
|
||||
\code{\link{reactiveTimer}} is a slightly less safe alternative.
|
||||
}
|
||||
|
||||
|
||||
@@ -15,4 +15,3 @@ Checks whether its argument is a reactivevalues object.
|
||||
\seealso{
|
||||
\code{\link{reactiveValues}}.
|
||||
}
|
||||
|
||||
|
||||
@@ -77,4 +77,3 @@ isolate(fun())
|
||||
# isolate also works if the reactive expression accesses values from the
|
||||
# input object, like input$x
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/app.R
|
||||
\name{knitr_methods}
|
||||
\alias{knit_print.reactive}
|
||||
\alias{knitr_methods}
|
||||
\alias{knit_print.shiny.appobj}
|
||||
\alias{knit_print.shiny.render.function}
|
||||
\alias{knitr_methods}
|
||||
\alias{knit_print.reactive}
|
||||
\title{Knitr S3 methods}
|
||||
\usage{
|
||||
knit_print.shiny.appobj(x, ...)
|
||||
@@ -24,4 +24,3 @@ knit_print.reactive(x, ..., inline = FALSE)
|
||||
These S3 methods are necessary to help Shiny applications and UI chunks embed
|
||||
themselves in knitr/rmarkdown documents.
|
||||
}
|
||||
|
||||
|
||||
@@ -27,4 +27,3 @@ mainPanel(
|
||||
plotOutput("mpgPlot")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,4 +30,3 @@ observe(print(b()))
|
||||
a <- 20
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
18
man/markOutputAttrs.Rd
Normal file
18
man/markOutputAttrs.Rd
Normal file
@@ -0,0 +1,18 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/shinywrappers.R
|
||||
\name{markOutputAttrs}
|
||||
\alias{markOutputAttrs}
|
||||
\title{Mark a render function with attributes that will be used by the output}
|
||||
\usage{
|
||||
markOutputAttrs(renderFunc, snapshotExclude = NULL)
|
||||
}
|
||||
\arguments{
|
||||
\item{renderFunc}{A function that is suitable for assigning to a Shiny output
|
||||
slot.}
|
||||
|
||||
\item{snapshotExclude}{If TRUE, exclude the output from test snapshots.}
|
||||
}
|
||||
\description{
|
||||
Mark a render function with attributes that will be used by the output
|
||||
}
|
||||
\keyword{internal}
|
||||
@@ -30,4 +30,3 @@ Shiny regarding what UI function is most commonly used with this type of
|
||||
render function. This can be used in R Markdown documents to create complete
|
||||
output widgets out of just the render function.
|
||||
}
|
||||
|
||||
|
||||
@@ -21,4 +21,3 @@ default, an error).
|
||||
\seealso{
|
||||
\code{\link{isolate}}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,4 +18,3 @@ When clicked, a \code{modalButton} will dismiss the modal dialog.
|
||||
\seealso{
|
||||
\code{\link{modalDialog}} for examples.
|
||||
}
|
||||
|
||||
|
||||
@@ -129,4 +129,3 @@ shinyApp(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user