Compare commits

..

1 Commits

Author SHA1 Message Date
Joe Cheng
53c05128b3 Firefox compatibility; visual tweaks 2013-07-06 18:14:30 -07:00
24 changed files with 69 additions and 1140 deletions

View File

@@ -9,4 +9,3 @@
^\.gitignore$
^res$
^tools$
^man-roxygen$

View File

@@ -1,7 +1,7 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 0.7.0
Version: 0.6.0.99
Date: 2013-01-23
Author: RStudio, Inc.
Maintainer: Winston Chang <winston@rstudio.com>
@@ -16,14 +16,14 @@ Imports:
stats,
tools,
utils,
datasets,
methods,
httpuv (>= 1.1.0),
httpuv (>= 1.0.6.2),
caTools,
RJSONIO,
xtable,
digest
Suggests:
datasets,
markdown,
Cairo,
testthat

View File

@@ -17,7 +17,6 @@ S3method(as.list,reactivevalues)
S3method(format,shiny.tag)
S3method(format,shiny.tag.list)
S3method(names,reactivevalues)
S3method(print,reactive)
S3method(print,shiny.tag)
S3method(print,shiny.tag.list)
export(HTML)
@@ -58,8 +57,6 @@ export(includeMarkdown)
export(includeScript)
export(includeText)
export(invalidateLater)
export(is.reactive)
export(is.reactivevalues)
export(isolate)
export(mainPanel)
export(numericInput)
@@ -73,9 +70,7 @@ export(plotPNG)
export(pre)
export(radioButtons)
export(reactive)
export(reactiveFileReader)
export(reactivePlot)
export(reactivePoll)
export(reactivePrint)
export(reactiveTable)
export(reactiveText)
@@ -132,9 +127,9 @@ export(validateCssUnit)
export(verbatimTextOutput)
export(wellPanel)
export(withTags)
export(writeReactLog)
import(RJSONIO)
import(caTools)
import(digest)
import(httpuv)
import(methods)
import(xtable)

45
NEWS
View File

@@ -1,49 +1,6 @@
shiny 0.7.0
shiny 0.6.0.99
--------------------------------------------------------------------------------
* Stopped sending websocket subprotocol. This fixes a compatibility issue with
Google Chrome 30.
* The `input` and `output` objects are now also accessible via `session$input`
and `session$output`.
* Added click and hover events for static plots; see `?plotOutput` for details.
* Added optional logging of the execution states of a reactive program, and
tools for visualizing the log data. To use, start a new R session and call
`options(shiny.reactlog=TRUE)`. Then launch a Shiny app and interact with it.
Press Ctrl+F3 (or for Mac, Cmd+F3) in the browser to launch an interactive
visualization of the reactivity that has occurred. See `?showReactLog` for
more information.
* Added `includeScript()` and `includeCSS()` functions.
* Reactive expressions now have class="reactive" attribute. Also added
`is.reactive()` and `is.reactivevalues()` functions.
* New `stopApp()` function, which stops an app and returns a value to the caller
of `runApp()`.
* Added the `shiny.usecairo` option, which can be used to tell Shiny not to use
Cairo for PNG output even when it is installed. (Defaults to `TRUE`.)
* Speed increases for `selectInput()` and `radioButtons()`, and their
corresponding updater functions, for when they have many options.
* Added `tagSetChildren()` and `tagAppendChildren()` functions.
* The HTTP request object that created the websocket is now accessible from the
`session` object, as `session$request`. This is a Rook-like request
environment that can be used to access HTTP headers, among other things.
(Note: When running in a Shiny Server environment, the request will reflect
the proxy HTTP request that was made from the Shiny Server process to the R
process, not the request that was made from the web browser to Shiny Server.)
* Fix `getComputedStyle` issue, for IE8 browser compatibility (#196). Note:
Shiny Server is still required for IE8/9 compatibility.
* Add shiny.sharedSecret option, to require the HTTP header Shiny-Shared-Secret
to be set to the given value.
shiny 0.6.0
--------------------------------------------------------------------------------

View File

@@ -626,10 +626,8 @@ actionButton <- function(inputId, label) {
#' @param label A descriptive label to be displayed with the widget.
#' @param min The minimum value (inclusive) that can be selected.
#' @param max The maximum value (inclusive) that can be selected.
#' @param value The initial value of the slider. A numeric vector of length
#' one will create a regular slider; a numeric vector of length two will
#' create a double-ended range slider.. A warning will be issued if the
#' value doesn't fit between \code{min} and \code{max}.
#' @param value The initial value of the slider. A warning will be issued if the
#' value doesn't fit between \code{min} and \code{max}.
#' @param step Specifies the interval between each selectable value on the
#' slider (\code{NULL} means no restriction).
#' @param round \code{TRUE} to round all values to the nearest integer;
@@ -1095,24 +1093,6 @@ imageOutput <- function(outputId, width = "100%", height="400px") {
#' \code{"400px"}, \code{"auto"}) or a number, which will be coerced to a
#' string and have \code{"px"} appended.
#' @param height Plot height
#' @param clickId If not \code{NULL}, the plot will send coordinates to the
#' server whenever it is clicked. This information will be accessible on the
#' \code{input} object using \code{input$}\emph{\code{clickId}}. The value will be a
#' named list or vector with \code{x} and \code{y} elements indicating the
#' mouse position in user units.
#' @param hoverId If not \code{NULL}, the plot will send coordinates to the
#' server whenever the mouse pauses on the plot for more than the number of
#' milliseconds determined by \code{hoverTimeout}. This information will be
# accessible on the \code{input} object using \code{input$}\emph{\code{clickId}}.
#' The value will be \code{NULL} if the user is not hovering, and a named
#' list or vector with \code{x} and \code{y} elements indicating the mouse
#' position in user units.
#' @param hoverDelay The delay for hovering, in milliseconds.
#' @param hoverDelayType The type of algorithm for limiting the number of hover
#' events. Use \code{"throttle"} to limit the number of hover events to one
#' every \code{hoverDelay} milliseconds. Use \code{"debounce"} to suspend
#' events while the cursor is moving, and wait until the cursor has been at
#' rest for \code{hoverDelay} milliseconds before sending an event.
#' @return A plot output element that can be included in a panel
#' @examples
#' # Show a plot of the generated distribution
@@ -1120,23 +1100,10 @@ imageOutput <- function(outputId, width = "100%", height="400px") {
#' plotOutput("distPlot")
#' )
#' @export
plotOutput <- function(outputId, width = "100%", height="400px",
clickId = NULL, hoverId = NULL, hoverDelay = 300,
hoverDelayType = c("debounce", "throttle")) {
if (is.null(clickId) && is.null(hoverId)) {
hoverDelay <- NULL
hoverDelayType <- NULL
} else {
hoverDelayType <- match.arg(hoverDelayType)[[1]]
}
plotOutput <- function(outputId, width = "100%", height="400px") {
style <- paste("width:", validateCssUnit(width), ";",
"height:", validateCssUnit(height))
div(id = outputId, class = "shiny-plot-output", style = style,
`data-click-id` = clickId,
`data-hover-id` = hoverId,
`data-hover-delay` = hoverDelay,
`data-hover-delay-type` = hoverDelayType)
div(id = outputId, class = "shiny-plot-output", style = style)
}
#' Create a table output element

View File

@@ -1,40 +1,8 @@
#' @export
writeReactLog <- function(file=stdout()) {
cat(RJSONIO::toJSON(.graphEnv$log, pretty=TRUE), file=file)
}
#' Reactive Log Visualizer
#'
#' Provides an interactive browser-based tool for visualizing reactive
#' dependencies and execution in your application.
#'
#' To use the reactive log visualizer, start with a fresh R session and
#' run the command \code{options(shiny.reactlog=TRUE)}; then launch your
#' application in the usual way (e.g. using \code{\link{runApp}}). At
#' any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
#' web browser to launch the reactive log visualization.
#'
#' The reactive log visualization only includes reactive activity up
#' until the time the report was loaded. If you want to see more recent
#' activity, refresh the browser.
#'
#' Note that Shiny does not distinguish between reactive dependencies
#' that "belong" to one Shiny user session versus another, so the
#' visualization will include all reactive activity that has taken place
#' in the process, not just for a particular application or session.
#'
#' As an alternative to pressing Ctrl/Command+F3--for example, if you
#' are using reactives outside of the context of a Shiny
#' application--you can run the \code{showReactLog} function, which will
#' generate the reactive log visualization as a static HTML file and
#' launch it in your default browser. In this case, refreshing your
#' browser will not load new activity into the report; you will need to
#' call \code{showReactLog()} explicitly.
#'
#' For security and performance reasons, do not enable
#' \code{shiny.reactlog} in production environments. When the option is
#' enabled, it's possible for any user of your app to see at least some
#' of the source code of your reactive expressions and observers.
#'
#' @export
showReactLog <- function() {
browseURL(renderReactLog())

View File

@@ -173,7 +173,7 @@ ReactiveValues <- setRefClass(
#' @param ... Objects that will be added to the reactivevalues object. All of
#' these objects must be named.
#'
#' @seealso \code{\link{isolate}} and \code{\link{is.reactivevalues}}.
#' @seealso \code{\link{isolate}}.
#'
#' @export
reactiveValues <- function(...) {
@@ -199,15 +199,6 @@ setOldClass("reactivevalues")
structure(list(impl=values), class='reactivevalues', readonly=readonly)
}
#' Checks whether an object is a reactivevalues object
#'
#' Checks whether its argument is a reactivevalues object.
#'
#' @param x The object to test.
#' @seealso \code{\link{reactiveValues}}.
#' @export
is.reactivevalues <- function(x) inherits(x, 'reactivevalues')
#' @S3method $ reactivevalues
`$.reactivevalues` <- function(x, name) {
.subset2(x, 'impl')$get(name)
@@ -378,8 +369,7 @@ Observable <- setRefClass(
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{Shiny tutorial} for
#' more information about reactive expressions.
#'
#' @param x For \code{reactive}, an expression (quoted or unquoted). For
#' \code{is.reactive}, an object to test.
#' @param x An expression (quoted or unquoted).
#' @param env The parent environment for the reactive expression. By default, this
#' is the calling environment, the same as when defining an ordinary
#' non-reactive expression.
@@ -387,7 +377,6 @@ Observable <- setRefClass(
#' This is useful when you want to use an expression that is stored in a
#' variable; to do so, it must be quoted with `quote()`.
#' @param label A label for the reactive expression, useful for debugging.
#' @return a function, wrapped in a S3 class "reactive"
#'
#' @examples
#' values <- reactiveValues(A=1)
@@ -414,20 +403,9 @@ reactive <- function(x, env = parent.frame(), quoted = FALSE, label = NULL) {
if (is.null(label))
label <- sprintf('reactive(%s)', paste(deparse(body(fun)), collapse='\n'))
o <- Observable$new(fun, label)
structure(o$getValue@.Data, observable = o, class = "reactive")
Observable$new(fun, label)$getValue
}
#' @S3method print reactive
print.reactive <- function(x, ...) {
label <- attr(x, "observable")$.label
cat(label, "\n")
}
#' @export
#' @rdname 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.function(x))
@@ -765,168 +743,6 @@ invalidateLater <- function(millis, session) {
invisible()
}
coerceToFunc <- function(x) {
force(x);
if (is.function(x))
return(x)
else
return(function() x)
}
#' Reactive polling
#'
#' Used to create a reactive data source, which works by periodically polling a
#' non-reactive data source.
#'
#' \code{reactivePoll} works by pairing a relatively cheap "check" function with
#' a more expensive value retrieval function. The check function will be
#' executed periodically and should always return a consistent value until the
#' data changes. When the check function returns a different value, then the
#' value retrieval function will be used to re-populate the data.
#'
#' Note that the check function doesn't return \code{TRUE} or \code{FALSE} to
#' indicate whether the underlying data has changed. Rather, the check function
#' indicates change by returning a different value from the previous time it was
#' called.
#'
#' For example, \code{reactivePoll} is used to implement
#' \code{reactiveFileReader} by pairing a check function that simply returns the
#' last modified timestamp of a file, and a value retrieval function that
#' actually reads the contents of the file.
#'
#' As another example, one might read a relational database table reactively by
#' using a check function that does \code{SELECT MAX(timestamp) FROM table} and
#' a value retrieval function that does \code{SELECT * FROM table}.
#'
#' The \code{intervalMillis}, \code{checkFunc}, and \code{valueFunc} functions
#' will be executed in a reactive context; therefore, they may read reactive
#' values and reactive expressions.
#'
#' @param intervalMillis Approximate number of milliseconds to wait between
#' calls to \code{checkFunc}. This can be either a numeric value, or a
#' function that returns a numeric value.
#' @param session The user session to associate this file reader with, or
#' \code{NULL} if none. If non-null, the reader will automatically stop when
#' the session ends.
#' @param checkFunc A relatively cheap function whose values over time will be
#' tested for equality; inequality indicates that the underlying value has
#' changed and needs to be invalidated and re-read using \code{valueFunc}. See
#' Details.
#' @param valueFunc A function that calculates the underlying value. See
#' Details.
#'
#' @return A reactive expression that returns the result of \code{valueFunc},
#' and invalidates when \code{checkFunc} changes.
#'
#' @seealso \code{\link{reactiveFileReader}}
#'
#' @examples
#' \dontrun{
#' # Assume the existence of readTimestamp and readValue functions
#' shinyServer(function(input, output, session) {
#' data <- reactivePoll(1000, session, readTimestamp, readValue)
#' output$dataTable <- renderTable({
#' data()
#' })
#' })
#' }
#'
#' @export
reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
intervalMillis <- coerceToFunc(intervalMillis)
rv <- reactiveValues(cookie = isolate(checkFunc()))
observe({
rv$cookie <- checkFunc()
invalidateLater(intervalMillis(), session)
})
# TODO: what to use for a label?
re <- reactive({
rv$cookie
valueFunc()
}, label = NULL)
return(re)
}
#' Reactive file reader
#'
#' Given a file path and read function, returns a reactive data source for the
#' contents of the file.
#'
#' \code{reactiveFileReader} works by periodically checking the file's last
#' modified time; if it has changed, then the file is re-read and any reactive
#' dependents are invalidated.
#'
#' The \code{intervalMillis}, \code{filePath}, and \code{readFunc} functions
#' will each be executed in a reactive context; therefore, they may read
#' reactive values and reactive expressions.
#'
#' @param intervalMillis Approximate number of milliseconds to wait between
#' checks of the file's last modified time. This can be a numeric value, or a
#' function that returns a numeric value.
#' @param session The user session to associate this file reader with, or
#' \code{NULL} if none. If non-null, the reader will automatically stop when
#' the session ends.
#' @param filePath The file path to poll against and to pass to \code{readFunc}.
#' This can either be a single-element character vector, or a function that
#' returns one.
#' @param readFunc The function to use to read the file; must expect the first
#' argument to be the file path to read. The return value of this function is
#' used as the value of the reactive file reader.
#' @param ... Any additional arguments to pass to \code{readFunc} whenever it is
#' invoked.
#'
#' @return A reactive expression that returns the contents of the file, and
#' automatically invalidates when the file changes on disk (as determined by
#' last modified time).
#'
#' @seealso \code{\link{reactivePoll}}
#'
#' @examples
#' \dontrun{
#' # Per-session reactive file reader
#' shinyServer(function(input, output, session)) {
#' fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
#'
#' output$data <- renderTable({
#' fileData()
#' })
#' }
#'
#' # 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)
#' shinyServer(function(input, output, session)) {
#' output$data <- renderTable({
#' fileData()
#' })
#' }
#' }
#'
#' @export
reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...) {
filePath <- coerceToFunc(filePath)
extraArgs <- list(...)
reactivePoll(
intervalMillis, session,
function() {
path <- filePath()
info <- file.info(path)
return(paste(path, info$mtime, info$size))
},
function() {
do.call(readFunc, c(filePath(), extraArgs))
}
)
}
#' Create a non-reactive scope for an expression
#'
#' Executes the given expression in a scope where reactive values or expression
@@ -1000,8 +816,8 @@ reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...)
#' @export
isolate <- function(expr) {
ctx <- Context$new('[isolate]', type='isolate')
on.exit(ctx$invalidate())
ctx$run(function() {
expr
})
ctx$invalidate()
}

View File

@@ -12,7 +12,7 @@
#' @name shiny-package
#' @aliases shiny
#' @docType package
#' @import httpuv caTools RJSONIO xtable digest methods
#' @import httpuv caTools RJSONIO xtable digest
NULL
suppressPackageStartupMessages({
@@ -39,8 +39,6 @@ ShinySession <- setRefClass(
.input = 'ReactiveValues', # Internal object for normal input sent from client
.clientData = 'ReactiveValues', # Internal object for other data sent from the client
.closedCallbacks = 'Callbacks',
.flushCallbacks = 'Callbacks',
.flushedCallbacks = 'Callbacks',
input = 'reactivevalues', # Externally-usable S3 wrapper object for .input
output = 'ANY', # Externally-usable S3 wrapper object for .outputs
clientData = 'reactivevalues', # Externally-usable S3 wrapper object for .clientData
@@ -48,7 +46,7 @@ ShinySession <- setRefClass(
files = 'Map', # For keeping track of files sent to client
downloads = 'Map',
closed = 'logical',
session = 'environment', # Object for the server app to access session stuff
session = 'list', # Object for the server app to access session stuff
.workerId = 'character'
),
methods = list(
@@ -77,21 +75,13 @@ ShinySession <- setRefClass(
.outputs <<- list()
.outputOptions <<- list()
session <<- new.env(parent=emptyenv())
session$clientData <<- clientData
session$sendCustomMessage <<- .self$.sendCustomMessage
session$sendInputMessage <<- .self$.sendInputMessage
session$onSessionEnded <<- .self$onSessionEnded
session$onFlush <<- .self$onFlush
session$onFlushed <<- .self$onFlushed
session$isClosed <<- .self$isClosed
session$input <<- .self$input
session$output <<- .self$output
session$.impl <<- .self
# session$request should throw an error if httpuv doesn't have
# websocket$request, but don't throw it until a caller actually
# tries to access session$request
delayedAssign('request', websocket$request, assign.env = session)
session <<- list(clientData = clientData,
sendCustomMessage = .self$.sendCustomMessage,
sendInputMessage = .self$.sendInputMessage,
onSessionEnded = .self$onSessionEnded,
isClosed = .self$isClosed,
input = .self$input,
output = .self$output)
.write(toJSON(list(config = list(
workerId = .workerId,
@@ -176,9 +166,6 @@ ShinySession <- setRefClass(
},
flushOutput = function() {
.flushCallbacks$invoke()
on.exit(.flushedCallbacks$invoke())
if (length(.progressKeys) == 0
&& length(.invalidatedOutputValues) == 0
&& length(.invalidatedOutputErrors) == 0
@@ -261,28 +248,6 @@ ShinySession <- setRefClass(
# Add to input message queue
.inputMessageQueue[[length(.inputMessageQueue) + 1]] <<- data
},
onFlush = function(func, once = TRUE) {
if (!isTRUE(once)) {
return(.flushCallbacks$register(func))
} else {
dereg <- .flushCallbacks$register(function() {
dereg()
func()
})
return(dereg)
}
},
onFlushed = function(func, once = TRUE) {
if (!isTRUE(once)) {
return(.flushedCallbacks$register(func))
} else {
dereg <- .flushedCallbacks$register(function() {
dereg()
func()
})
return(dereg)
}
},
.write = function(json) {
if (getOption('shiny.trace', FALSE))
message('SEND ',
@@ -649,7 +614,7 @@ httpResponse <- function(status = 200,
return(resp)
}
httpServer <- function(handlers, sharedSecret) {
httpServer <- function(handlers) {
handler <- joinHandlers(handlers)
# TODO: Figure out what this means after httpuv migration
@@ -658,13 +623,6 @@ httpServer <- function(handlers, sharedSecret) {
filter <- function(req, response) response
function(req) {
if (!is.null(sharedSecret)
&& !identical(sharedSecret, req$HTTP_SHINY_SHARED_SECRET)) {
return(list(status=403,
body='<h1>403 Forbidden</h1><p>Shared secret mismatch</p>',
headers=list('Content-Type' = 'text/html')))
}
response <- handler(req)
if (is.null(response))
response <- httpResponse(404, content="<h1>Not Found</h1>")
@@ -1063,11 +1021,6 @@ startApp <- function(httpHandlers, serverFuncSource, port, workerId) {
sys.www.root <- system.file('www', package='shiny')
# This value, if non-NULL, must be present on all HTTP and WebSocket
# requests as the Shiny-Shared-Secret header or else access will be
# denied (403 response for HTTP, and instant close for websocket).
sharedSecret <- getOption('shiny.sharedSecret', NULL)
httpuvCallbacks <- list(
onHeaders = function(req) {
maxSize <- getOption('shiny.maxRequestSize', 5 * 1024 * 1024)
@@ -1095,13 +1048,8 @@ startApp <- function(httpHandlers, serverFuncSource, port, workerId) {
httpHandlers,
sys.www.root,
resourcePathHandler,
reactLogHandler), sharedSecret),
reactLogHandler)),
onWSOpen = function(ws) {
if (!is.null(sharedSecret)
&& !identical(sharedSecret, ws$request$HTTP_SHINY_SHARED_SECRET)) {
ws$close()
}
shinysession <- ShinySession$new(ws, workerId)
appsByToken$set(shinysession$token, shinysession)
@@ -1188,28 +1136,7 @@ startApp <- function(httpHandlers, serverFuncSource, port, workerId) {
shinysession$dispatch(msg)
)
shinysession$manageHiddenOutputs()
if (exists(".shiny__stdout", globalenv()) &&
exists("HTTP_GUID", ws$request)) {
# safe to assume we're in shiny-server
shiny_stdout <- get(".shiny__stdout", globalenv())
# eNter a flushReact
writeLines(paste("_n_flushReact ", get("HTTP_GUID", ws$request),
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
sep=""), con=shiny_stdout)
flush(shiny_stdout)
flushReact()
# eXit a flushReact
writeLines(paste("_x_flushReact ", get("HTTP_GUID", ws$request),
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
sep=""), con=shiny_stdout)
flush(shiny_stdout)
} else {
flushReact()
}
flushReact()
lapply(appsByToken$values(), function(shinysession) {
shinysession$flushOutput()
NULL
@@ -1321,7 +1248,7 @@ runApp <- function(appDir=getwd(),
}
require(shiny)
if (is.character(appDir)) {
orig.wd <- getwd()
setwd(appDir)

View File

@@ -80,53 +80,15 @@ renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
pixelratio <- shinysession$clientData$pixelratio
if (is.null(pixelratio))
pixelratio <- 1
coordmap <- NULL
plotFunc <- function() {
# Actually perform the plotting
func()
# Now capture some graphics device info before we close it
usrCoords <- par('usr')
usrBounds <- usrCoords
if (par('xlog')) {
usrBounds[c(1,2)] <- 10 ^ usrBounds[c(1,2)]
}
if (par('ylog')) {
usrBounds[c(3,4)] <- 10 ^ usrBounds[c(3,4)]
}
coordmap <<- list(
usr = c(
left = usrCoords[1],
right = usrCoords[2],
bottom = usrCoords[3],
top = usrCoords[4]
),
# The bounds of the plot area, in DOM pixels
bounds = c(
left = grconvertX(usrBounds[1], 'user', 'nfc') * width,
right = grconvertX(usrBounds[2], 'user', 'nfc') * width,
bottom = (1-grconvertY(usrBounds[3], 'user', 'nfc')) * height,
top = (1-grconvertY(usrBounds[4], 'user', 'nfc')) * height
),
log = c(
x = par('xlog'),
y = par('ylog')
),
pixelratio = pixelratio
)
}
outfile <- do.call(plotPNG, c(plotFunc, width=width*pixelratio,
outfile <- do.call(plotPNG, c(func, width=width*pixelratio,
height=height*pixelratio, res=res*pixelratio, args))
on.exit(unlink(outfile))
# Return a list of attributes for the img
return(list(
src=shinysession$fileUrl(name, outfile, contentType='image/png'),
width=width, height=height, coordmap=coordmap
))
width=width, height=height))
})
}

View File

@@ -19,20 +19,12 @@ For an introduction and examples, visit the [Shiny homepage](http://www.rstudio.
## Installation
To install the stable version from CRAN, simply run the following from an R console:
From an R console:
```r
install.packages("shiny")
```
To install the latest development builds directly from GitHub, run this instead:
```r
if (!require("devtools"))
install.packages("devtools")
devtools::install_github("shiny", "rstudio")
```
## Getting Started
To learn more we highly recommend you check out the [Shiny Tutorial](http://rstudio.github.com/shiny/tutorial). The tutorial explains the framework in-depth, walks you through building a simple application, and includes extensive annotated examples.

View File

@@ -10,7 +10,7 @@ shinyUI(pageWithSidebar(
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 1,
min = 0,
max = 1000,
value = 500)
),

View File

@@ -663,35 +663,3 @@ test_that("Observer priorities are respected", {
expect_identical(results, c(30, 20, 21, 22, 10))
})
test_that("reactivePoll and reactiveFileReader", {
path <- tempfile('file')
on.exit(unlink(path))
write.csv(cars, file=path, row.names=FALSE)
rfr <- reactiveFileReader(100, NULL, path, read.csv)
expect_equal(isolate(rfr()), cars)
write.csv(rbind(cars, cars), file=path, row.names=FALSE)
Sys.sleep(0.15)
timerCallbacks$executeElapsed()
expect_equal(isolate(rfr()), cars)
flushReact()
expect_equal(isolate(rfr()), rbind(cars, cars))
})
test_that("classes of reactive object", {
v <- reactiveValues(a = 1)
r <- reactive({ v$a + 1 })
o <- observe({ print(r()) })
expect_false(is.reactivevalues(12))
expect_true(is.reactivevalues(v))
expect_false(is.reactivevalues(r))
expect_false(is.reactivevalues(o))
expect_false(is.reactive(12))
expect_false(is.reactive(v))
expect_true(is.reactive(r))
expect_false(is.reactive(o))
})

View File

@@ -114,37 +114,6 @@ svg {
height: auto;
display: none;
}
#timeline {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 20px;
transition: height 500ms;
}
#timeline, #timeline * {
cursor: pointer;
}
#timeline:hover {
height: 32px;
}
#timeline-bg {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 12px;
background-color: silver;
}
#timeline-fill {
background-color: #28A3F2;
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 0;
transition: width 500ms;
}
</style>
<script>
var log = [
@@ -271,6 +240,11 @@ var force = d3.layout.force()
force.on('tick', onTick);
function pathDataForNode(node) {
/*
d="m 58,2 c -75,0 -75,100 0,100 l 60,0 l 50,-50 l -50,-50 Z"
d="m 58,2 c -75,0 -75,100 0,100 l 100,0 l 0,-100 Z"
d="m 2,0 l 0,100 l 100,0 l 50,-50 l -50,-50 Z"
*/
switch (node.type) {
case 'observer':
return 'M -25,-50 c -75,0 -75,100 0,100 l 100,0 l 0,-100 Z';
@@ -329,10 +303,10 @@ function update() {
force.size([document.documentElement.clientWidth / 4,
document.documentElement.clientHeight / 4]);
var layoutDirty = false;
var layoutDirty = true;
node = d3.select('#nodes').selectAll('.node').data(nodeList);
layoutDirty = layoutDirty || !node.enter().empty() || !node.exit().empty();
//layoutDirty = layoutDirty || !node.enter().empty() || !node.exit().empty();
var newG = node.enter().append('g')
.attr('class', function(n) {return 'node ' + n.type;})
.attr('r', 5)
@@ -357,7 +331,7 @@ function update() {
newG.append('text')
.attr('x', 3)
.attr('y', 0)
.attr('font-size', 3.25)
.attr('font-size', 2.5)
.attr('transform', function(n) {
if (n.type !== 'observer')
return 'translate(1.5, 0)';
@@ -383,7 +357,7 @@ function update() {
return changed;
}).selectAll('tspan')
.data(function(n) {
var lines = n.label.replace(/ /g, '\xA0').split('\n');
var lines = n.label.split('\n');
if (lines.length > MAX_LINES) {
lines.splice(MAX_LINES);
}
@@ -400,7 +374,7 @@ function update() {
.text(function(line) { return line; });
link = d3.select('#links').selectAll('.link').data(links);
layoutDirty = layoutDirty || !link.enter().empty() || !link.exit().empty();
//layoutDirty = layoutDirty || !link.enter().empty() || !link.exit().empty();
link.enter().append('path')
.attr('class', 'link')
.attr('marker-mid', 'url(#triangle)');
@@ -434,7 +408,7 @@ function onTick() {
});
}
function createNodeWithUndo(data) {
function createNode(data) {
var node;
if (!data.prevId) {
node = {
@@ -443,163 +417,53 @@ function createNodeWithUndo(data) {
hide: data.hide
};
nodes[data.id] = node;
pushUndo(function() {
delete nodes[data.id];
});
if (!node.hide) {
if (!node.hide)
nodeList.push(node);
pushUndo(function() {
nodeList.pop();
});
}
} else {
node = nodes[data.prevId];
var oldLabel = node.label;
var oldInvalidated = node.invalidated;
delete nodes[data.prevId];
nodes[data.id] = node;
node.label = data.label;
node.invalidated = false;
pushUndo(function() {
node.label = oldLabel;
node.invalidated = oldInvalidated;
delete nodes[data.id];
nodes[data.prevId] = node;
});
}
}
Array.prototype.pushWithUndo = function(value) {
var self = this;
this.push(value);
pushUndo(function() {
self.pop();
});
}
Array.prototype.shiftWithUndo = function(value) {
var self = this;
var value = this.shift();
pushUndo(function() {
self.unshift(value);
});
return value;
}
var undoStack = [];
var currentUndos = null;
function startUndoScope() {
if (currentUndos !== null)
throw new Error('Illegal state');
currentUndos = [];
}
function pushUndo(func) {
currentUndos.push(func);
}
function endUndoScope() {
var localUndos = currentUndos;
undoStack.push(function() {
while (localUndos.length) {
localUndos.pop()();
}
});
currentUndos = null;
}
function undo() {
if (undoStack.length) {
undoStack.pop()();
update();
return true;
}
return false;
}
function undoAll() {
while (undo()) {}
}
// Here we monkeypatch Math.random to take part in the undo mechanism.
// This allows "random" d3 force-layout decisions to be reproducible.
// If we don't do this, then doing/undoing/redoing a node creation step
// looks very confusing, as the node comes flying in from a different
// direction each time.
var trueRandom = Math.random;
Math.random = (function() {
var randomStack = [];
return function() {
if (!currentUndos)
return trueRandom();
var value;
if (randomStack.length > 0) {
value = randomStack.pop();
}
else {
value = trueRandom();
}
pushUndo(function() {
randomStack.push(value);
});
return value;
};
})();
var callbacks = {
ctx: function(data) {
createNodeWithUndo(data);
createNode(data);
return true;
},
dep: function(data) {
var dependsOn = nodes[data.dependsOn];
if (!dependsOn) {
createNodeWithUndo({id: data.dependsOn, label: data.dependsOn, type: 'value'});
createNode({id: data.dependsOn, label: data.dependsOn, type: 'value'});
dependsOn = nodes[data.dependsOn];
}
if (dependsOn.hide) {
dependsOn.hide = false;
nodeList.push(dependsOn);
pushUndo(function() {
dependsOn.hide = true;
nodeList.pop();
});
}
links.push({
source: nodes[data.id],
target: nodes[data.dependsOn]
});
pushUndo(function() {
links.pop();
});
},
depId: function(data) {
links.push({
source: nodes[data.id],
target: nodes[data.dependsOn]
});
pushUndo(function() {
links.pop();
});
},
invalidate: function(data) {
var node = nodes[data.id];
if (node.invalidated)
throw new Error('Illegal sequence');
node.invalidated = true;
pushUndo(function() {
node.invalidated = false;
});
var origLinks = links;
links = links.filter(function(link) {
return link.source !== node;
});
pushUndo(function() {
links = origLinks;
});
},
valueChange: function(data) {
var existed = !!nodes[data.id];
createNodeWithUndo({
createNode({
id: data.id,
label: data.id + ' = ' + data.value,
type: 'value',
@@ -609,94 +473,38 @@ var callbacks = {
if (!existed || nodes[data.id].hide)
return true;
nodes[data.id].changed = true;
pushUndo(function() {
executeBeforeNextCommand.push(function() {
nodes[data.id].changed = false;
});
executeBeforeNextCommand.pushWithUndo(function() {
nodes[data.id].changed = false;
pushUndo(function() {
nodes[data.id].changed = true;
});
});
},
enter: function(data) {
var node = nodes[data.id];
node.running = true;
pushUndo(function() {
node.running = false;
});
},
exit: function(data) {
var node = nodes[data.id];
node.running = false;
pushUndo(function() {
node.running = true;
});
}
};
function processMessage(data, suppressUpdate) {
function processMessage(data) {
console.log(JSON.stringify(data));
if (!callbacks.hasOwnProperty(data.action))
throw new Error('Unknown action ' + data.action);
var result = callbacks[data.action].call(callbacks, data);
if (!suppressUpdate)
update();
update();
return result;
}
var executeBeforeNextCommand = [];
function doNext(suppressUpdate) {
if (!log.length)
return;
startUndoScope();
while (executeBeforeNextCommand.length) {
executeBeforeNextCommand.shiftWithUndo()();
}
while (log.length) {
var result = (function() {
var message = log.shift();
pushUndo(function() {
log.unshift(message);
})
return processMessage(message, suppressUpdate);
})();
if (!result)
function doNext() {
while (executeBeforeNextCommand.length)
executeBeforeNextCommand.shift()();
while (log.length)
if (!processMessage(log.shift()))
break;
}
if (!log.length) {
if (!log.length)
$('#ended').fadeIn(1500);
pushUndo(function() {
$('#ended').hide();
});
}
step++;
updateTimeline();
pushUndo(function() {
step--;
updateTimeline();
});
endUndoScope();
}
function countSteps() {
if (undoStack.length !== 0) {
throw new Error(
'Illegal state; must call countSteps before execution begins');
}
var steps = 0;
while (log.length) {
doNext();
steps++;
}
while (undoStack.length)
undoStack.pop()();
return steps;
}
function updateTimeline() {
$('#timeline-fill').width((step/totalSteps*100) + '%');
}
function zoom() {
@@ -705,71 +513,20 @@ function zoom() {
var y = d3.event.translate[1];
d3.select('#viz').attr('transform', 'scale(' + scale + ') translate(' + x/scale + ' ' + y/scale + ')');
}
// The total number of steps, as far as the user is concerned, in the log.
// This may/will be different than the number of log entries, since each
// step may include more than one log entry.
var totalSteps;
// The current step we're on.
var step;
$(function() {
d3.select('svg').call(d3.behavior.zoom().scale(4).on('zoom', zoom));
$(document.body).on('keydown', function(e) {
if (e.which === 39 || e.which === 32) { // space, right
// Move one step ahead
if (e.which === 39 || e.which === 32)
doNext();
}
if (e.which === 37) { // left
// Move one step back
undo();
}
if (e.which === 35) { // end
// Seek to end
if (e.which === 35) {
while (log.length) {
doNext();
}
}
if (e.which === 36) { // home
// Seek to beginning
undoAll();
}
});
// Timeline click and scrub
$('#timeline').on('click mousemove', function(e) {
// Make sure left mouse button is down.
// Firefox is stupid; e.which is always 1 on mousemove events,
// even when button is not down!! So read e.originalEvent.buttons.
if (typeof(e.originalEvent.buttons) !== 'undefined') {
if (e.originalEvent.buttons !== 1)
return;
} else if (e.which !== 1) {
return;
}
var timeline = e.currentTarget;
var pos = e.offsetX || e.originalEvent.layerX;
var width = timeline.offsetWidth;
var targetStep = Math.round((pos/width) * totalSteps);
while (step < targetStep) {
doNext();
}
while (step > targetStep && step != 1) {
undo();
}
});
totalSteps = countSteps();
step = 0;
doNext();
// don't allow undoing past initial state
while (undoStack.length)
undoStack.pop();
executeBeforeNextCommand.push(function() {
$('#instructions').fadeOut(1000);
// It's weird for the instructions to fade back in, so no pushUndo here
});
});
</script>
@@ -800,7 +557,7 @@ $(function() {
Press right-arrow to advance
</div>
<div id="ended" style="display: none;">
<strong>You&rsquo;ve reached the end</strong><br/>Press the Home key to start over
<strong>You&rsquo;ve reached the end</strong><br/>Reload the page to start over
</div>
<div id="legend">
<div class="color normal"></div> Normal<br/>
@@ -809,10 +566,5 @@ $(function() {
</div>
<br/>
<pre id="description"><br/></pre>
<div id="timeline">
<div id="timeline-bg">
<div id="timeline-fill"></div>
</div>
</div>
</body>
</html>

View File

@@ -82,7 +82,3 @@ span.jslider {
-o-transition: none;
transition: none;
}
.crosshair {
cursor: crosshair;
}

View File

@@ -13,17 +13,6 @@
return Math.floor(0x100000000 + (Math.random() * 0xF00000000)).toString(16);
}
// A wrapper for getComputedStyle that is compatible with older browsers.
// This is significantly faster than jQuery's .css() function.
function getStyle(el, styleProp) {
if (el.currentStyle)
var x = el.currentStyle[styleProp];
else if (window.getComputedStyle)
var x = document.defaultView.getComputedStyle(el, null)
.getPropertyValue(styleProp);
return x;
}
// Convert a number to a string with leading zeros
function padZeros(n, digits) {
var str = n.toString();
@@ -461,7 +450,7 @@
var self = this;
var createSocketFunc = exports.createSocket || function() {
var ws = new WebSocket('ws://' + window.location.host + '/websocket/');
var ws = new WebSocket('ws://' + window.location.host, 'shiny');
ws.binaryType = 'arraybuffer';
return ws;
};
@@ -1002,106 +991,19 @@
return $(scope).find('.shiny-image-output, .shiny-plot-output');
},
renderValue: function(el, data) {
var self = this;
var $el = $(el);
// Load the image before emptying, to minimize flicker
var img = null;
var coordmap, clickId, hoverId;
if (data) {
clickId = $el.data('click-id');
hoverId = $el.data('hover-id');
coordmap = data.coordmap;
delete data.coordmap;
img = document.createElement('img');
// Copy items from data to img. This should include 'src'
$.each(data, function(key, value) {
img[key] = value;
});
// Firefox doesn't have offsetX/Y, so we need to use an alternate
// method of calculation for it
function mouseOffset(mouseEvent) {
if (typeof(mouseEvent.offsetX) !== 'undefined') {
return {
x: mouseEvent.offsetX,
y: mouseEvent.offsetY
};
}
var origEvent = mouseEvent.originalEvent || {};
return {
x: origEvent.layerX - origEvent.target.offsetLeft,
y: origEvent.layerY - origEvent.target.offsetTop
};
}
function createMouseHandler(inputId) {
return function(e) {
if (e === null) {
Shiny.onInputChange(inputId, null);
return;
}
// TODO: Account for scrolling within the image??
function devToUsrX(deviceX) {
var x = deviceX - coordmap.bounds.left;
var factor = (coordmap.usr.right - coordmap.usr.left) /
(coordmap.bounds.right - coordmap.bounds.left);
return (x * factor) + coordmap.usr.left;
}
function devToUsrY(deviceY) {
var y = deviceY - coordmap.bounds.bottom;
var factor = (coordmap.usr.top - coordmap.usr.bottom) /
(coordmap.bounds.top - coordmap.bounds.bottom);
return (y * factor) + coordmap.usr.bottom;
}
var offset = mouseOffset(e);
var userX = devToUsrX(offset.x);
if (coordmap.log.x)
userX = Math.pow(10, userX);
var userY = devToUsrY(offset.y);
if (coordmap.log.y)
userY = Math.pow(10, userY);
Shiny.onInputChange(inputId, {
x: userX, y: userY,
".nonce": Math.random()
});
}
};
if (!$el.data('hover-func')) {
var hoverDelayType = $el.data('hover-delay-type') || 'debounce';
var delayFunc = (hoverDelayType === 'throttle') ? throttle : debounce;
var hoverFunc = delayFunc($el.data('hover-delay') || 300,
createMouseHandler(hoverId));
$el.data('hover-func', hoverFunc);
}
if (clickId)
$(img).on('mousedown', createMouseHandler(clickId));
if (hoverId) {
$(img).on('mousemove', $el.data('hover-func'));
$(img).on('mouseout', function(e) {
$el.data('hover-func')(null);
});
}
if (clickId || hoverId) {
$(img).addClass('crosshair');
}
}
$el.empty();
$(el).empty();
if (img)
$el.append(img);
$(el).append(img);
}
});
outputBindings.register(imageOutputBinding, 'shiny.imageOutput');
@@ -2620,7 +2522,7 @@
// non-zero, then we know that no ancestor has display:none.
if (obj === null || obj.offsetWidth !== 0 || obj.offsetHeight !== 0) {
return false;
} else if (getStyle(obj, 'display') === 'none') {
} else if (getComputedStyle(obj, null).display === 'none') {
return true;
} else {
return(isHidden(obj.parentNode));
@@ -2744,7 +2646,7 @@
});
$(document).on('keydown', function(e) {
if (e.which !== 114 || (!e.ctrlKey && !e.metaKey) || (e.shiftKey || e.altKey))
if (e.which !== 114)
return;
var url = 'reactlog?w=' + Shiny.shinyapp.config.workerId;
window.open(url);

View File

@@ -1,16 +0,0 @@
\name{is.reactivevalues}
\alias{is.reactivevalues}
\title{Checks whether an object is a reactivevalues object}
\usage{
is.reactivevalues(x)
}
\arguments{
\item{x}{The object to test.}
}
\description{
Checks whether its argument is a reactivevalues object.
}
\seealso{
\code{\link{reactiveValues}}.
}

View File

@@ -2,9 +2,7 @@
\alias{plotOutput}
\title{Create an plot output element}
\usage{
plotOutput(outputId, width = "100\%", height = "400px",
clickId = NULL, hoverId = NULL, hoverDelay = 300,
hoverDelayType = c("debounce", "throttle"))
plotOutput(outputId, width = "100\%", height = "400px")
}
\arguments{
\item{outputId}{output variable to read the plot from}
@@ -15,33 +13,6 @@
\code{"px"} appended.}
\item{height}{Plot height}
\item{clickId}{If not \code{NULL}, the plot will send
coordinates to the server whenever it is clicked. This
information will be accessible on the \code{input} object
using \code{input$}\emph{\code{clickId}}. The value will
be a named list or vector with \code{x} and \code{y}
elements indicating the mouse position in user units.}
\item{hoverId}{If not \code{NULL}, the plot will send
coordinates to the server whenever the mouse pauses on
the plot for more than the number of milliseconds
determined by \code{hoverTimeout}. This information will
be The value will be \code{NULL} if the user is not
hovering, and a named list or vector with \code{x} and
\code{y} elements indicating the mouse position in user
units.}
\item{hoverDelay}{The delay for hovering, in
milliseconds.}
\item{hoverDelayType}{The type of algorithm for limiting
the number of hover events. Use \code{"throttle"} to
limit the number of hover events to one every
\code{hoverDelay} milliseconds. Use \code{"debounce"} to
suspend events while the cursor is moving, and wait until
the cursor has been at rest for \code{hoverDelay}
milliseconds before sending an event.}
}
\value{
A plot output element that can be included in a panel

View File

@@ -1,16 +1,12 @@
\name{reactive}
\alias{is.reactive}
\alias{reactive}
\title{Create a reactive expression}
\usage{
reactive(x, env = parent.frame(), quoted = FALSE,
label = NULL)
is.reactive(x)
}
\arguments{
\item{x}{For \code{reactive}, an expression (quoted or
unquoted). For \code{is.reactive}, an object to test.}
\item{x}{An expression (quoted or unquoted).}
\item{env}{The parent environment for the reactive
expression. By default, this is the calling environment,
@@ -25,9 +21,6 @@
\item{label}{A label for the reactive expression, useful
for debugging.}
}
\value{
a function, wrapped in a S3 class "reactive"
}
\description{
Wraps a normal expression to create a reactive
expression. Conceptually, a reactive expression is a

View File

@@ -1,75 +0,0 @@
\name{reactiveFileReader}
\alias{reactiveFileReader}
\title{Reactive file reader}
\usage{
reactiveFileReader(intervalMillis, session, filePath,
readFunc, ...)
}
\arguments{
\item{intervalMillis}{Approximate number of milliseconds
to wait between checks of the file's last modified time.
This can be a numeric value, or a function that returns a
numeric value.}
\item{session}{The user session to associate this file
reader with, or \code{NULL} if none. If non-null, the
reader will automatically stop when the session ends.}
\item{filePath}{The file path to poll against and to pass
to \code{readFunc}. This can either be a single-element
character vector, or a function that returns one.}
\item{readFunc}{The function to use to read the file;
must expect the first argument to be the file path to
read. The return value of this function is used as the
value of the reactive file reader.}
\item{...}{Any additional arguments to pass to
\code{readFunc} whenever it is invoked.}
}
\value{
A reactive expression that returns the contents of the
file, and automatically invalidates when the file changes
on disk (as determined by last modified time).
}
\description{
Given a file path and read function, returns a reactive
data source for the contents of the file.
}
\details{
\code{reactiveFileReader} works by periodically checking
the file's last modified time; if it has changed, then
the file is re-read and any reactive dependents are
invalidated.
The \code{intervalMillis}, \code{filePath}, and
\code{readFunc} functions will each be executed in a
reactive context; therefore, they may read reactive
values and reactive expressions.
}
\examples{
\dontrun{
# Per-session reactive file reader
shinyServer(function(input, output, session)) {
fileData <- reactiveFileReader(1000, session, 'data.csv', read.csv)
output$data <- renderTable({
fileData()
})
}
# 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)
shinyServer(function(input, output, session)) {
output$data <- renderTable({
fileData()
})
}
}
}
\seealso{
\code{\link{reactivePoll}}
}

View File

@@ -1,81 +0,0 @@
\name{reactivePoll}
\alias{reactivePoll}
\title{Reactive polling}
\usage{
reactivePoll(intervalMillis, session, checkFunc,
valueFunc)
}
\arguments{
\item{intervalMillis}{Approximate number of milliseconds
to wait between calls to \code{checkFunc}. This can be
either a numeric value, or a function that returns a
numeric value.}
\item{session}{The user session to associate this file
reader with, or \code{NULL} if none. If non-null, the
reader will automatically stop when the session ends.}
\item{checkFunc}{A relatively cheap function whose values
over time will be tested for equality; inequality
indicates that the underlying value has changed and needs
to be invalidated and re-read using \code{valueFunc}. See
Details.}
\item{valueFunc}{A function that calculates the
underlying value. See Details.}
}
\value{
A reactive expression that returns the result of
\code{valueFunc}, and invalidates when \code{checkFunc}
changes.
}
\description{
Used to create a reactive data source, which works by
periodically polling a non-reactive data source.
}
\details{
\code{reactivePoll} works by pairing a relatively cheap
"check" function with a more expensive value retrieval
function. The check function will be executed
periodically and should always return a consistent value
until the data changes. When the check function returns a
different value, then the value retrieval function will
be used to re-populate the data.
Note that the check function doesn't return \code{TRUE}
or \code{FALSE} to indicate whether the underlying data
has changed. Rather, the check function indicates change
by returning a different value from the previous time it
was called.
For example, \code{reactivePoll} is used to implement
\code{reactiveFileReader} by pairing a check function
that simply returns the last modified timestamp of a
file, and a value retrieval function that actually reads
the contents of the file.
As another example, one might read a relational database
table reactively by using a check function that does
\code{SELECT MAX(timestamp) FROM table} and a value
retrieval function that does \code{SELECT * FROM table}.
The \code{intervalMillis}, \code{checkFunc}, and
\code{valueFunc} functions will be executed in a reactive
context; therefore, they may read reactive values and
reactive expressions.
}
\examples{
\dontrun{
# Assume the existence of readTimestamp and readValue functions
shinyServer(function(input, output, session) {
data <- reactivePoll(1000, session, readTimestamp, readValue)
output$dataTable <- renderTable({
data()
})
})
}
}
\seealso{
\code{\link{reactiveFileReader}}
}

View File

@@ -42,7 +42,6 @@ values <- reactiveValues(a = 1, b = 2)
isolate(values$a)
}
\seealso{
\code{\link{isolate}} and
\code{\link{is.reactivevalues}}.
\code{\link{isolate}}.
}

View File

@@ -1,47 +0,0 @@
\name{showReactLog}
\alias{showReactLog}
\title{Reactive Log Visualizer}
\usage{
showReactLog()
}
\description{
Provides an interactive browser-based tool for
visualizing reactive dependencies and execution in your
application.
}
\details{
To use the reactive log visualizer, start with a fresh R
session and run the command
\code{options(shiny.reactlog=TRUE)}; then launch your
application in the usual way (e.g. using
\code{\link{runApp}}). At any time you can hit Ctrl+F3
(or for Mac users, Command+F3) in your web browser to
launch the reactive log visualization.
The reactive log visualization only includes reactive
activity up until the time the report was loaded. If you
want to see more recent activity, refresh the browser.
Note that Shiny does not distinguish between reactive
dependencies that "belong" to one Shiny user session
versus another, so the visualization will include all
reactive activity that has taken place in the process,
not just for a particular application or session.
As an alternative to pressing Ctrl/Command+F3--for
example, if you are using reactives outside of the
context of a Shiny application--you can run the
\code{showReactLog} function, which will generate the
reactive log visualization as a static HTML file and
launch it in your default browser. In this case,
refreshing your browser will not load new activity into
the report; you will need to call \code{showReactLog()}
explicitly.
For security and performance reasons, do not enable
\code{shiny.reactlog} in production environments. When
the option is enabled, it's possible for any user of your
app to see at least some of the source code of your
reactive expressions and observers.
}

View File

@@ -19,11 +19,9 @@
\item{max}{The maximum value (inclusive) that can be
selected.}
\item{value}{The initial value of the slider. A numeric
vector of length one will create a regular slider; a
numeric vector of length two will create a double-ended
range slider.. A warning will be issued if the value
doesn't fit between \code{min} and \code{max}.}
\item{value}{The initial value of the slider. A warning
will be issued if the value doesn't fit between
\code{min} and \code{max}.}
\item{step}{Specifies the interval between each
selectable value on the slider (\code{NULL} means no

View File

@@ -1,8 +1,6 @@
\name{tag}
\alias{tag}
\alias{tagAppendChild}
\alias{tagAppendChildren}
\alias{tagSetChildren}
\alias{tagList}
\title{
HTML Tag Object
@@ -18,14 +16,6 @@ sets of tags; see the contents of bootstrap.R for examples.
\code{tagAppendChild(tag, child)}
\code{tagAppendChildren(tag, child1, child2)}
\code{tagAppendChildren(tag, list = list(child1, child2))}
\code{tagSetChildren(tag, child1, child2)}
\code{tagSetChildren(tag, list = list(child1, child2))}
\code{tagList(...)}
}
@@ -48,10 +38,6 @@ sets of tags; see the contents of bootstrap.R for examples.
}
\item{...}{
Unnamed items that comprise this list of tags.
}
\item{list}{
An optional list of elements. Can be used with or instead of the \code{...}
items.
}
}