Compare commits

...

334 Commits

Author SHA1 Message Date
Joe Cheng
b6300f3a5c More robust setInternet2 workaround 2012-10-30 12:31:36 -07:00
Joe Cheng
cc0b5e5e0f Remove problematic link (fails R CMD check)
I first attempted to remove \code, but I couldn't figure out how to get the # in the URL to work right under both web-based help and PDF.
2012-10-29 11:56:59 -07:00
Joe Cheng
5c3f7d8f94 Update NEWS 2012-10-29 11:47:27 -07:00
Joe Cheng
8c3f8cd450 Add wellPanel and bootstrapPage functions 2012-10-29 11:45:58 -07:00
Joe Cheng
046582711a Update NEWS 2012-10-29 11:22:30 -07:00
Joe Cheng
15756ec92d Case insensitive probing for server.R, ui.R, et al 2012-10-29 11:19:23 -07:00
Joe Cheng
fc49abc9fb Fix issue #27: Warnings cause reactive functions to stop executing 2012-10-29 11:09:13 -07:00
Winston Chang
4a9ff27f3e Download gists in binary mode 2012-10-26 16:38:18 -05:00
Joe Cheng
790e6f370f Remove RCurl dependency 2012-10-26 14:07:07 -07:00
Joe Cheng
16ccc1321d Update docs 2012-10-26 10:46:42 -07:00
Joe Cheng
8648c94dd4 Update version to 0.1.8 2012-10-26 10:43:53 -07:00
Joe Cheng
dc4eb720ae Introduce input type hints
These allow the server to use custom deserialization code on a per-type basis.
2012-10-26 10:28:40 -07:00
Joe Cheng
0b891ad557 Run a GitHub gist 2012-10-25 20:41:52 -07:00
Joe Cheng
e96193ae28 Do .Random.seed restoring correctly 2012-10-24 23:19:13 -07:00
Joe Cheng
3ff9075959 Update NEWS 2012-10-24 21:11:56 -07:00
Joe Cheng
c03842056c Convert JSON to UTF-8
If reactivePrint or reactiveText return non-ASCII characters on
Windows, it causes invalid UTF-8 strings to be received by the
browser which closes the websocket connection.

I'm not sure this is the right place to do encoding, but it seems
to me like this approach is likely to work best for the most
users (especially those who just aren't thinking about encoding).
If you want to handle encoding in the reactives themselves (for
example), use `options(shiny.transcode.json=F)`.
2012-10-24 21:10:09 -07:00
Joe Cheng
6df226b21c Add repeatable utility function to stabilize RNGs 2012-10-24 16:12:08 -07:00
Joe Cheng
7dfa7d7426 Fix issue #26: Shiny.OutputBindings not correctly exported 2012-10-24 14:41:32 -07:00
Joe Cheng
b8b1a891cf Add custom message handler support, console logging
If the server sends a message with a "custom" field, that field's value will
be passed to a custom window.Shiny.oncustommessage function, if it is defined.

Also add support for messages like so:
{
  console: [
    'line one',
    'line two'
  ]
}

This will cause "line one" and "line two" to be printed at the browser console.
2012-10-04 17:45:20 -07:00
Joe Cheng
7df0e8b0f9 Update docs for 0.1.6 2012-09-25 03:08:31 -07:00
Joe Cheng
ff072ae9d9 bindAll should send initial values to server 2012-09-25 01:29:52 -07:00
Joe Cheng
f81ca39741 Add uiOutput. Tweak comments. 2012-09-25 00:33:00 -07:00
Joe Cheng
3db1f2a98c Don't animate showing/hiding of conditionalPanel 2012-09-21 19:51:24 -07:00
Joe Cheng
4865df9be1 Mark fileInput and reactiveUI as experimental. 2012-09-21 19:50:50 -07:00
Joe Cheng
0c16f2c334 Fix broken imports 2012-09-21 14:00:03 -07:00
Joe Cheng
d01149620f Fix issue #19: Checkboxes and radios can't be added dynamically 2012-09-19 11:48:28 -05:00
Joe Cheng
ab9401f390 Fix issue #20: DESCRIPTION file should use Imports instead of Depends 2012-09-19 11:47:12 -05:00
Joe Cheng
3223c17b74 Update websockets dependency version 2012-09-15 00:52:04 -07:00
Joe Cheng
404035bcf0 Bump version number 2012-09-14 19:16:03 -07:00
Joe Cheng
a0185bb0b4 Introduce shiny.http.response.filter option
Allows post-processing of HTTP responses
2012-09-14 13:15:58 -07:00
Joe Cheng
1a591cd9f1 conditionalPanel now triggers show/shown/hide/hidden event 2012-09-07 00:44:20 -07:00
Joe Cheng
e9b81b2033 [BREAKING] Simplify input binding callbacks
InputBinding.subscribe used to have to call callbacks with at least two arguments,
now there is only one optional argument (allowDeferred). The binding argument in
particular was problematic because it required "var self=this;".
2012-09-06 12:06:15 -07:00
Joe Cheng
cbfc1e8ed1 Add reactiveUI output type 2012-09-05 15:22:34 -07:00
Joe Cheng
cb63338805 Allow htmlOutput to contain inputs/outputs 2012-09-05 11:17:39 -07:00
Joe Cheng
bcdc82ccee Add conditionalPanel; JS API changes
- bindAll/unbindAll added
- bindInput/bindOutput/unbindInput/unbindOutput removed
2012-09-05 09:40:40 -07:00
Joe Cheng
76a4cf6c34 Update NEWS 2012-08-31 23:21:04 -07:00
Joe Cheng
872f23b0f0 Improvements for output binding/unbinding
- When bound, outputs receive cached error/value
- On binding, (potentially all) output plot sizes are resent
2012-08-31 23:12:20 -07:00
Joe Cheng
e61f7405fd Upload example app should accept text/plain files 2012-08-31 22:39:45 -07:00
Joe Cheng
0714871b56 Improve blob handling browser compatibility 2012-08-31 22:39:26 -07:00
Joe Cheng
8a89fb2a1a Expose and fix Shiny.unbindOutputs 2012-08-31 18:29:42 -07:00
Joe Cheng
036544e3ed Eagerly evaluate output name 2012-08-31 12:33:13 -07:00
Joe Cheng
7a6784d809 Add missing param to prototype method 2012-08-31 11:48:21 -07:00
Joe Cheng
ed9301705b Refactor JS to use more consistent OOP style
(function() { }).call(Foo.prototype) for extending prototypes manually, and
$.extend for extending objects manually or prototypes inheriting from each
other.
2012-08-31 10:00:20 -07:00
Joe Cheng
21f9694574 Add NEWS for file upload 2012-08-30 22:10:16 -07:00
Joe Cheng
3a0b11b89d Add file upload feature
This feature is currently pretty rough. It only works in the most modern
browsers (depends on HTML5 File API, including Blob.slice) and doesn't
show upload progress.
2012-08-30 22:07:00 -07:00
Joe Cheng
d5272e3e74 Update version 2012-08-30 12:27:05 -07:00
Joe Cheng
b5197869db Update NEWS 2012-08-30 12:18:46 -07:00
Joe Cheng
5f775db40a Enhancements to Shiny transport
- JS can now do remote procedure calls (with return value or exception), not just message passing
- RPC calls can include non-JSON-compatible binary data (not compatible with IE)
2012-08-30 12:16:12 -07:00
Joe Cheng
9b84b83627 Allow binding and unbinding of Shiny input/output 2012-08-30 12:04:17 -07:00
Joe Cheng
b0d9b5762a Don't use WebSocket constant, it's not on IE8 2012-08-24 11:28:46 -07:00
Joe Cheng
8d9fd402be Check inheritance properly 2012-08-23 18:07:09 -07:00
Joe Cheng
73a44a4f8e Packages can register their own URL namespace
Helpful for serving up custom stylesheets, CSS, images, etc.
2012-08-23 13:08:08 -07:00
Joe Cheng
a7dd62249e Add checkboxGroupInput control 2012-08-22 13:39:19 -07:00
Joe Cheng
42fac871fb Extensible websocket creation 2012-08-22 12:32:33 -07:00
Joe Cheng
2782bf6735 Execute sendPlotSize when anything is shown/hidden 2012-08-21 14:18:00 -07:00
Joe Cheng
f2a1ce4977 Update NEWS 2012-08-21 14:16:25 -07:00
Joe Cheng
c8969c4cc0 Upgrade to Bootstrap 2.1 2012-08-21 11:35:37 -07:00
Joe Cheng
cfefb4a07c Update NEWS 2012-08-20 17:16:23 -07:00
Joe Cheng
653509368b Let Bootstrap tabset send its selected tab as input 2012-08-20 17:01:41 -07:00
Joe Cheng
51b269f329 roxygen2 on my dev box
For some reason my machines can't agree on the order of the NAMESPACE file
2012-08-20 13:49:28 -07:00
Joe Cheng
c92d2cc32e Documentation for numericInput(step) 2012-08-20 13:46:36 -07:00
Joe Cheng
cb4091895a Fix S3 generic method consistency 2012-08-20 13:44:04 -07:00
Joe Cheng
b96bc5a710 Fix broken roxygen declaration 2012-08-20 13:43:49 -07:00
Joe Cheng
4ace825c58 Add step param to numericInput 2012-08-20 13:32:10 -07:00
Joe Cheng
589e0f7bb5 Bump version/date 2012-08-20 10:05:58 -07:00
Joe Cheng
347b9e1d7a Add NEWS 2012-08-20 10:05:16 -07:00
Joe Cheng
363633b01f Fix issue #10: Plots in tabsets not rendered 2012-08-20 09:54:50 -07:00
Joe Cheng
575350842a Fix broken progress indication 2012-08-18 00:01:38 -07:00
Joe Cheng
d49e8d172f Improvements to reactives and UI
- `input` object now implements names() and as.list()
- Simpler dependency tracking impl using Dependencies class
- New `singleton` function for making HTML content appear only once
- Fix issue #4: head deduplication should not be line-oriented
2012-08-18 00:01:16 -07:00
Joe Cheng
642d153202 Dynamic output bindings 2012-08-14 01:21:25 -07:00
Joe Cheng
8cf7d8b4cb Input binding enhancements
- Add textarea binding
- Deterministic priority ordering
- Allow getting/setting priorities for existing bindings
2012-08-14 00:58:56 -07:00
Joe Cheng
b0b7cfa3a5 Remove comment cruft 2012-08-13 14:45:49 -07:00
Joe Cheng
3692d5f008 Delay Shiny init to after document-ready
Also remove some dead code
2012-08-13 11:51:53 -07:00
Joe Cheng
2344dc04a5 Fix bug in deferred submission 2012-08-13 10:09:44 -07:00
Joe Cheng
cf37072bed Don't debounce when animating 2012-08-13 10:04:06 -07:00
Joe Cheng
cc5c933e7d Use InputBinding for sliders 2012-08-09 17:13:14 -07:00
Joe Cheng
ad1737f16b Infrastructure for extensible inputs 2012-08-09 16:38:21 -07:00
Joe Cheng
2ac1895a6e Inputs without names shouldn't be sent 2012-08-08 17:13:19 -07:00
Joe Cheng
4dc7630adc Cleanup code, exports, radio values
- Radio values based on id could not be kept in sync because implicitly deselected radios don't trigger a change event. So don't pass id-based values for radios at all (still works for checkboxes though)
- Make onInputChange available in a Shiny namespace on the window
- Remove no longer used debounce/throttle code
2012-08-07 15:25:00 -07:00
Joe Cheng
66a3d68755 Hook up modular input pipeline 2012-08-07 15:25:00 -07:00
Joe Cheng
ce9213cdc1 Infrastructure for more flexible input handling 2012-08-07 15:25:00 -07:00
Joe Cheng
99b1ed51a6 Update install instructions
Adding ourselves to the repo list means we don't have to serve up
all the CRAN dependencies on our repo.
2012-08-06 15:40:05 -07:00
Joe Cheng
c7dcff56c7 New, simpler install instructions 2012-08-02 18:11:49 -07:00
Joe Cheng
fcdb8f08b8 Bump version 2012-08-02 14:02:32 -07:00
Joe Cheng
daa03999b6 Fix error when no ui.R file exists
Error message was:
'Error: argument "handler is missing, with no default'

Previously I was intentionally allowing the main dynamicHandler code to run, intending to allow ui.R to be created even after the application started. Hopefully I can bring that capability back when I figure out more deeply why the error is happening.
2012-08-02 13:55:32 -07:00
JJ Allaire
cd7c5dc048 add .Rprofile to gitignore 2012-07-30 06:55:25 -07:00
JJ Allaire
09f0f85b9c update README with installation instructions 2012-07-30 08:02:57 -04:00
JJ Allaire
8aee7563f0 bump version to 0.1.1 2012-07-30 07:27:07 -04:00
Joe Cheng
6d6c8cecd6 Fix path bug on Windows 2012-07-29 18:39:10 -07:00
JJ Allaire
334f6c8757 sync readme to welcome 2012-07-29 11:00:03 -07:00
Joe Cheng
8455343fee Only instantiate sliders if sliders are loaded 2012-07-29 11:04:27 -07:00
JJ Allaire
d234ab016f tweaks to readme 2012-07-29 08:54:43 -07:00
JJ Allaire
a312b46e97 man page for shiny-package 2012-07-29 08:32:55 -07:00
JJ Allaire
ff06c7997b update package DESCRIPTION 2012-07-29 08:12:53 -07:00
JJ Allaire
3dc6d84d1f docs for output elements 2012-07-29 07:39:44 -07:00
JJ Allaire
ef74483ebb fix types in bootstrap docs 2012-07-29 07:39:24 -07:00
JJ Allaire
d8cf7bcbf8 docs for tabsets 2012-07-29 07:02:04 -07:00
JJ Allaire
33336a7ad2 docs for text and numeric inputs 2012-07-29 06:21:26 -07:00
JJ Allaire
79865b39d1 docs for radio buttons and submit button 2012-07-29 06:12:52 -07:00
JJ Allaire
375125e992 additional control docs 2012-07-29 06:03:39 -07:00
JJ Allaire
ebc5a992dc remove docs for startApp 2012-07-29 05:40:48 -07:00
JJ Allaire
da01184fc9 remove more unexported functions from docs 2012-07-29 05:36:13 -07:00
JJ Allaire
e0a6a6c558 remove internal functions from docs 2012-07-29 05:34:48 -07:00
JJ Allaire
93ec81bc57 break tag/tagAppendChild out into a separate help topic 2012-07-29 05:27:57 -07:00
JJ Allaire
29295fa8a7 add client param (rd file checkin) 2012-07-29 05:15:42 -07:00
JJ Allaire
74e7130bee fix typo in main panel docs 2012-07-29 05:15:08 -07:00
JJ Allaire
30cd83662a add docs for client param to registerClient 2012-07-29 05:14:49 -07:00
JJ Allaire
5f8f3ca328 fix type in mainPanel example 2012-07-29 05:14:32 -07:00
JJ Allaire
5744f1c7ee don't export tagWrite or tagWriteChildren 2012-07-29 04:57:24 -07:00
JJ Allaire
ba05f03359 simple docs shims for clearClients and registerClient 2012-07-29 04:54:44 -07:00
JJ Allaire
43c9ed0655 document top level shiny ui defintion functions 2012-07-29 04:40:57 -07:00
JJ Allaire
43fa8f53d3 eliminate problematic usage section for HTML Builder Functions 2012-07-29 04:16:36 -07:00
JJ Allaire
258dad0389 change usage of tags in docs (was yielding a warning) 2012-07-29 04:13:50 -07:00
JJ Allaire
5d5eaa2065 update namespace 2012-07-29 03:45:51 -07:00
Joe Cheng
1329136792 Get rid of R CMD CHECK warnings 2012-07-28 14:26:13 -07:00
Joe Cheng
c6cbcff9ce Document HTML function 2012-07-28 14:25:53 -07:00
Joe Cheng
ed2e637596 Fix bug where HTML() nodes were still being escaped 2012-07-28 14:24:54 -07:00
Joe Cheng
c97aecf9ff Document and enhance builder functions
Add easy string conversion and printing
2012-07-28 14:00:50 -07:00
JJ Allaire
9672088158 remove helpText from animation example 2012-07-28 11:12:23 -04:00
JJ Allaire
995908d3c6 remove docs since they have been folded into the tutorial 2012-07-28 11:04:49 -04:00
JJ Allaire
74314457ba update readme 2012-07-28 05:06:22 -07:00
Joe Cheng
d64c99ed28 Fix broken Rd link 2012-07-28 01:54:33 -07:00
Joe Cheng
38bf13e9bf Add doc for sliderInput's animate param 2012-07-28 01:52:41 -07:00
Joe Cheng
4101c1efd0 Rd docs for observe, reactive, reactiveTimer
Also improve some error messages
2012-07-28 01:47:19 -07:00
Joe Cheng
f095700485 Rd docs for runApp, runExample, shinyServer 2012-07-28 01:17:21 -07:00
Joe Cheng
4ff1c95083 Slider and animation docs 2012-07-27 17:46:15 -07:00
Joe Cheng
c3e14933e2 Use simpler output format for 05_sliders 2012-07-27 14:46:11 -07:00
Joe Cheng
1b3cf52a17 More control over slider animation
- Slider now takes animate argument that expects NULL, TRUE, FALSE, or a list that can be constructed using animationOptions()
- Update examples/05_sliders to use new animation format
- Tweak spacing around slider
2012-07-27 14:45:22 -07:00
Joe Cheng
e2f8163b21 Change git URL to SSH-style 2012-07-27 14:34:35 -07:00
Joe Cheng
54d3e1a5e1 Serialize logical attrib values using lowercase 2012-07-27 14:05:40 -07:00
Joe Cheng
57e088f6e1 Implicit initialization of jslider 2012-07-27 14:05:12 -07:00
Joe Cheng
c759dcd7df Add htmlOutput function 2012-07-27 13:21:08 -07:00
Joe Cheng
033eb41a1d Make slider send only 1 event per animation frame 2012-07-27 13:21:08 -07:00
JJ Allaire
77f6e138ac shorten first readme bullet 2012-07-27 13:17:32 -07:00
JJ Allaire
c5c70b0f49 bold recommended 2012-07-27 13:16:41 -07:00
JJ Allaire
6b37e026fd update readme 2012-07-27 13:15:33 -07:00
JJ Allaire
731018082b Merge branch 'master' of github.com:rstudio/shiny 2012-07-27 12:57:26 -07:00
JJ Allaire
a7eab9f00e use c for install packages 2012-07-27 12:57:19 -07:00
Joe Cheng
0d3aebc077 Slider improvements
- Get rid of smooth--it doesn't make sense for our purposes since we always provide step
- Don't do any rounding by default (this required changes in jslider)
- Switch order of format and locale arguments
- Animation should pause automatically when it reaches the end
- Default to 1s animation interval
- If animation is started when sliders are at the end, restart
- Animation button click target ran the width of the slider
2012-07-27 11:52:57 -07:00
JJ Allaire
fb37e3254d updated readme 2012-07-27 11:40:21 -07:00
JJ Allaire
6d9da1260a add comments to sliders example 2012-07-27 09:53:32 -07:00
JJ Allaire
0d749f333a don't use as.integer since it's no longer required 2012-07-27 09:34:03 -07:00
JJ Allaire
338463057c initial code for slider example 2012-07-27 09:05:23 -07:00
JJ Allaire
35c131f661 use span for textOutput 2012-07-27 08:05:52 -07:00
Joe Cheng
da6771eaae Back out accidentally-committed test code 2012-07-27 01:37:24 -07:00
Joe Cheng
fbf3623343 Add rudimentary animation to sliders 2012-07-27 01:35:09 -07:00
Joe Cheng
2d43817b2f Slider improvements, typed input values
- Slider now has 'smooth' parameter that, if false, snaps slider to step
- Two-handle slider (provide a vector of length 2 to value=)
- Slider and number inputs yield numeric values
2012-07-26 17:43:45 -07:00
Joe Cheng
01905c51dd Expose more slider options, add tick logic 2012-07-26 17:43:45 -07:00
JJ Allaire
84494b8a0a dont import datasets into mpg ui 2012-07-26 10:12:50 -07:00
JJ Allaire
aded289558 use reactiveText where appropriate 2012-07-26 10:07:35 -07:00
JJ Allaire
f1462fa0d2 fix spelling error 2012-07-26 09:21:41 -07:00
JJ Allaire
5cfd546b2a change indentation for tabset app 2012-07-26 09:18:14 -07:00
JJ Allaire
3b38792481 user new header panel syntax for tabsets 2012-07-26 07:27:04 -07:00
JJ Allaire
31b347e8dd headerPanel now includes a title element and just inserts a plain h1 2012-07-26 07:22:41 -07:00
JJ Allaire
d87149ab5d roxygenzie 2012-07-25 19:01:49 -07:00
JJ Allaire
fd2f4789d3 inlcude radioButtons in example 6 2012-07-25 19:01:38 -07:00
Joe Cheng
0d8d35743d Observers defer first execution until flushReact
This allows us to greatly simplify the way outputs are defined
2012-07-25 16:07:40 -07:00
Joe Cheng
b5a65040b3 Comment tweaks 2012-07-25 15:50:11 -07:00
Joe Cheng
d44289f036 reactiveText -> reactivePrint 2012-07-25 15:30:53 -07:00
Joe Cheng
cb4b45aff1 Support radio/checkbox; unlist input lists when unnamed 2012-07-25 14:53:08 -07:00
JJ Allaire
0f4851e77d add comment noting immediate rendering of caption 2012-07-25 14:44:54 -07:00
JJ Allaire
42fe86e024 add comments for examples 7 and 8 2012-07-25 14:42:50 -07:00
JJ Allaire
3bb0ebb98f add comments for example 6 2012-07-25 14:33:49 -07:00
JJ Allaire
391310faa5 add main server comment for examples 3 and 4 2012-07-25 14:26:56 -07:00
JJ Allaire
ab0552f409 different prefix for output comments 2012-07-25 14:21:04 -07:00
JJ Allaire
8a6f59e350 add comments to example 4 2012-07-25 14:19:38 -07:00
JJ Allaire
8e859e53c2 add comments to examples 2 and 3 2012-07-25 14:12:52 -07:00
JJ Allaire
a44e475451 add comments to example 1 2012-07-25 14:02:31 -07:00
JJ Allaire
f958839af1 load mpgData at startup 2012-07-25 11:16:04 -07:00
JJ Allaire
f741851250 eliminate animation example 2012-07-25 11:01:18 -07:00
JJ Allaire
acd68b5de8 more widgets example 2012-07-25 10:10:03 -07:00
JJ Allaire
466ea7277f correct handling of variable inputs for helpText and HTML functions 2012-07-25 10:09:39 -07:00
JJ Allaire
c80072a62e example 2 should use a reactive function 2012-07-25 10:09:03 -07:00
JJ Allaire
bc0a37e8da export radioButtons function 2012-07-25 10:07:09 -07:00
JJ Allaire
a323f40da2 stubs for examples we haven't built yet 2012-07-25 09:36:42 -07:00
JJ Allaire
ee05e6ba03 use numeric sequences for example directories 2012-07-25 09:32:32 -07:00
JJ Allaire
ae9ef5c13f use mpg dataset for user example 2012-07-25 12:20:27 -04:00
JJ Allaire
fcc90df31c implement radioButtons (note that initial value isn't correctly sent right now) 2012-07-25 11:53:50 -04:00
JJ Allaire
d6b6719b54 rename model function to formulaText 2012-07-25 11:15:33 -04:00
JJ Allaire
21e8af827f add titanic example 2012-07-25 09:42:53 -04:00
JJ Allaire
5e5d233d83 add library(datasets) where required 2012-07-25 09:20:55 -04:00
JJ Allaire
214fd92b12 add html ui example 2012-07-25 09:14:20 -04:00
JJ Allaire
3687790730 remove br tags from reactivity example 2012-07-25 08:57:33 -04:00
JJ Allaire
d0f86078aa add tabsets example 2012-07-25 08:51:18 -04:00
JJ Allaire
649cb69466 allow helpText to take multiple strings 2012-07-25 08:47:38 -04:00
JJ Allaire
2f342e7664 remove allcaps and hash examples 2012-07-25 08:40:29 -04:00
JJ Allaire
e4fccc2f84 add reactivity example 2012-07-25 08:37:10 -04:00
JJ Allaire
61bd2d356b add text example 2012-07-25 08:07:36 -04:00
JJ Allaire
66ddb6ce0a make min and max optional for numeric input 2012-07-25 08:06:23 -04:00
JJ Allaire
573b3b1dfd tweak hello initial value 2012-07-25 08:04:58 -04:00
JJ Allaire
560bd3ca85 use condensed style for tables 2012-07-25 02:48:02 -07:00
Joe Cheng
1f5fe5b570 Use Sys.time instead of C code 2012-07-24 22:09:23 -07:00
Joe Cheng
d18d2df417 More robust runExample logic 2012-07-24 21:53:16 -07:00
Joe Cheng
91731a86bf Fix CSS for jslider 2012-07-24 18:47:14 -07:00
Joe Cheng
7108761e8f Bootstrap-styled tables
Other UI packages can override the table styles by using the option shiny.table.class.
2012-07-24 18:36:02 -07:00
Joe Cheng
0fe8bacf73 Integrate slider, more efficient input event handling 2012-07-24 18:20:11 -07:00
Joe Cheng
ef1afb482f common.R => global.R 2012-07-24 14:12:44 -07:00
JJ Allaire
134a3de256 hello sample app 2012-07-24 13:32:04 -07:00
JJ Allaire
71975546cb add support for select multiple attribute 2012-07-24 13:26:49 -07:00
Joe Cheng
b4c02f42f7 Add support for progress indication
The CSS class 'recalculating' will be added to any output elements whose content might be affected by a change to one or more of the inputs.
2012-07-24 10:45:00 -07:00
JJ Allaire
da7210f43f rename server function to shinyServer 2012-07-24 02:53:48 -07:00
Joe Cheng
8b4d62e374 Error handling support (very basic) 2012-07-23 17:10:19 -07:00
JJ Allaire
b68da2c3d3 add jslider component 2012-07-23 13:54:01 -07:00
JJ Allaire
b2db41c7f4 remove submit button from example 3 2012-07-23 12:08:54 -07:00
JJ Allaire
c4922d1655 insert name/value handling for selectList (now has same behavior as manipulate) 2012-07-23 12:08:26 -07:00
JJ Allaire
94ca77e697 remove html tags from example 3 2012-07-23 11:43:43 -07:00
JJ Allaire
c1d076ef79 change name of selectListInput to selectInput 2012-07-23 11:43:09 -07:00
JJ Allaire
39c69a4aff bind directly to shiny css class names 2012-07-23 11:39:16 -07:00
JJ Allaire
5a0921ed74 flesh out Readme.md 2012-07-23 11:23:44 -07:00
JJ Allaire
68c668615f add runExample function for easily running examples from within the tutorial 2012-07-23 09:01:36 -07:00
JJ Allaire
e1d5876ae6 move doc and examples to inst directory 2012-07-23 08:59:29 -07:00
JJ Allaire
741910407f change applicationPage to pageWithSidebar 2012-07-23 08:50:21 -07:00
JJ Allaire
39d4befc54 make tags module responsible for the export of the HTML function 2012-07-23 08:49:56 -07:00
JJ Allaire
d13505ce91 eliminate withTags construct 2012-07-23 08:42:45 -07:00
JJ Allaire
8c6d586fb0 fix HTML function 2012-07-23 06:04:31 -07:00
JJ Allaire
f66c2967dd use panel suffix for tab components 2012-07-23 05:53:20 -07:00
JJ Allaire
ef44a2295f export html tags from shinyui module rather than tags module 2012-07-23 05:09:50 -07:00
JJ Allaire
6186231041 unify naming convention for tags module 2012-07-23 04:59:17 -07:00
JJ Allaire
25ec5550b5 remove jslider component 2012-07-23 03:15:54 -07:00
JJ Allaire
1f93610a95 add COPYING and NOTICE files 2012-07-23 02:10:04 -07:00
JJ Allaire
01cde51a71 liveText should not include a label (should be done at a higher level in the system) 2012-07-23 01:53:32 -07:00
JJ Allaire
7d054c11de submit button: change default caption and ensure it is wrapped in a block element 2012-07-22 09:21:15 -07:00
JJ Allaire
98f717d5b4 add comment on slider control source/dependencies 2012-07-22 07:52:20 -07:00
JJ Allaire
6f315144cc factor slider into core slider function and sliderInput wrapper for integration into bootstrap forms 2012-07-22 07:47:36 -07:00
JJ Allaire
9ba8f569db merge 2012-07-22 10:15:20 -04:00
JJ Allaire
51f169571f add primary style to submit button 2012-07-22 10:14:14 -04:00
JJ Allaire
b6a9ffb4c7 initial implementaiton of slider control 2012-07-22 10:13:58 -04:00
JJ Allaire
346612aac1 initial implementaiton of slider control 2012-07-22 10:11:47 -04:00
JJ Allaire
205144d92d add support for form submit button 2012-07-21 18:40:33 -07:00
JJ Allaire
af2e321f45 add helpText widget 2012-07-21 18:31:32 -07:00
JJ Allaire
e22a20701b automatically generate ids for tabsets 2012-07-21 13:28:05 -07:00
JJ Allaire
f3edde8f81 add HTML function for including raw html 2012-07-21 13:12:50 -07:00
JJ Allaire
f405a0c905 perform html escaping of attribs and text 2012-07-21 12:50:51 -07:00
JJ Allaire
4907df497f use more natural attribute names now that we need to use withTags less often 2012-07-21 12:31:51 -07:00
JJ Allaire
e551c42f32 export some additional commonly used html tags 2012-07-21 12:18:12 -07:00
JJ Allaire
0c1a235cc1 ensure that attribute names don't conflict with tag names 2012-07-21 12:03:44 -07:00
JJ Allaire
5384b3a8c0 explicitliy specify h1 element in headers 2012-07-21 12:03:00 -07:00
JJ Allaire
0acb5f5857 export a small set of text and heading oriented html tags 2012-07-21 11:45:34 -07:00
JJ Allaire
cee124a4d6 use padding rather than br for header panel 2012-07-21 11:44:42 -07:00
JJ Allaire
084b983b44 change name of application to applicationPage so as to be less likely to conflict with future apis 2012-07-21 11:21:58 -07:00
JJ Allaire
bf15948275 add some padding to the top of the header panel 2012-07-21 11:21:23 -07:00
JJ Allaire
8796875128 change name of createTag function to tag 2012-07-21 10:55:48 -07:00
JJ Allaire
af9c2b1449 rename shiny core output functions with live prefix 2012-07-20 18:50:22 -07:00
Joe Cheng
f0d6b6f558 Hot-reload of server.R 2012-07-20 15:59:56 -07:00
Joe Cheng
3778e01d7c Hot-reload of ui.R 2012-07-20 15:16:05 -07:00
JJ Allaire
70ebad0410 rename client.R to ui.R 2012-07-20 14:01:57 -07:00
JJ Allaire
7cf58bd864 improve names of functions in tags.R 2012-07-20 13:29:53 -07:00
JJ Allaire
5858483fca use withTags where appropriate 2012-07-20 13:20:38 -07:00
JJ Allaire
fb94d2a99c Merge branch 'master' of github.com:rstudio/shiny 2012-07-20 12:42:15 -07:00
JJ Allaire
55b5441f00 implement withTags 2012-07-20 12:42:08 -07:00
Joe Cheng
bf397e496c Add option for printing websocket traffic 2012-07-20 12:06:42 -07:00
Joe Cheng
a78ae8ca4a Don't plot unless width and height are positive 2012-07-20 12:06:42 -07:00
JJ Allaire
c635b92991 re-organize bootstrap.R 2012-07-20 12:06:31 -07:00
JJ Allaire
53d406f640 eliminate labelOnTop option 2012-07-20 11:59:38 -07:00
JJ Allaire
701f4b743b rename functions to clarify shiny core vs. bootstrap 2012-07-20 11:52:10 -07:00
JJ Allaire
7466baf1b2 support for otuput tabsets 2012-07-20 11:31:58 -07:00
JJ Allaire
13ecf8ef21 use bootstrap for example 3 2012-07-20 08:53:04 -07:00
JJ Allaire
c946a3973a use standard html attribute names for components whenever possible 2012-07-20 08:04:31 -07:00
JJ Allaire
615f265c00 bootstrap for examples 1 and 2 2012-07-20 07:52:35 -07:00
JJ Allaire
4177ba7840 eliminate withTags (couldn't get it to work properly) 2012-07-20 05:11:33 -07:00
JJ Allaire
393593b2d2 roxygenize 2012-07-19 19:01:22 -07:00
Joe Cheng
e736c3949a Use new client.R/server.R scheme 2012-07-19 14:26:01 -07:00
JJ Allaire
e1509e7db3 recursively include lists of lists of tags 2012-07-19 13:12:37 -07:00
JJ Allaire
49150b07fd correctly handle lists of tags 2012-07-19 13:03:25 -07:00
JJ Allaire
1d8f1b4c6a unpack var args before calling createTag 2012-07-19 12:57:09 -07:00
JJ Allaire
833f0c67cf Merge branch 'master' of github.com:rstudio/shiny 2012-07-19 12:48:20 -07:00
JJ Allaire
4b559b5a94 break tags into their own namespace 2012-07-19 12:48:15 -07:00
Joe Cheng
55c8d60cfb Add Bootstrap 2.0.4 to shared resources 2012-07-19 11:14:40 -07:00
JJ Allaire
0e129379e9 use html builder for example 2 2012-07-19 08:44:35 -07:00
JJ Allaire
7e3f704285 add textInput function 2012-07-19 08:10:04 -07:00
JJ Allaire
d8a595ac70 change clientUI -> clientPage 2012-07-19 06:57:00 -07:00
JJ Allaire
c13cb9b723 remove example code from ui.R 2012-07-19 06:41:43 -07:00
JJ Allaire
8cc83855b9 replace defineUI and page functions with single clientUI function (page conflicted with base::page and having a single function seemed simpler) 2012-07-19 06:41:16 -07:00
JJ Allaire
faebbf5753 Merge branch 'master' of github.com:rstudio/shiny 2012-07-19 06:38:03 -07:00
JJ Allaire
3e297bad1f use withHeadTags function rather than head directly (since it conflicted with base::head) 2012-07-19 06:33:07 -07:00
JJ Allaire
f56949dd0b use withHeadTags function rather than head directly (since it conflicted with base::head) 2012-07-19 06:08:07 -07:00
Joe Cheng
04081ec2d3 Integrate UI builder into Shiny
Replace example #1 HTML with builder
2012-07-18 15:27:27 -07:00
JJ Allaire
442f3d93c6 Merge branch 'master' of github.com:rstudio/shiny 2012-07-18 17:53:08 -04:00
Joe Cheng
b41d9bff51 HTML escaping utility function 2012-07-18 14:44:44 -07:00
JJ Allaire
7e1cd68cb4 comment out example/demo code 2012-07-18 17:06:16 -04:00
JJ Allaire
47675633d2 only self-close void elements 2012-07-18 16:54:02 -04:00
JJ Allaire
8e59834989 Merge branch 'master' of github.com:rstudio/shiny 2012-07-18 16:36:25 -04:00
JJ Allaire
a87c3cdb88 add doctype and charset to html header 2012-07-18 16:36:17 -04:00
Joe Cheng
b2f9903e18 Allow dynamic rendering of front-ends 2012-07-18 13:04:35 -07:00
JJ Allaire
a48c8056f2 allow attributes without values via NA 2012-07-18 15:56:01 -04:00
JJ Allaire
dfd6b85296 defer varargs processing until tag function 2012-07-18 15:43:58 -04:00
JJ Allaire
f3aed1bd53 more work on html builder 2012-07-18 15:41:36 -04:00
Joe Cheng
41716d160b Change startApp/runApp signature
Also change example apps to launch directly using runApp
2012-07-18 09:00:32 -07:00
Joe Cheng
bd87be2f7e Tweak docs 2012-07-18 09:00:32 -07:00
JJ Allaire
9bd4ad6e47 first stab at html generation syntax 2012-07-18 08:56:08 -07:00
JJ Allaire
9bd0c01bdd more scaffolding for ui module 2012-07-18 03:32:04 -07:00
JJ Allaire
7dc6b4035a add initial scaffolding for ui module 2012-07-18 03:25:40 -07:00
JJ Allaire
3a65b9e0e5 update reactivePlot docs 2012-07-18 03:25:14 -07:00
Joe Cheng
569b98c724 Update Example 3 to use auto-sized plot 2012-07-17 23:00:48 -07:00
Joe Cheng
3de022ba05 Add autosizing to reactive plots
Autosizing meaning the plot's HTML tag's clientside width and height will automatically be used by the renderer
2012-07-17 23:00:32 -07:00
Joe Cheng
b697718826 Add client throttle/debounce support
Also add 500ms debounce to input keyup/change
2012-07-17 22:58:57 -07:00
Joe Cheng
a16f7b34ab Allow output functions to access shinyapp and their name 2012-07-17 22:57:47 -07:00
Joe Cheng
0660ddbfbf Values keys that start with . were not reactive 2012-07-17 22:28:22 -07:00
Joe Cheng
f1a4bf4dd7 Allow deferred submission of input 2012-07-16 16:28:53 -07:00
Joe Cheng
06c319d1aa remove launchApp 2012-07-16 11:24:39 -07:00
Joe Cheng
2d89749c9b Include docs for launch.browser param 2012-07-16 09:37:12 -07:00
Joe Cheng
696bee13af Better interactive app lifecycle management
- When runApp returns, close the server socket
- Optionally launch browser when runApp is called
2012-07-16 09:30:35 -07:00
Joe Cheng
c5b835186c Keep R responsive when running interactively 2012-07-16 09:12:49 -07:00
Joe Cheng
ea3c1dacea Take version number out of jquery filename 2012-07-16 09:12:49 -07:00
JJ Allaire
7de29090db add launchApp function to run an app asynchronously and open it in a web browser 2012-07-16 06:18:57 -07:00
JJ Allaire
d982d15fbc sleep for 100ms around calls to serviceApp (makes huge improvement in IDE responsiveness while apps are running) 2012-07-16 05:30:48 -07:00
Joe Cheng
4455810b5b Ignore Mac build directories 2012-07-13 12:23:00 -07:00
Joe Cheng
00a8372a74 Fix some Rd formatting issues 2012-07-13 02:29:19 -07:00
Joe Cheng
108dd4ff24 Add invalidateLater API call
Provides a simpler mechanism for doing time-based invalidation of reactive functions.
2012-07-13 02:29:03 -07:00
Joe Cheng
8a687851f2 Allow multiple clients to connect; doc improvements
- Multiple clients can now connect on a single port, and each one gets a unique shinyapp instance
- Improve docs for reactiveXXX functions
- Simplify interface for running an app
2012-07-13 00:29:17 -07:00
Joe Cheng
52394d61bf Add time infrastructure, reactiveTimer 2012-07-12 16:36:32 -07:00
Joe Cheng
270d97f3db Merge remote-tracking branch 'upstream/master'
Conflicts:
	shiny.Rproj
2012-07-12 10:56:20 -07:00
Joe Cheng
c5b7e549ec Pass input/output args to app func 2012-07-12 01:59:50 -07:00
Joe Cheng
891a93a7a3 Update gitignore 2012-07-12 01:49:00 -07:00
Joe Cheng
13c7800c8c Add function for getting sys time in millis 2012-07-12 01:46:02 -07:00
JJ Allaire
e89f5de680 add build type to project file 2012-07-10 12:45:30 -04:00
Joe Cheng
c4fdd04fb4 Allow running in package form 2012-07-04 14:24:58 -07:00
Joe Cheng
500501497f Roxygenize 2012-07-04 14:11:35 -07:00
Joe Cheng
4106161753 Initial pass at packaging 2012-07-02 12:15:44 -07:00
Joe Cheng
8ce5a23c4b Rename flush.react to flushReact 2012-07-02 12:03:15 -07:00
Joe Cheng
5c524af472 Use camel case for all functions and fields 2012-06-29 17:09:07 -07:00
Joe Cheng
4b1123c4e4 Simplify output API 2012-06-29 15:53:10 -07:00
Joe Cheng
c3268d0362 Simplify API 2012-06-29 09:34:15 -07:00
Joe Cheng
f3fa9883aa Don't crash on errors in callbacks 2012-06-28 22:33:21 -07:00
Joe Cheng
8cf7ec9738 Drop 'shiny' from func names; layout changes 2012-06-28 22:27:22 -07:00
Joe Cheng
7c3a92662f Make websocket URL port independent 2012-06-27 14:50:18 -07:00
Joe Cheng
e05358db1d Add summary to example 3 2012-06-27 14:43:56 -07:00
Joe Cheng
74d450703c Add script for windows 2012-06-27 11:38:33 -07:00
Joe Cheng
aee4f3780c Stop using private functions from websockets 2012-06-27 11:19:32 -07:00
Joe Cheng
3aa0702ff8 Plots can now use <img> 2012-06-27 11:10:22 -07:00
Joe Cheng
cc51dbd4e6 Improve default table styles 2012-06-27 10:44:49 -07:00
Joe Cheng
228a83e0a7 Ignore .Rhistory 2012-06-27 00:01:25 -07:00
Joe Cheng
ee1ed1e9e5 Update README, add proj file 2012-06-27 00:00:55 -07:00
Joe Cheng
6a394cc30e Remove Ruby implementation... *sniff* 2012-06-26 21:56:13 -07:00
117 changed files with 16593 additions and 1029 deletions

9
.Rbuildignore Normal file
View File

@@ -0,0 +1,9 @@
^\.Rproj\.user$
^\.git$
^examples$
^README\.md$
^shiny\.Rproj$
^shiny\.sh$
^shiny\.cmd$
^run\.R$
^\.gitignore$

11
.gitignore vendored
View File

@@ -1,4 +1,9 @@
vendor/ruby
\.bundle/
\.DS_Store
.DS_Store
.Rproj.user
.Rhistory
.Rprofile
*.o
*.so
/src-i386/
/src-x86_64/
README.html

39
DESCRIPTION Normal file
View File

@@ -0,0 +1,39 @@
Package: shiny
Type: Package
Title: Web Application Framework for R
Version: 0.1.8
Date: 2012-10-26
Author: RStudio, Inc.
Maintainer: Joe Cheng <joe@rstudio.org>
Description: 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 powerful applications with minimal effort.
License: GPL-3
Depends:
R (>= 2.14.1), websockets (>= 1.1.5)
Imports:
stats,
tools,
utils,
datasets,
methods,
caTools,
RJSONIO,
xtable,
digest
URL: https://github.com/rstudio/shiny, http://rstudio.github.com/shiny/tutorial
BugReports: https://github.com/rstudio/shiny/issues
Collate:
'map.R'
'random.R'
'timer.R'
'tags.R'
'react.R'
'reactives.R'
'fileupload.R'
'shiny.R'
'shinywrappers.R'
'shinyui.R'
'slider.R'
'bootstrap.R'

View File

@@ -1,5 +0,0 @@
source 'https://rubygems.org'
gem 'em-websocket'
gem 'eventmachine_httpserver'
gem 'json'

View File

@@ -1,18 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.2.8)
em-websocket (0.3.6)
addressable (>= 2.1.1)
eventmachine (>= 0.12.9)
eventmachine (0.12.10)
eventmachine_httpserver (0.2.1)
json (1.7.3)
PLATFORMS
ruby
DEPENDENCIES
em-websocket
eventmachine_httpserver
json

80
NAMESPACE Normal file
View File

@@ -0,0 +1,80 @@
export(a)
export(addResourcePath)
export(animationOptions)
export(bootstrapPage)
export(br)
export(checkboxGroupInput)
export(checkboxInput)
export(code)
export(conditionalPanel)
export(div)
export(em)
export(fileInput)
export(h1)
export(h2)
export(h3)
export(h4)
export(h5)
export(h6)
export(headerPanel)
export(helpText)
export(HTML)
export(htmlOutput)
export(img)
export(invalidateLater)
export(mainPanel)
export(numericInput)
export(p)
export(pageWithSidebar)
export(plotOutput)
export(pre)
export(radioButtons)
export(reactive)
export(reactivePlot)
export(reactivePrint)
export(reactiveTable)
export(reactiveText)
export(reactiveTimer)
export(reactiveUI)
export(repeatable)
export(runApp)
export(runExample)
export(runGist)
export(selectInput)
export(shinyServer)
export(shinyUI)
export(sidebarPanel)
export(singleton)
export(sliderInput)
export(span)
export(strong)
export(submitButton)
export(tableOutput)
export(tabPanel)
export(tabsetPanel)
export(tag)
export(tagAppendChild)
export(tagList)
export(tags)
export(textInput)
export(textOutput)
export(uiOutput)
export(verbatimTextOutput)
export(wellPanel)
import(caTools)
import(digest)
import(RJSONIO)
import(websockets)
import(xtable)
S3method(as.character,shiny.tag)
S3method(as.character,shiny.tag.list)
S3method(as.list,reactvaluesreader)
S3method(format,shiny.tag)
S3method(format,shiny.tag.list)
S3method(names,reactvaluesreader)
S3method(print,shiny.tag)
S3method(print,shiny.tag.list)
S3method(reactive,default)
S3method(reactive,"function")
S3method("$",reactvaluesreader)
S3method("$<-",shinyoutput)

78
NEWS Normal file
View File

@@ -0,0 +1,78 @@
shiny 0.1.8
--------------------------------------------------------------------------------
* Add `runGist` function for conveniently running a Shiny app that is published
on gist.github.com.
* Fix issue #27: Warnings cause reactive functions to stop executing.
* The server.R and ui.R filenames are now case insensitive.
* Add `wellPanel` function for creating inset areas on the page.
* Add `bootstrapPage` function for creating new Twitter Bootstrap based
layouts from scratch.
shiny 0.1.7
--------------------------------------------------------------------------------
* Fix issue #26: Shiny.OutputBindings not correctly exported.
* Add `repeatable` function for making easily repeatable versions of random
number generating functions.
* Transcode JSON into UTF-8 (prevents non-ASCII reactivePrint values from
causing errors on Windows).
shiny 0.1.6
--------------------------------------------------------------------------------
* Import package dependencies, instead of attaching them (with the exception of
websockets, which doesn't currently work unless attached).
* conditionalPanel was animated, now it is not.
* bindAll was not correctly sending initial values to the server; fixed.
shiny 0.1.5
--------------------------------------------------------------------------------
* BREAKING CHANGE: JS APIs Shiny.bindInput and Shiny.bindOutput removed and
replaced with Shiny.bindAll; Shiny.unbindInput and Shiny.unbindOutput removed
and replaced with Shiny.unbindAll.
* Add file upload support (currently only works with Chrome and Firefox). Use
a normal HTML file input, or call the `fileInput` UI function.
* Shiny.unbindOutputs did not work, now it does.
* Generally improved robustness of dynamic input/output bindings.
* Add conditionalPanel UI function to allow showing/hiding UI based on a JS
expression; for example, whether an input is a particular value. Also works in
raw HTML (add the `data-display-if` attribute to the element that should be
shown/hidden).
* htmlOutput (CSS class `shiny-html-output`) can contain inputs and outputs.
shiny 0.1.4
--------------------------------------------------------------------------------
* Allow Bootstrap tabsets to act as reactive inputs; their value indicates which
tab is active
* Upgrade to Bootstrap 2.1
* Add `checkboxGroupInput` control, which presents a list of checkboxes and
returns a vector of the selected values
* Add `addResourcePath`, intended for reusable component authors to access CSS,
JavaScript, image files, etc. from their package directories
* Add Shiny.bindInputs(scope), .unbindInputs(scope), .bindOutputs(scope), and
.unbindOutputs(scope) JS API calls to allow dynamic binding/unbinding of HTML
elements
shiny 0.1.3
--------------------------------------------------------------------------------
* Introduce Shiny.inputBindings.register JS API and InputBinding class, for
creating custom input controls
* Add `step` parameter to numericInput
* Read names of input using `names(input)`
* Access snapshot of input as a list using `as.list(input)`
* Fix issue #10: Plots in tabsets not rendered
shiny 0.1.2
--------------------------------------------------------------------------------
Initial private beta release!

790
R/bootstrap.R Normal file
View File

@@ -0,0 +1,790 @@
#' Create a Twitter Bootstrap page
#'
#' Create a Shiny UI page that loads the CSS and JavaScript for
#' \href{http://getbootstrap.com}{Twitter Bootstrap}, and has no content in the
#' page body (other than what you provide).
#'
#' This function is primarily intended for users who are proficient in HTML/CSS,
#' and know how to lay out pages in Bootstrap. Most users should use template
#' functions like \code{\link{pageWithSidebar}}.
#'
#' @param ... The contents of the document body.
#' @return A UI defintion that can be passed to the \link{shinyUI} function.
#'
#' @export
bootstrapPage <- function(...) {
# required head tags for boostrap
importBootstrap <- function(min = TRUE, responsive = TRUE) {
ext <- function(ext) {
ifelse(min, paste(".min", ext, sep=""), ext)
}
cssExt <- ext(".css")
jsExt = ext(".js")
bs <- "shared/bootstrap/"
result <- tags$head(
tags$link(rel="stylesheet",
type="text/css",
href=paste(bs, "css/bootstrap", cssExt, sep="")),
tags$script(src=paste(bs, "js/bootstrap", jsExt, sep=""))
)
if (responsive) {
result <- tagAppendChild(
result,
tags$meta(name="viewport",
content="width=device-width, initial-scale=1.0"))
result <- tagAppendChild(
result,
tags$link(rel="stylesheet",
type="text/css",
href=paste(bs, "css/bootstrap-responsive", cssExt, sep="")))
}
result
}
tagList(
# inject bootstrap requirements into head
importBootstrap(),
list(...)
)
}
#' Create a page with a sidebar
#'
#' Create a Shiny UI that contains a header with the application title, a
#' sidebar for input controls, and a main area for output.
#'
#' @param headerPanel The \link{headerPanel} with the application title
#' @param sidebarPanel The \link{sidebarPanel} containing input controls
#' @param mainPanel The \link{mainPanel} containing outputs
#' @return A UI defintion that can be passed to the \link{shinyUI} function
#'
#' @examples
#' # Define UI
#' shinyUI(pageWithSidebar(
#'
#' # Application title
#' headerPanel("Hello Shiny!"),
#'
#' # Sidebar with a slider input
#' sidebarPanel(
#' sliderInput("obs",
#' "Number of observations:",
#' min = 0,
#' max = 1000,
#' value = 500)
#' ),
#'
#' # Show a plot of the generated distribution
#' mainPanel(
#' plotOutput("distPlot")
#' )
#' ))
#'
#' @export
pageWithSidebar <- function(headerPanel, sidebarPanel, mainPanel) {
bootstrapPage(
# basic application container divs
div(
class="container-fluid",
div(class="row-fluid",
headerPanel
),
div(class="row-fluid",
sidebarPanel,
mainPanel
)
)
)
}
#' Create a header panel
#'
#' Create a header panel containing an application title.
#'
#' @param title An application title to display
#' @return A headerPanel that can be passed to \link{pageWithSidebar}
#'
#' @examples
#' headerPanel("Hello Shiny!")
#' @export
headerPanel <- function(title) {
tagList(
tags$head(tags$title(title)),
div(class="span12", style="padding: 10px 0px;",
h1(title)
)
)
}
#' Create a well panel
#'
#' Creates a panel with a slightly inset border and grey background. Equivalent
#' to Twitter Bootstrap's \code{well} CSS class.
#'
#' @param ... UI elements to include inside the panel.
#' @return The newly created panel.
#'
#' @export
wellPanel <- function(...) {
div(class="well", ...)
}
#' Create a sidebar panel
#'
#' Create a sidebar panel containing input controls that can in turn be
#' passed to \link{pageWithSidebar}.
#'
#' @param ... UI elements to include on the sidebar
#' @return A sidebar that can be passed to \link{pageWithSidebar}
#'
#' @examples
#' # Sidebar with controls to select a dataset and specify
#' # the number of observations to view
#' sidebarPanel(
#' selectInput("dataset", "Choose a dataset:",
#' choices = c("rock", "pressure", "cars")),
#'
#' numericInput("obs", "Observations:", 10)
#' )
#' @export
sidebarPanel <- function(...) {
div(class="span4",
tags$form(class="well",
...
)
)
}
#' Create a main panel
#'
#' Create a main panel containing output elements that can in turn be
#' passed to \link{pageWithSidebar}.
#'
#' @param ... Ouput elements to include in the main panel
#' @return A main panel that can be passed to \link{pageWithSidebar}
#'
#' @examples
#' # Show the caption and plot of the requested variable against mpg
#' mainPanel(
#' h3(textOutput("caption")),
#' plotOutput("mpgPlot")
#' )
#' @export
mainPanel <- function(...) {
div(class="span8",
...
)
}
#' Conditional Panel
#'
#' Creates a panel that is visible or not, depending on the value of a
#' JavaScript expression. The JS expression is evaluated once at startup and
#' whenever Shiny detects a relevant change in input/output.
#'
#' In the JS expression, you can refer to \code{input} and \code{output}
#' JavaScript objects that contain the current values of input and output. For
#' example, if you have an input with an id of \code{foo}, then you can use
#' \code{input.foo} to read its value. (Be sure not to modify the input/output
#' objects, as this may cause unpredictable behavior.)
#'
#' @param condition A JavaScript expression that will be evaluated repeatedly to
#' determine whether the panel should be displayed.
#' @param ... Elements to include in the panel.
#'
#' @examples
#' sidebarPanel(
#' selectInput(
#' "plotType", "Plot Type",
#' list(Scatter = "scatter",
#' Histogram = "hist")),
#'
#' # Only show this panel if the plot type is a histogram
#' conditionalPanel(
#' condition = "input.plotType == 'hist'",
#' selectInput(
#' "breaks", "Breaks",
#' list("Sturges",
#' "Scott",
#' "Freedman-Diaconis",
#' "[Custom]" = "custom")),
#'
#' # Only show this panel if Custom is selected
#' conditionalPanel(
#' condition = "input.breaks == 'custom'",
#' sliderInput("breakCount", "Break Count", min=1, max=1000, value=10)
#' )
#' )
#' )
#'
#' @export
conditionalPanel <- function(condition, ...) {
div('data-display-if'=condition, ...)
}
#' Create a text input control
#'
#' Create an input control for entry of unstructured text values
#'
#' @param inputId Input variable to assign the control's value to
#' @param label Display label for the control
#' @param value Initial value
#' @return A text input control that can be added to a UI definition.
#'
#' @examples
#' textInput("caption", "Caption:", "Data Summary")
#' @export
textInput <- function(inputId, label, value = "") {
tagList(
tags$label(label),
tags$input(id = inputId, type="text", value=value)
)
}
#' Create a numeric input control
#'
#' Create an input control for entry of numeric values
#'
#' @param inputId Input variable to assign the control's value to
#' @param label Display label for the control
#' @param value Initial value
#' @param min Minimum allowed value
#' @param max Maximum allowed value
#' @param step Interval to use when stepping between min and max
#' @return A numeric input control that can be added to a UI definition.
#'
#' @examples
#' numericInput("obs", "Observations:", 10,
#' min = 1, max = 100)
#' @export
numericInput <- function(inputId, label, value, min = NA, max = NA, step = NA) {
# build input tag
inputTag <- tags$input(id = inputId, type = "number", value = value)
if (!is.na(min))
inputTag$attribs$min = min
if (!is.na(max))
inputTag$attribs$max = max
if (!is.na(step))
inputTag$attribs$step = step
tagList(
tags$label(label),
inputTag
)
}
#' File Upload Control
#'
#' Create a file upload control that can be used to upload one or more files.
#' \bold{Experimental feature. Only works in some browsers (primarily tested on
#' Chrome and Firefox).}
#'
#' @param inputId Input variable to assign the control's value to.
#' @param label Display label for the control.
#' @param multiple Whether the user should be allowed to select and upload
#' multiple files at once.
#' @param accept A character vector of MIME types; gives the browser a hint of
#' what kind of files the server is expecting.
#'
#' @export
fileInput <- function(inputId, label, multiple = FALSE, accept = NULL) {
inputTag <- tags$input(id = inputId, type = "file")
if (multiple)
inputTag$attribs$multiple <- "multiple"
if (length(accept) > 0)
inputTag$attribs$accept <- paste(accept, collapse=',')
tagList(
tags$label(label),
inputTag
)
}
#' Checkbox Input Control
#'
#' Create a checkbox that can be used to specify logical values.
#'
#' @param inputId Input variable to assign the control's value to.
#' @param label Display label for the control.
#' @param value Initial value (\code{TRUE} or \code{FALSE}).
#' @return A checkbox control that can be added to a UI definition.
#'
#' @seealso \code{\link{checkboxGroupInput}}
#'
#' @examples
#' checkboxInput("outliers", "Show outliers", FALSE)
#' @export
checkboxInput <- function(inputId, label, value = FALSE) {
inputTag <- tags$input(id = inputId, type="checkbox")
if (!is.null(value) && value)
inputTag$attribs$checked <- "checked"
tags$label(class = "checkbox", inputTag, label)
}
#' Checkbox Group Input Control
#'
#' Create a group of checkboxes that can be used to toggle multiple choices
#' independently. The server will receive the input as a character vector of the
#' selected values.
#'
#' @param inputId Input variable to assign the control's value to.
#' @param label Display label for the control.
#' @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.
#' @param selected Names of items that should be initially selected, if any.
#' @return A list of HTML elements that can be added to a UI definition.
#'
#' @seealso \code{\link{checkboxInput}}
#'
#' @examples
#' checkboxGroupInput("variable", "Variable:",
#' list("Cylinders" = "cyl",
#' "Transmission" = "am",
#' "Gears" = "gear"))
#'
#' @export
checkboxGroupInput <- function(inputId, label, choices, selected = NULL) {
# resolve names
choices <- choicesWithNames(choices)
checkboxes <- list()
for (choiceName in names(choices)) {
checkbox <- tags$input(name = inputId, type="checkbox",
value = choices[[choiceName]])
if (choiceName %in% selected)
checkbox$attribs$selected <- 'selected'
checkboxes[[length(checkboxes)+1]] <- checkbox
checkboxes[[length(checkboxes)+1]] <- choiceName
checkboxes[[length(checkboxes)+1]] <- tags$br()
}
# return label and select tag
tags$div(class='control-group',
controlLabel(inputId, label),
checkboxes)
}
#' Create a help text element
#'
#' Create help text which can be added to an input form to provide
#' additional explanation or context.
#'
#' @param text Help text string
#' @param ... Additional help text strings
#' @return A help text element that can be added to a UI definition.
#'
#' @examples
#' helpText("Note: while the data view will show only",
#' "the specified number of observations, the",
#' "summary will be based on the full dataset.")
#' @export
helpText <- function(text, ...) {
text <- c(text, as.character(list(...)))
text <- paste(text, collapse=" ")
span(class="help-block", text)
}
controlLabel <- function(controlName, label) {
tags$label(class = "control-label", `for` = controlName, label)
}
choicesWithNames <- function(choices) {
# get choice names
choiceNames <- names(choices)
if (is.null(choiceNames))
choiceNames <- character(length(choices))
# default missing names to choice values
missingNames <- choiceNames == ""
choiceNames[missingNames] <- paste(choices)[missingNames]
names(choices) <- choiceNames
# return choices
return (choices)
}
#' Create a select list input control
#'
#' Create a select list that can be used to choose a single or
#' multiple items from a list of values.
#'
#' @param inputId Input variable to assign the control's value to
#' @param label Display label for the control
#' @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.
#' @param selected Name of initially selected item (or multiple names if
#' \code{multiple = TRUE}). If not specified then defaults to the first item
#' for single-select lists and no items for multiple select lists.
#' @param multiple Is selection of multiple items allowed?
#' @return A select list control that can be added to a UI definition.
#'
#' @examples
#' selectInput("variable", "Variable:",
#' list("Cylinders" = "cyl",
#' "Transmission" = "am",
#' "Gears" = "gear"))
#' @export
selectInput <- function(inputId,
label,
choices,
selected = NULL,
multiple = FALSE) {
# resolve names
choices <- choicesWithNames(choices)
# default value if it's not specified
if (is.null(selected) && !multiple)
selected <- names(choices)[[1]]
# create select tag and add options
selectTag <- tags$select(id = inputId)
if (multiple)
selectTag$attribs$multiple <- "multiple"
for (choiceName in names(choices)) {
optionTag <- tags$option(value = choices[[choiceName]], choiceName)
if (choiceName %in% selected)
optionTag$attribs$selected = "selected"
selectTag <- tagAppendChild(selectTag, optionTag)
}
# return label and select tag
tagList(controlLabel(inputId, label), selectTag)
}
#' Create radio buttons
#'
#' Create a set of radio buttons used to select an item from a list.
#'
#' @param inputId Input variable to assign the control's value to
#' @param label Display label for the control
#' @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)
#' @param selected Name of initially selected item (if not specified then
#' defaults to the first item)
#' @return A set of radio buttons that can be added to a UI definition.
#'
#' @examples
#' radioButtons("dist", "Distribution type:",
#' list("Normal" = "norm",
#' "Uniform" = "unif",
#' "Log-normal" = "lnorm",
#' "Exponential" = "exp"))
#' @export
radioButtons <- function(inputId, label, choices, selected = NULL) {
# resolve names
choices <- choicesWithNames(choices)
# default value if it's not specified
if (is.null(selected))
selected <- names(choices)[[1]]
# build list of radio button tags
inputTags <- list()
for (i in 1:length(choices)) {
id <- paste(inputId, i, sep="")
name <- names(choices)[[i]]
value <- choices[[i]]
inputTag <- tags$input(type = "radio",
name = inputId,
id = id,
value = value)
if (identical(name, selected))
inputTag$attribs$checked = "checked"
labelTag <- tags$label(class = "radio")
labelTag <- tagAppendChild(labelTag, inputTag)
labelTag <- tagAppendChild(labelTag, name)
inputTags[[length(inputTags) + 1]] <- labelTag
}
tagList(tags$label(class = "control-label", label),
inputTags)
}
#' Create a submit button
#'
#' Create a submit button for an input form. Forms that include a submit
#' button do not automatically update their outputs when inputs change,
#' rather they wait until the user explicitly clicks the submit button.
#'
#' @param text Button caption
#' @return A submit button that can be added to a UI definition.
#'
#' @examples
#' submitButton("Update View")
#' @export
submitButton <- function(text = "Apply Changes") {
div(
tags$button(type="submit", class="btn btn-primary", text)
)
}
#' Slider Input Widget
#'
#' Constructs a slider widget to select a numeric value from a range.
#'
#' @param inputId Specifies the \code{input} slot that will be used to access
#' the value.
#' @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 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;
#' \code{FALSE} if no rounding is desired; or an integer to round to that
#' number of digits (for example, 1 will round to the nearest 10, and -2 will
#' round to the nearest .01). Any rounding will be applied after snapping to
#' the nearest step.
#' @param format Customize format values in slider labels. See
#' \url{http://archive.plugins.jquery.com/project/numberformatter} for syntax
#' details.
#' @param locale The locale to be used when applying \code{format}. See details.
#' @param ticks \code{FALSE} to hide tick marks, \code{TRUE} to show them
#' according to some simple heuristics.
#' @param animate \code{TRUE} to show simple animation controls with default
#' settings; \code{FALSE} not to; or a custom settings list, such as those
#' created using \code{\link{animationOptions}}.
#'
#' @details
#'
#' Valid values for \code{locale} are: \tabular{ll}{ Arab Emirates \tab "ae" \cr
#' Australia \tab "au" \cr Austria \tab "at" \cr Brazil \tab "br" \cr Canada
#' \tab "ca" \cr China \tab "cn" \cr Czech \tab "cz" \cr Denmark \tab "dk" \cr
#' Egypt \tab "eg" \cr Finland \tab "fi" \cr France \tab "fr" \cr Germany \tab
#' "de" \cr Greece \tab "gr" \cr Great Britain \tab "gb" \cr Hong Kong \tab "hk"
#' \cr India \tab "in" \cr Israel \tab "il" \cr Japan \tab "jp" \cr Russia \tab
#' "ru" \cr South Korea \tab "kr" \cr Spain \tab "es" \cr Sweden \tab "se" \cr
#' Switzerland \tab "ch" \cr Taiwan \tab "tw" \cr Thailand \tab "th" \cr United
#' States \tab "us" \cr Vietnam \tab "vn" \cr }
#'
#' @export
sliderInput <- function(inputId, label, min, max, value, step = NULL,
round=FALSE, format='#,##0.#####', locale='us',
ticks=TRUE, animate=FALSE) {
# validate label
labelText <- as.character(label)
if (!is.character(labelText))
stop("label not specified")
if (identical(animate, T))
animate <- animationOptions()
if (!is.null(animate) && !identical(animate, F)) {
if (is.null(animate$playButton))
animate$playButton <- tags$i(class='icon-play')
if (is.null(animate$pauseButton))
animate$pauseButton <- tags$i(class='icon-pause')
}
# build slider
tagList(
controlLabel(inputId, labelText),
slider(inputId, min=min, max=max, value=value, step=step, round=round,
locale=locale, format=format, ticks=ticks,
animate=animate)
)
}
#' Create a tab panel
#'
#' Create a tab panel that can be included within a \code{\link{tabsetPanel}}.
#'
#' @param title Display title for tab
#' @param ... UI elements to include within the tab
#' @param value The value that should be sent when \code{tabsetPanel} reports
#' that this tab is selected. If omitted and \code{tabsetPanel} has an
#' \code{id}, then the title will be used.
#' @return A tab that can be passed to \code{\link{tabsetPanel}}
#'
#' @examples
#' # Show a tabset that includes a plot, summary, and
#' # table view of the generated distribution
#' mainPanel(
#' tabsetPanel(
#' tabPanel("Plot", plotOutput("plot")),
#' tabPanel("Summary", verbatimTextOutput("summary")),
#' tabPanel("Table", tableOutput("table"))
#' )
#' )
#' @export
tabPanel <- function(title, ..., value = NULL) {
div(class="tab-pane", title=title, `data-value`=value, ...)
}
#' Create a tabset panel
#'
#' Create a tabset that contains \code{\link{tabPanel}} elements. Tabsets are
#' useful for dividing output into multiple independently viewable sections.
#'
#' @param ... \code{\link{tabPanel}} elements to include in the tabset
#' @param id If provided, you can use \code{input$}\emph{\code{id}} in your server
#' logic to determine which of the current tabs is active. The value will
#' correspond to the \code{value} argument that is passed to
#' \code{\link{tabPanel}}.
#' @return A tabset that can be passed to \code{\link{mainPanel}}
#'
#' @examples
#' # Show a tabset that includes a plot, summary, and
#' # table view of the generated distribution
#' mainPanel(
#' tabsetPanel(
#' tabPanel("Plot", plotOutput("plot")),
#' tabPanel("Summary", verbatimTextOutput("summary")),
#' tabPanel("Table", tableOutput("table"))
#' )
#' )
#' @export
tabsetPanel <- function(..., id = NULL) {
# build tab-nav and tab-content divs
tabs <- list(...)
tabNavList <- tags$ul(class = "nav nav-tabs", id = id)
tabContent <- tags$div(class = "tab-content")
firstTab <- TRUE
tabsetId <- as.integer(stats::runif(1, 1, 10000))
tabId <- 1
for (divTag in tabs) {
# compute id and assign it to the div
thisId <- paste("tab", tabsetId, tabId, sep="-")
divTag$attribs$id <- thisId
tabId <- tabId + 1
tabValue <- divTag$attribs$`data-value`
if (!is.null(tabValue) && is.null(id)) {
stop("tabsetPanel doesn't have an id assigned, but one of its tabPanels ",
"has a value. The value won't be sent without an id.")
}
# create the li tag
liTag <- tags$li(tags$a(href=paste("#", thisId, sep=""),
`data-toggle` = "tab",
`data-value` = tabValue,
divTag$attribs$title))
# set the first tab as active
if (firstTab) {
liTag$attribs$class <- "active"
divTag$attribs$class <- "tab-pane active"
firstTab = FALSE
}
# append the elements to our lists
tabNavList <- tagAppendChild(tabNavList, liTag)
tabContent <- tagAppendChild(tabContent, divTag)
}
tabDiv <- tags$div(class = "tabbable", tabNavList, tabContent)
}
#' Create a text output element
#'
#' Render a reactive output variable as text within an application page. The
#' text will be included within an HTML \code{div} tag.
#' @param outputId output variable to read the value from
#' @return A text output element that can be included in a panel
#' @details Text is HTML-escaped prior to rendering. This element is often used
#' to dispaly \link{reactiveText} output variables.
#' @examples
#' h3(textOutput("caption"))
#' @export
textOutput <- function(outputId) {
div(id = outputId, class = "shiny-text-output")
}
#' Create a verbatim text output element
#'
#' Render a reactive output variable as verbatim text within an
#' application page. The text will be included within an HTML \code{pre} tag.
#' @param outputId output variable to read the value from
#' @return A verbatim text output element that can be included in a panel
#' @details Text is HTML-escaped prior to rendering. This element is often used
#' with the \link{reactivePrint} function to preserve fixed-width formatting
#' of printed objects.
#' @examples
#' mainPanel(
#' h4("Summary"),
#' verbatimTextOutput("summary"),
#'
#' h4("Observations"),
#' tableOutput("view")
#' )
#' @export
verbatimTextOutput <- function(outputId) {
pre(id = outputId, class = "shiny-text-output")
}
#' Create a plot output element
#'
#' Render a \link{reactivePlot} within an application page.
#' @param outputId output variable to read the plot from
#' @param width Plot width
#' @param height Plot height
#' @return A plot output element that can be included in a panel
#' @examples
#' # Show a plot of the generated distribution
#' mainPanel(
#' plotOutput("distPlot")
#' )
#' @export
plotOutput <- function(outputId, width = "100%", height="400px") {
style <- paste("width:", width, ";", "height:", height)
div(id = outputId, class="shiny-plot-output", style = style)
}
#' Create a table output element
#'
#' Render a \link{reactiveTable} within an application page.
#' @param outputId output variable to read the table from
#' @return A table output element that can be included in a panel
#' @examples
#' mainPanel(
#' tableOutput("view")
#' )
#' @export
tableOutput <- function(outputId) {
div(id = outputId, class="shiny-html-output")
}
#' Create an HTML output element
#'
#' Render a reactive output variable as HTML within an application page. The
#' text will be included within an HTML \code{div} tag, and is presumed to
#' contain HTML content which should not be escaped.
#'
#' \code{uiOutput} is intended to be used with \code{reactiveUI} on the
#' server side. It is currently just an alias for \code{htmlOutput}.
#'
#' @param outputId output variable to read the value from
#' @return An HTML output element that can be included in a panel
#' @examples
#' htmlOutput("summary")
#' @export
htmlOutput <- function(outputId) {
div(id = outputId, class="shiny-html-output")
}
#' @rdname htmlOutput
#' @export
uiOutput <- function(outputId) {
htmlOutput(outputId)
}

95
R/fileupload.R Normal file
View File

@@ -0,0 +1,95 @@
# For HTML5-capable browsers, file uploads happen through a series of requests.
#
# 1. Client tells server that one or more files are about to be uploaded; the
# server responds with a "job ID" that the client should use for the rest of
# the upload.
#
# 2. For each file (sequentially):
# a. Client tells server the name, size, and type of the file.
# b. Client sends server a small-ish blob of data.
# c. Repeat 2b until the entire file has been uploaded.
# d. Client tells server that the current file is done.
#
# 3. Repeat 2 until all files have been uploaded.
#
# 4. Client tells server that all files have been uploaded, along with the
# input ID that this data should be associated with.
#
# Unfortunately this approach will not work for browsers that don't support
# HTML5 File API, but the fallback approach we would like to use (multipart
# form upload, i.e. traditional HTTP POST-based file upload) doesn't work with
# the websockets package's HTTP server at the moment.
FileUploadOperation <- setRefClass(
'FileUploadOperation',
fields = list(
.parent = 'ANY',
.id = 'character',
.files = 'data.frame',
.dir = 'character',
.currentFileInfo = 'list',
.currentFileData = 'ANY'
),
methods = list(
initialize = function(parent, id, dir) {
.parent <<- parent
.id <<- id
.dir <<- dir
},
fileBegin = function(file) {
.currentFileInfo <<- file
filename <- file.path(.dir, as.character(length(.files)))
row <- data.frame(name=file$name, size=file$size, type=file$type,
datapath=filename, stringsAsFactors=F)
if (length(.files) == 0)
.files <<- row
else
.files <<- rbind(.files, row)
.currentFileData <<- file(filename, open='wb')
},
fileChunk = function(rawdata) {
writeBin(rawdata, .currentFileData)
},
fileEnd = function() {
close(.currentFileData)
},
finish = function() {
.parent$onJobFinished(.id)
return(.files)
}
)
)
FileUploadContext <- setRefClass(
'FileUploadContext',
fields = list(
.basedir = 'character',
.operations = 'Map'
),
methods = list(
initialize = function(dir=tempdir()) {
.basedir <<- dir
},
createUploadOperation = function() {
while (T) {
id <- paste(as.raw(runif(12, min=0, max=0xFF)), collapse='')
dir <- file.path(.basedir, id)
if (!dir.create(dir))
next
op <- FileUploadOperation$new(.self, id, dir)
.operations$set(id, op)
return(id)
}
},
getUploadOperation = function(jobId) {
.operations$get(jobId)
},
onJobFinished = function(jobId) {
.operations$remove(jobId)
}
)
)

74
R/map.R Normal file
View File

@@ -0,0 +1,74 @@
# TESTS
# Simple set/get
# Simple remove
# Simple containsKey
# Simple keys
# Simple values
# Simple clear
# Get of unknown key returns NULL
# Remove of unknown key does nothing
# Setting a key twice always results in last-one-wins
# /TESTS
Map <- setRefClass(
'Map',
fields = list(
.env = 'environment'
),
methods = list(
initialize = function() {
.env <<- new.env(parent=emptyenv())
},
get = function(key) {
if (.self$containsKey(key))
return(base::get(key, pos=.env, inherits=F))
else
return(NULL)
},
set = function(key, value) {
assign(key, value, pos=.env, inherits=F)
return(value)
},
remove = function(key) {
if (.self$containsKey(key)) {
result <- .self$get(key)
rm(list = key, pos=.env, inherits=F)
return(result)
}
return(NULL)
},
containsKey = function(key) {
exists(key, where=.env, inherits=F)
},
keys = function() {
ls(envir=.env, all.names=T)
},
values = function() {
mget(.self$keys(), envir=.env, inherits=F)
},
clear = function() {
.env <<- new.env(parent=emptyenv())
invisible(NULL)
},
size = function() {
length(.env)
}
)
)
`[.Map` <- function(map, name) {
map$get(name)
}
`[<-.Map` <- function(map, name, value) {
map$set(name, value)
return(map)
}
as.list.Map <- function(map) {
sapply(map$keys(),
map$get,
simplify=F)
}
length.Map <- function(map) {
map$size()
}

41
R/random.R Normal file
View File

@@ -0,0 +1,41 @@
#' Make a random number generator repeatable
#'
#' Given a function that generates random data, returns a wrapped version of
#' that function that always uses the same seed when called. The seed to use can
#' be passed in explicitly if desired; otherwise, a random number is used.
#'
#' @param rngfunc The function that is affected by the R session's seed.
#' @param seed The seed to set every time the resulting function is called.
#' @return A repeatable version of the function that was passed in.
#'
#' @note When called, the returned function attempts to preserve the R session's
#' current seed by snapshotting and restoring
#' \code{\link[base]{.Random.seed}}.
#'
#' @examples
#' rnormA <- repeatable(rnorm)
#' rnormB <- repeatable(rnorm)
#' rnormA(3) # [1] 1.8285879 -0.7468041 -0.4639111
#' rnormA(3) # [1] 1.8285879 -0.7468041 -0.4639111
#' rnormA(5) # [1] 1.8285879 -0.7468041 -0.4639111 -1.6510126 -1.4686924
#' rnormB(5) # [1] -0.7946034 0.2568374 -0.6567597 1.2451387 -0.8375699
#'
#' @export
repeatable <- function(rngfunc, seed = runif(1, 0, .Machine$integer.max)) {
force(seed)
function(...) {
# When we exit, restore the seed to its original state
if (exists('.Random.seed', where=globalenv())) {
currentSeed <- get('.Random.seed', pos=globalenv())
on.exit(assign('.Random.seed', currentSeed, pos=globalenv()))
}
else {
on.exit(rm('.Random.seed', pos=globalenv()))
}
set.seed(seed)
do.call(rngfunc, list(...))
}
}

312
R/react.R
View File

@@ -1,106 +1,64 @@
# TESTS
# Simple set/get
# Simple remove
# Simple contains.key
# Simple keys
# Simple values
# Simple clear
# Get of unknown key returns NULL
# Remove of unknown key does nothing
# Setting a key twice always results in last-one-wins
# /TESTS
Map <- setRefClass(
'Map',
fields = list(
.env = 'environment'
),
methods = list(
initialize = function() {
.env <<- new.env(parent=emptyenv())
},
get = function(key) {
if (.self$contains.key(key))
return(base::get(key, pos=.env, inherits=F))
else
return(NULL)
},
set = function(key, value) {
assign(key, value, pos=.env, inherits=F)
return(value)
},
remove = function(key) {
if (.self$contains.key(key)) {
result <- .self$get(key)
rm(list = key, pos=.env, inherits=F)
return(result)
}
return(NULL)
},
contains.key = function(key) {
exists(key, where=.env, inherits=F)
},
keys = function() {
ls(envir=.env, all.names=T)
},
values = function() {
mget(.self$keys(), envir=.env, inherits=F)
},
clear = function() {
.env <<- new.env(parent=emptyenv())
invisible(NULL)
},
size = function() {
length(.env)
}
)
)
as.list.Map <- function(map) {
sapply(map$keys(),
map$get,
simplify=F)
}
length.Map <- function(map) {
map$size()
}
Context <- setRefClass(
'Context',
fields = list(
id = 'character',
.invalidated = 'logical',
.callbacks = 'list'
.callbacks = 'list',
.hintCallbacks = 'list'
),
methods = list(
initialize = function() {
id <<- .get.reactive.environment()$next.id()
id <<- .getReactiveEnvironment()$nextId()
.invalidated <<- F
.callbacks <<- list()
.hintCallbacks <<- list()
},
run = function(func) {
env <- .get.reactive.environment()
old.ctx <- env$current.context(warn=F)
env$set.current.context(.self)
on.exit(env$set.current.context(old.ctx))
func()
"Run the provided function under this context."
env <- .getReactiveEnvironment()
env$runWith(.self, func)
},
invalidateHint = function() {
"Let this context know it may or may not be invalidated very soon; that
is, something in its dependency graph has been invalidated but there's no
guarantee that the cascade of invalidations will reach all the way here.
This is used to show progress in the UI."
lapply(.hintCallbacks, function(func) {
func()
})
},
invalidate = function() {
"Schedule this context for invalidation. It will not actually be
invalidated until the next call to \\code{\\link{flushReact}}."
if (.invalidated)
return()
.invalidated <<- T
.get.reactive.environment()$add.pending.invalidate(.self)
.getReactiveEnvironment()$addPendingInvalidate(.self)
NULL
},
on.invalidate = function(func) {
onInvalidate = function(func) {
"Register a function to be called when this context is invalidated.
If this context is already invalidated, the function is called
immediately."
if (.invalidated)
func()
else
.callbacks <<- c(.callbacks, func)
NULL
},
execute.callbacks = function() {
onInvalidateHint = function(func) {
.hintCallbacks <<- c(.hintCallbacks, func)
},
executeCallbacks = function() {
"For internal use only."
lapply(.callbacks, function(func) {
func()
withCallingHandlers({
func()
}, warning = function(e) {
# TODO: Callbacks in app
}, error = function(e) {
# TODO: Callbacks in app
})
})
}
)
@@ -108,34 +66,39 @@ Context <- setRefClass(
ReactiveEnvironment <- setRefClass(
'ReactiveEnvironment',
fields = c('.current.context', '.next.id', '.pending.invalidate'),
fields = c('.currentContext', '.nextId', '.pendingInvalidate'),
methods = list(
initialize = function() {
.current.context <<- NULL
.next.id <<- 0L
.pending.invalidate <<- list()
.currentContext <<- NULL
.nextId <<- 0L
.pendingInvalidate <<- list()
},
next.id = function() {
.next.id <<- .next.id + 1L
return(as.character(.next.id))
nextId = function() {
.nextId <<- .nextId + 1L
return(as.character(.nextId))
},
current.context = function(warn=T) {
if (warn && is.null(.current.context))
warning('No reactive context is active')
return(.current.context)
currentContext = function() {
if (is.null(.currentContext))
stop('Operation not allowed without an active reactive context. ',
'(You tried to do something that can only be done from inside a ',
'reactive function.)')
return(.currentContext)
},
set.current.context = function(ctx) {
.current.context <<- ctx
runWith = function(ctx, func) {
old.ctx <- .currentContext
.currentContext <<- ctx
on.exit(.currentContext <<- old.ctx)
func()
},
add.pending.invalidate = function(ctx) {
.pending.invalidate <<- c(.pending.invalidate, ctx)
addPendingInvalidate = function(ctx) {
.pendingInvalidate <<- c(.pendingInvalidate, ctx)
},
flush = function() {
while (length(.pending.invalidate) > 0) {
contexts <- .pending.invalidate
.pending.invalidate <<- list()
while (length(.pendingInvalidate) > 0) {
contexts <- .pendingInvalidate
.pendingInvalidate <<- list()
lapply(contexts, function(ctx) {
ctx$execute.callbacks()
ctx$executeCallbacks()
NULL
})
}
@@ -143,161 +106,20 @@ ReactiveEnvironment <- setRefClass(
)
)
Values <- setRefClass(
'Values',
fields = list(
.values = 'environment',
.dependencies = 'environment'
),
methods = list(
initialize = function() {
.values <<- new.env(parent=emptyenv())
.dependencies <<- new.env(parent=emptyenv())
},
get = function(key) {
ctx <- .get.reactive.environment()$current.context()
dep.key <- paste(key, ':', ctx$id, sep='')
if (!exists(dep.key, where=.dependencies, inherits=F)) {
assign(dep.key, ctx, pos=.dependencies, inherits=F)
ctx$on.invalidate(function() {
rm(list=dep.key, pos=.dependencies, inherits=F)
})
}
if (!exists(key, where=.values, inherits=F))
NULL
else
base::get(key, pos=.values, inherits=F)
},
set = function(key, value) {
if (exists(key, where=.values, inherits=F)) {
if (identical(base::get(key, pos=.values, inherits=F), value)) {
return(invisible())
}
}
assign(key, value, pos=.values, inherits=F)
dep.keys <- objects(
pos=.dependencies,
pattern=paste('^\\Q', key, ':', '\\E', '\\d+$', sep='')
)
lapply(
mget(dep.keys, envir=.dependencies),
function(ctx) {
ctx$invalidate()
NULL
}
)
invisible()
},
mset = function(lst) {
lapply(names(lst),
function(name) {
.self$set(name, lst[[name]])
})
}
)
)
Observable <- setRefClass(
'Observable',
fields = c(
'.func', # function
'.dependencies', # Map
'.initialized', # logical
'.value' # any
),
methods = list(
initialize = function(func) {
.func <<- func
.dependencies <<- Map$new()
.initialized <<- F
},
get.value = function() {
if (!.initialized) {
.initialized <<- T
.self$.update.value()
}
ctx <- .get.reactive.environment()$current.context()
if (!.dependencies$contains.key(ctx$id)) {
.dependencies$set(ctx$id, ctx)
ctx$on.invalidate(function() {
.dependencies$remove(ctx$id)
})
}
return(.value)
},
.update.value = function() {
old.value <- .value
ctx <- Context$new()
ctx$on.invalidate(function() {
.self$.update.value()
})
ctx$run(function() {
.value <<- .func()
})
if (!identical(old.value, .value)) {
lapply(
.dependencies$values(),
function(dep.ctx) {
dep.ctx$invalidate()
NULL
}
)
}
}
)
)
Observer <- setRefClass(
'Observer',
fields = list(
.func = 'function'
),
methods = list(
initialize = function(func) {
.func <<- func
.self$run()
},
run = function() {
ctx <- Context$new()
ctx$on.invalidate(function() {
run()
})
ctx$run(.func)
}
)
)
.get.reactive.environment <- function() {
.getReactiveEnvironment <- function() {
if (!exists('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)) {
assign('.ReactiveEnvironment', ReactiveEnvironment$new(), envir=.GlobalEnv)
}
get('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)
}
flush.react <- function() {
.get.reactive.environment()$flush()
# Causes any pending invalidations to run.
flushReact <- function() {
.getReactiveEnvironment()$flush()
}
test <- function () {
values <- Values$new()
obs <- Observer$new(function() {print(values$get('foo'))})
flush.react()
values$set('foo', 'bar')
flush.react()
values$set('a', 100)
values$set('b', 250)
observable <- Observable$new(function() {
values$get('a') + values$get('b')
})
obs2 <- Observer$new(function() {print(paste0('a+b: ', observable$get.value()))})
flush.react()
values$set('b', 300)
flush.react()
values$mset(list(a = 10, b = 20))
flush.react()
# Retrieves the current reactive context, or errors if there is no reactive
# context active at the moment.
getCurrentContext <- function() {
.getReactiveEnvironment()$currentContext()
}

337
R/reactives.R Normal file
View File

@@ -0,0 +1,337 @@
Dependencies <- setRefClass(
'Dependencies',
fields = list(
.dependencies = 'Map'
),
methods = list(
register = function() {
ctx <- .getReactiveEnvironment()$currentContext()
if (!.dependencies$containsKey(ctx$id)) {
.dependencies$set(ctx$id, ctx)
ctx$onInvalidate(function() {
.dependencies$remove(ctx$id)
})
}
},
invalidate = function() {
lapply(
.dependencies$values(),
function(ctx) {
ctx$invalidateHint()
ctx$invalidate()
NULL
}
)
},
invalidateHint = function() {
lapply(
.dependencies$values(),
function(dep.ctx) {
dep.ctx$invalidateHint()
NULL
})
}
)
)
Values <- setRefClass(
'Values',
fields = list(
.values = 'environment',
.dependencies = 'environment',
# Dependencies for the list of names
.namesDeps = 'Dependencies',
# Dependencies for all values
.allDeps = 'Dependencies'
),
methods = list(
initialize = function() {
.values <<- new.env(parent=emptyenv())
.dependencies <<- new.env(parent=emptyenv())
},
get = function(key) {
ctx <- .getReactiveEnvironment()$currentContext()
dep.key <- paste(key, ':', ctx$id, sep='')
if (!exists(dep.key, where=.dependencies, inherits=F)) {
assign(dep.key, ctx, pos=.dependencies, inherits=F)
ctx$onInvalidate(function() {
rm(list=dep.key, pos=.dependencies, inherits=F)
})
}
if (!exists(key, where=.values, inherits=F))
NULL
else
base::get(key, pos=.values, inherits=F)
},
set = function(key, value) {
if (exists(key, where=.values, inherits=F)) {
if (identical(base::get(key, pos=.values, inherits=F), value)) {
return(invisible())
}
}
else {
.namesDeps$invalidate()
}
.allDeps$invalidate()
assign(key, value, pos=.values, inherits=F)
dep.keys <- objects(
pos=.dependencies,
pattern=paste('^\\Q', key, ':', '\\E', '\\d+$', sep=''),
all.names=T
)
lapply(
mget(dep.keys, envir=.dependencies),
function(ctx) {
ctx$invalidateHint()
ctx$invalidate()
NULL
}
)
invisible()
},
mset = function(lst) {
lapply(base::names(lst),
function(name) {
.self$set(name, lst[[name]])
})
},
names = function() {
.namesDeps$register()
return(ls(.values, all.names=T))
},
toList = function() {
.allDeps$register()
return(as.list(.values))
}
)
)
`[.Values` <- function(values, name) {
values$get(name)
}
`[<-.Values` <- function(values, name, value) {
values$set(name, value)
return(values)
}
.createValuesReader <- function(values) {
acc <- list(impl=values)
class(acc) <- 'reactvaluesreader'
return(acc)
}
#' @S3method $ reactvaluesreader
`$.reactvaluesreader` <- function(x, name) {
x[['impl']]$get(name)
}
#' @S3method names reactvaluesreader
names.reactvaluesreader <- function(x) {
x[['impl']]$names()
}
#' @S3method as.list reactvaluesreader
as.list.reactvaluesreader <- function(x, ...) {
x[['impl']]$toList()
}
Observable <- setRefClass(
'Observable',
fields = list(
.func = 'function',
.dependencies = 'Dependencies',
.initialized = 'logical',
.value = 'ANY'
),
methods = list(
initialize = function(func) {
if (length(formals(func)) > 0)
stop("Can't make a reactive function from a function that takes one ",
"or more parameters; only functions without parameters can be ",
"reactive.")
.func <<- func
.initialized <<- F
},
getValue = function() {
if (!.initialized) {
.initialized <<- T
.self$.updateValue()
}
.dependencies$register()
if (identical(class(.value), 'try-error'))
stop(attr(.value, 'condition'))
return(.value)
},
.updateValue = function() {
old.value <- .value
ctx <- Context$new()
ctx$onInvalidate(function() {
.self$.updateValue()
})
ctx$onInvalidateHint(function() {
.dependencies$invalidateHint()
})
ctx$run(function() {
.value <<- try(.func(), silent=F)
})
if (!identical(old.value, .value)) {
.dependencies$invalidate()
}
}
)
)
#' Create a Reactive Function
#'
#' Wraps a normal function to create a reactive function. Conceptually, a
#' reactive function is a function whose result will change over time.
#'
#' Reactive functions are functions that can read reactive values and call other
#' reactive functions. Whenever a reactive value changes, any reactive functions
#' that depended on it are marked as "invalidated" and will automatically
#' re-execute if necessary. If a reactive function is marked as invalidated, any
#' other reactive functions that recently called it are also marked as
#' invalidated. In this way, invalidations ripple through the functions that
#' depend on each other.
#'
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{Shiny tutorial} for
#' more information about reactive functions.
#'
#' @param x The value or function to make reactive. The function must not have
#' any parameters.
#' @return A reactive function. (Note that reactive functions can only be called
#' from within other reactive functions.)
#'
#' @export
reactive <- function(x) {
UseMethod("reactive")
}
#' @S3method reactive function
reactive.function <- function(x) {
return(Observable$new(x)$getValue)
}
#' @S3method reactive default
reactive.default <- function(x) {
stop("Don't know how to make this object reactive!")
}
Observer <- setRefClass(
'Observer',
fields = list(
.func = 'function',
.hintCallbacks = 'list'
),
methods = list(
initialize = function(func) {
if (length(formals(func)) > 0)
stop("Can't make an observer from a function that takes parameters; ",
"only functions without parameters can be reactive.")
.func <<- func
# Defer the first running of this until flushReact is called
ctx <- Context$new()
ctx$onInvalidate(function() {
run()
})
ctx$invalidate()
},
run = function() {
ctx <- Context$new()
ctx$onInvalidate(function() {
run()
})
ctx$onInvalidateHint(function() {
lapply(.hintCallbacks, function(func) {
func()
NULL
})
})
ctx$run(.func)
},
onInvalidateHint = function(func) {
.hintCallbacks <<- c(.hintCallbacks, func)
}
)
)
# NOTE: we de-roxygenized this comment because the function isn't exported
# Observe
#
# Creates an observer from the given function. An observer is like a reactive
# function in that it can read reactive values and call reactive functions,
# and will automatically re-execute when those dependencies change. But unlike
# reactive functions, it doesn't yield a result and can't be used as an input
# to other reactive functions. Thus, observers are only useful for their side
# effects (for example, performing I/O).
#
# @param func The function to observe. It must not have any parameters. Any
# return value from this function will be ignored.
#
observe <- function(func) {
Observer$new(func)
}
#' Timer
#'
#' Creates a reactive timer with the given interval. A reactive timer is like a
#' reactive value, except reactive values are triggered when they are set, while
#' reactive timers are triggered simply by the passage of time.
#'
#' \link[=reactive]{Reactive functions} and observers that want to be
#' invalidated by the timer need to call the timer function that
#' \code{reactiveTimer} returns, even if the current time value is not actually
#' needed.
#'
#' See \code{\link{invalidateLater}} as a safer and simpler alternative.
#'
#' @param intervalMs How often to fire, in milliseconds
#' @return A no-parameter function that can be called from a reactive context,
#' in order to cause that context to be invalidated the next time the timer
#' interval elapses. Calling the returned function also happens to yield the
#' current time (as in \code{\link{Sys.time}}).
#' @seealso invalidateLater
#' @export
reactiveTimer <- function(intervalMs=1000) {
dependencies <- Map$new()
timerCallbacks$schedule(intervalMs, function() {
timerCallbacks$schedule(intervalMs, sys.function())
lapply(
dependencies$values(),
function(dep.ctx) {
dep.ctx$invalidate()
NULL
})
})
return(function() {
ctx <- .getReactiveEnvironment()$currentContext()
if (!dependencies$containsKey(ctx$id)) {
dependencies$set(ctx$id, ctx)
ctx$onInvalidate(function() {
dependencies$remove(ctx$id)
})
}
return(Sys.time())
})
}
#' Scheduled Invalidation
#'
#' Schedules the current reactive context to be invalidated in the given number
#' of milliseconds.
#' @param millis Approximate milliseconds to wait before invalidating the
#' current reactive context.
#' @export
invalidateLater <- function(millis) {
ctx <- .getReactiveEnvironment()$currentContext()
timerCallbacks$schedule(millis, function() {
ctx$invalidate()
})
invisible()
}

837
R/shiny.R
View File

@@ -1,105 +1,295 @@
#' @docType package
#' @import websockets caTools RJSONIO xtable digest
NULL
suppressPackageStartupMessages({
library(websockets)
library(RJSONIO)
library(caTools)
library(xtable)
})
ShinyApp <- setRefClass(
'ShinyApp',
fields = list(
.websocket = 'list',
.outputs = 'Map',
.invalidated.output.values = 'Map',
.invalidatedOutputValues = 'Map',
.invalidatedOutputErrors = 'Map',
.progressKeys = 'character',
.fileUploadContext = 'FileUploadContext',
session = 'Values'
),
methods = list(
initialize = function(ws) {
.websocket <<- ws
.outputs <<- Map$new()
.invalidated.output.values <<- Map$new()
.invalidatedOutputValues <<- Map$new()
.invalidatedOutputErrors <<- Map$new()
.progressKeys <<- character(0)
# TODO: Put file upload context in user/app-specific dir if possible
.fileUploadContext <<- FileUploadContext$new()
session <<- Values$new()
},
define.output = function(name, func) {
.outputs$set(name, func)
},
define.plot.output = function(name, func, ...) {
.outputs$set(name, function() {
png.file <- tempfile(fileext='.png')
png(filename=png.file, ...)
func()
dev.off()
bytes <- file.info(png.file)$size
if (is.na(bytes))
return(NULL)
b64 <- base64encode(readBin(png.file, 'raw', n=bytes))
return(paste("data:image/png;base64,", b64, sep=''))
})
},
define.table.output = function(name, func) {
.outputs$set(name, function() {
data <- func()
return(paste(capture.output(print(xtable(data), type='html')), collapse="\n"))
})
},
instantiate.outputs = function() {
lapply(.outputs$keys(),
function(key) {
func <- .outputs$remove(key)
Observer$new(function() {
value <- func()
.invalidated.output.values$set(key, value)
})
})
},
flush.output = function() {
if (length(.invalidated.output.values) == 0)
return(invisible())
defineOutput = function(name, func) {
"Binds an output generating function to this name. The function can either
take no parameters, or have named parameters for \\code{name} and
\\code{shinyapp} (in the future this list may expand, so it is a good idea
to also include \\code{...} in your function signature)."
data <- .invalidated.output.values
.invalidated.output.values <<- Map$new()
# cat(c("SEND", toJSON(as.list(data)), "\n"))
websocket_write(toJSON(as.list(data)), .websocket)
# jcheng 08/31/2012: User submitted an example of a dynamically calculated
# name not working unless name was eagerly evaluated. Yikes!
force(name)
if (is.function(func)) {
if (length(formals(func)) != 0) {
orig <- func
func <- function() {
orig(name=name, shinyapp=.self)
}
}
obs <- Observer$new(function() {
value <- try(func(), silent=F)
.invalidatedOutputErrors$remove(name)
.invalidatedOutputValues$remove(name)
if (inherits(value, 'try-error')) {
cond <- attr(value, 'condition')
.invalidatedOutputErrors$set(
name,
list(message=cond$message,
call=capture.output(print(cond$call))))
}
else
.invalidatedOutputValues$set(name, value)
})
obs$onInvalidateHint(function() {
showProgress(name)
})
}
else {
stop(paste("Unexpected", class(func), "output for", name))
}
},
flushOutput = function() {
if (length(.progressKeys) == 0
&& length(.invalidatedOutputValues) == 0
&& length(.invalidatedOutputErrors) == 0) {
return(invisible())
}
.progressKeys <<- character(0)
values <- .invalidatedOutputValues
.invalidatedOutputValues <<- Map$new()
errors <- .invalidatedOutputErrors
.invalidatedOutputErrors <<- Map$new()
json <- toJSON(list(errors=as.list(errors),
values=as.list(values)))
.write(json)
},
showProgress = function(id) {
'Send a message to the client that recalculation of the output identified
by \\code{id} is in progress. There is currently no mechanism for
explicitly turning off progress for an output component; instead, all
progress is implicitly turned off when flushOutput is next called.'
if (id %in% .progressKeys)
return()
.progressKeys <<- c(.progressKeys, id)
json <- toJSON(list(progress=list(id)))
.write(json)
},
dispatch = function(msg) {
method <- paste('@', msg$method, sep='')
func <- try(do.call(`$`, list(.self, method)), silent=T)
if (inherits(func, 'try-error')) {
.sendErrorResponse(msg, paste('Unknown method', msg$method))
}
value <- try(do.call(func, as.list(append(msg$args, msg$blobs))))
if (inherits(value, 'try-error')) {
.sendErrorResponse(msg, paste('Error:', as.character(value)))
}
else {
.sendResponse(msg, value)
}
},
.sendResponse = function(requestMsg, value) {
if (is.null(requestMsg$tag)) {
warning("Tried to send response for untagged message; method: ",
requestMsg$method)
return()
}
.write(toJSON(list(response=list(tag=requestMsg$tag, value=value))))
},
.sendErrorResponse = function(requestMsg, error) {
if (is.null(requestMsg$tag))
return()
.write(toJSON(list(response=list(tag=requestMsg$tag, error=error))))
},
.write = function(json) {
if (getOption('shiny.trace', F))
message('SEND ', json)
if (getOption('shiny.transcode.json', T))
json <- iconv(json, to='UTF-8')
websocket_write(json, .websocket)
},
# Public RPC methods
`@uploadInit` = function() {
return(list(jobId=.fileUploadContext$createUploadOperation()))
},
`@uploadFileBegin` = function(jobId, fileName, fileType, fileSize) {
.fileUploadContext$getUploadOperation(jobId)$fileBegin(list(
name=fileName, type=fileType, size=fileSize
))
invisible()
},
`@uploadFileChunk` = function(jobId, ...) {
args <- list(...)
if (length(args) != 1)
stop("Bad file chunk request")
.fileUploadContext$getUploadOperation(jobId)$fileChunk(args[[1]])
invisible()
},
`@uploadFileEnd` = function(jobId) {
.fileUploadContext$getUploadOperation(jobId)$fileEnd()
invisible()
},
`@uploadEnd` = function(jobId, inputId) {
fileData <- .fileUploadContext$getUploadOperation(jobId)$finish()
session$set(inputId, fileData)
invisible()
}
)
)
statics <- function(root, sys.root=NULL) {
root <- normalizePath(root, mustWork=T)
if (!is.null(sys.root))
sys.root <- normalizePath(sys.root, mustWork=T)
resolve <- function(dir, relpath) {
abs.path <- file.path(dir, relpath)
if (!file.exists(abs.path))
return(NULL)
abs.path <- normalizePath(abs.path, mustWork=T)
if (nchar(abs.path) <= nchar(dir) + 1)
return(NULL)
if (substr(abs.path, 1, nchar(dir)) != dir ||
!(substr(abs.path, nchar(dir)+1, nchar(dir)+1) %in% c('/', '\\'))) {
return(NULL)
}
return(abs.path)
.createOutputWriter <- function(shinyapp) {
ow <- list(impl=shinyapp)
class(ow) <- 'shinyoutput'
return(ow)
}
#' @S3method $<- shinyoutput
`$<-.shinyoutput` <- function(x, name, value) {
x[['impl']]$defineOutput(name, value)
return(invisible(x))
}
resolve <- function(dir, relpath) {
abs.path <- file.path(dir, relpath)
if (!file.exists(abs.path))
return(NULL)
abs.path <- normalizePath(abs.path, winslash='/', mustWork=T)
dir <- normalizePath(dir, winslash='/', mustWork=T)
if (nchar(abs.path) <= nchar(dir) + 1)
return(NULL)
if (substr(abs.path, 1, nchar(dir)) != dir ||
!(substr(abs.path, nchar(dir)+1, nchar(dir)+1) %in% c('/', '\\'))) {
return(NULL)
}
return(abs.path)
}
httpResponse <- function(status = 200,
content_type = "text/html; charset=UTF-8",
content = "") {
resp <- list(status = status, content_type = content_type, content = content);
class(resp) <- 'httpResponse'
return(resp)
}
httpServer <- function(handlers) {
handler <- joinHandlers(handlers)
filter <- getOption('shiny.http.response.filter', NULL)
if (is.null(filter))
filter <- function(ws, header, response) response
function(ws, header) {
response <- handler(ws, header)
if (is.null(response))
response <- httpResponse(404, content="<h1>Not Found</h1>")
response <- filter(ws, header, response)
return(http_response(ws,
status=response$status,
content_type=response$content_type,
content=response$content))
}
}
joinHandlers <- function(handlers) {
handlers <- lapply(handlers, function(h) {
if (is.character(h))
return(staticHandler(h))
else
return(h)
})
# Filter out NULL
handlers <- handlers[!sapply(handlers, is.null)]
if (length(handlers) == 0)
return(function(ws, header) NULL)
if (length(handlers) == 1)
return(handlers[[1]])
function(ws, header) {
for (handler in handlers) {
response <- handler(ws, header)
if (!is.null(response))
return(response)
}
return(NULL)
}
}
dynamicHandler <- function(filePath, dependencyFiles=filePath) {
lastKnownTimestamps <- NA
metaHandler <- function(ws, header) NULL
if (!file.exists(filePath))
return(metaHandler)
return (function(ws, header) {
# Check if we need to rebuild
mtime <- file.info(dependencyFiles)$mtime
if (!identical(lastKnownTimestamps, mtime)) {
lastKnownTimestamps <<- mtime
clearClients()
if (file.exists(filePath)) {
local({
source(filePath, local=T)
})
}
metaHandler <<- joinHandlers(.globals$clients)
clearClients()
}
return(metaHandler(ws, header))
})
}
staticHandler <- function(root) {
return(function(ws, header) {
# TODO: Stop using websockets' internal methods
path <- header$RESOURCE
if (is.null(path))
return(websockets:::.http_400(ws))
return(httpResponse(400, content="<h1>Bad Request</h1>"))
if (path == '/')
path <- '/index.html'
abs.path <- resolve(root, path)
if (is.null(abs.path) && !is.null(sys.root))
abs.path <- resolve(sys.root, path)
if (is.null(abs.path))
return(websockets:::.http_400(ws))
return(NULL)
ext <- tools::file_ext(abs.path)
content.type <- switch(ext,
@@ -113,69 +303,506 @@ statics <- function(root, sys.root=NULL) {
gif='image/gif',
'application/octet-stream')
response.content <- readBin(abs.path, 'raw', n=file.info(abs.path)$size)
return(websockets:::.http_200(ws, content.type, response.content))
return(httpResponse(200, content.type, response.content))
})
}
start.app <- function(app, www.root, sys.www.root=NULL, port=8101L) {
apps <- Map$new()
# Provide a character representation of the WS that can be used
# as a key in a Map.
wsToKey <- function(WS) {
as.character(WS$socket)
}
.globals <- new.env()
.globals$clients <- function(ws, header) NULL
clearClients <- function() {
.globals$clients <- function(ws, header) NULL
}
registerClient <- function(client) {
.globals$clients <- append(.globals$clients, client)
}
.globals$resources <- list()
#' Resource Publishing
#'
#' Adds a directory of static resources to Shiny's web server, with the given
#' path prefix. Primarily intended for package authors to make supporting
#' JavaScript/CSS files available to their components.
#'
#' @param prefix The URL prefix (without slashes). Valid characters are a-z,
#' A-Z, 0-9, hyphen, and underscore; and must begin with a-z or A-Z. For
#' example, a value of 'foo' means that any request paths that begin with
#' '/foo' will be mapped to the given directory.
#' @param directoryPath The directory that contains the static resources to be
#' served.
#'
#' @details You can call \code{addResourcePath} multiple times for a given
#' \code{prefix}; only the most recent value will be retained. If the
#' normalized \code{directoryPath} is different than the directory that's
#' currently mapped to the \code{prefix}, a warning will be issued.
#'
#' @seealso \code{\link{singleton}}
#'
#' @examples
#' addResourcePath('datasets', system.file('data', package='datasets'))
#'
#' @export
addResourcePath <- function(prefix, directoryPath) {
prefix <- prefix[1]
if (!grepl('^[a-z][a-z0-9\\-_]*$', prefix, ignore.case=T, perl=T)) {
stop("addResourcePath called with invalid prefix; please see documentation")
}
ws_env <- create_server(port=port, webpage=statics(www.root, sys.www.root))
if (prefix %in% c('shared')) {
stop("addResourcePath called with the reserved prefix '", prefix, "'; ",
"please use a different prefix")
}
directoryPath <- normalizePath(directoryPath, mustWork=T)
existing <- .globals$resources[[prefix]]
if (!is.null(existing)) {
if (existing$directoryPath != directoryPath) {
warning("Overriding existing prefix ", prefix, " => ",
existing$directoryPath)
}
}
message('Shiny URLs starting with /', prefix, ' will mapped to ', directoryPath)
.globals$resources[[prefix]] <- list(directoryPath=directoryPath,
func=staticHandler(directoryPath))
}
resourcePathHandler <- function(ws, header) {
path <- header$RESOURCE
match <- regexpr('^/([^/]+)/', path, perl=T)
if (match == -1)
return(NULL)
len <- attr(match, 'capture.length')
prefix <- substr(path, 2, 2 + len - 1)
resInfo <- .globals$resources[[prefix]]
if (is.null(resInfo))
return(NULL)
suffix <- substr(path, 2 + len, nchar(path))
header$RESOURCE <- suffix
return(resInfo$func(ws, header))
}
.globals$server <- NULL
#' Define Server Functionality
#'
#' Defines the server-side logic of the Shiny application. This generally
#' involves creating functions that map user inputs to various kinds of output.
#'
#' @param func The server function for this application. See the details section
#' for more information.
#'
#' @details
#' Call \code{shinyServer} from your application's \code{server.R} file, passing
#' in a "server function" that provides the server-side logic of your
#' application.
#'
#' The server function will be called when each client (web browser) first loads
#' the Shiny application's page. It must take an \code{input} and an
#' \code{output} parameter. Any return value will be ignored.
#'
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{tutorial} for more
#' on how to write a server function.
#'
#' @examples
#' \dontrun{
#' # A very simple Shiny app that takes a message from the user
#' # and outputs an uppercase version of it.
#' shinyServer(function(input, output) {
#' output$uppercase <- reactiveText(function() {
#' toupper(input$message)
#' })
#' })
#' }
#'
#' @export
shinyServer <- function(func) {
.globals$server <- func
invisible()
}
decodeMessage <- function(data) {
readInt <- function(pos) {
packBits(rawToBits(data[pos:(pos+3)]), type='integer')
}
if (readInt(1) != 0x01020202L)
return(fromJSON(rawToChar(data), asText=T, simplify=F))
i <- 5
parts <- list()
while (i <= length(data)) {
length <- readInt(i)
i <- i + 4
if (length != 0)
parts <- append(parts, list(data[i:(i+length-1)]))
else
parts <- append(parts, list(raw(0)))
i <- i + length
}
mainMessage <- decodeMessage(parts[[1]])
mainMessage$blobs <- parts[2:length(parts)]
return(mainMessage)
}
# Takes a list-of-lists and returns a matrix. The lists
# must all be the same length. NULL is replaced by NA.
unpackMatrix <- function(data) {
if (length(data) == 0)
return(matrix(nrow=0, ncol=0))
m <- matrix(unlist(lapply(data, function(x) {
sapply(x, function(y) {
ifelse(is.null(y), NA, y)
})
})), nrow = length(data[[1]]), ncol = length(data))
return(m)
}
# Combine dir and (file)name into a file path. If a file already exists with a
# name differing only by case, then use it instead.
file.path.ci <- function(dir, name) {
default <- file.path(dir, name)
if (file.exists(default))
return(default)
if (!file.exists(dir))
return(default)
matches <- list.files(dir, name, ignore.case=TRUE, full.names=TRUE,
include.dirs=TRUE)
if (length(matches) == 0)
return(default)
return(matches[[1]])
}
# Instantiates the app in the current working directory.
# port - The TCP port that the application should listen on.
startApp <- function(port=8101L) {
sys.www.root <- system.file('www', package='shiny')
globalR <- file.path.ci(getwd(), 'global.R')
uiR <- file.path.ci(getwd(), 'ui.R')
serverR <- file.path.ci(getwd(), 'server.R')
wwwDir <- file.path.ci(getwd(), 'www')
if (!file.exists(uiR) && !file.exists(wwwDir))
stop(paste("Neither ui.R nor a www subdirectory was found in", getwd()))
if (!file.exists(serverR))
stop(paste("server.R file was not found in", getwd()))
if (file.exists(globalR))
source(globalR, local=F)
shinyServer(NULL)
serverFileTimestamp <- NULL
local({
serverFileTimestamp <<- file.info(serverR)$mtime
source(serverR, local=T)
if (is.null(.globals$server))
stop("No server was defined in server.R")
})
serverFunc <- .globals$server
ws_env <- create_server(
port=port,
webpage=httpServer(c(dynamicHandler(uiR),
wwwDir,
sys.www.root,
resourcePathHandler)))
set_callback('established', function(WS, ...) {
shinyapp <<- ShinyApp$new(WS)
shinyapp <- ShinyApp$new(WS)
apps$set(wsToKey(WS), shinyapp)
}, ws_env)
set_callback('closed', function(WS, ...) {
}, ws_env)
set_callback('closed', function(WS, ...) {
apps$remove(wsToKey(WS))
}, ws_env)
set_callback('receive', function(DATA, WS, ...) {
# cat(c("RECV", rawToChar(DATA), "\n"))
if (getOption('shiny.trace', F)) {
if (as.raw(0) %in% DATA)
message("RECV ", '$$binary data$$')
else
message("RECV ", rawToChar(DATA))
}
if (identical(charToRaw("\003\xe9"), DATA))
return()
msg <- fromJSON(rawToChar(DATA), asText=T, simplify=F)
shinyapp <- apps$get(wsToKey(WS))
msg <- decodeMessage(DATA)
# Do our own list simplifying here. sapply/simplify2array give names to
# character vectors, which is rarely what we want.
if (!is.null(msg$data)) {
for (name in names(msg$data)) {
val <- msg$data[[name]]
splitName <- strsplit(name, ':')[[1]]
if (length(splitName) > 1) {
msg$data[[name]] <- NULL
# TODO: Make the below a user-extensible registry of deserializers
msg$data[[ splitName[[1]] ]] <- switch(
splitName[[2]],
matrix = unpackMatrix(val),
stop('Unknown type specified for ', name)
)
}
else if (is.list(val) && is.null(names(val)))
msg$data[[name]] <- unlist(val, recursive=F)
}
}
switch(
msg$method,
init = {
# Check if server.R has changed, and if so, reload
mtime <- file.info(serverR)$mtime
if (!identical(mtime, serverFileTimestamp)) {
shinyServer(NULL)
local({
serverFileTimestamp <<- mtime
source(serverR, local=T)
if (is.null(.globals$server))
stop("No server was defined in server.R")
})
serverFunc <<- .globals$server
}
shinyapp$session$mset(msg$data)
flush.react()
flushReact()
local({
define.shiny.output <- function(name, func) {
shinyapp$define.output(name, func)
}
define.shiny.plot <- function(name, func, ...) {
shinyapp$define.plot.output(name, func, ...)
}
define.shiny.table <- function(name, func) {
shinyapp$define.table.output(name, func)
}
get.shiny.input <- function(name) {
shinyapp$session$get(name)
}
if (is.function(app))
app()
else if (is.character(app))
source(app, local=T)
else
warning("Don't know how to configure app; it's neither a function or filename!")
serverFunc(input=.createValuesReader(shinyapp$session),
output=.createOutputWriter(shinyapp))
})
shinyapp$instantiate.outputs()
},
update = {
shinyapp$session$mset(msg$data)
})
flush.react()
shinyapp$flush.output()
},
shinyapp$dispatch(msg)
)
flushReact()
shinyapp$flushOutput()
}, ws_env)
cat(paste('Listening on http://0.0.0.0:', port, "\n", sep=''))
message('\n', 'Listening on port ', port)
return(ws_env)
}
run.app <- function(ws_env) {
while (T)
service(server=ws_env)
# NOTE: we de-roxygenized this comment because the function isn't exported
# Run an application that was created by \code{\link{startApp}}. This
# function should normally be called in a \code{while(T)} loop.
#
# @param ws_env The return value from \code{\link{startApp}}.
serviceApp <- function(ws_env) {
if (timerCallbacks$executeElapsed()) {
flushReact()
lapply(apps$values(), function(shinyapp) {
shinyapp$flushOutput()
NULL
})
}
# If this R session is interactive, then call service() with a short timeout
# to keep the session responsive to user input
maxTimeout <- ifelse(interactive(), 100, 5000)
timeout <- max(1, min(maxTimeout, timerCallbacks$timeToNextEvent()))
service(server=ws_env, timeout=timeout)
}
#' Run Shiny Application
#'
#' Runs a Shiny application. This function normally does not return; interrupt
#' R to stop the application (usually by pressing Ctrl+C or Esc).
#'
#' @param appDir The directory of the application. Should contain
#' \code{server.R}, plus, either \code{ui.R} or a \code{www} directory that
#' contains the file \code{index.html}. Defaults to the working directory.
#' @param port The TCP port that the application should listen on. Defaults to
#' port 8100.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
#'
#' @export
runApp <- function(appDir=getwd(),
port=8100L,
launch.browser=getOption('shiny.launch.browser',
interactive())) {
# Make warnings print immediately
ops <- options(warn = 1)
on.exit(options(ops))
orig.wd <- getwd()
setwd(appDir)
on.exit(setwd(orig.wd))
ws_env <- startApp(port=port)
if (launch.browser) {
appUrl <- paste("http://localhost:", port, sep="")
utils::browseURL(appUrl)
}
tryCatch(
while (T) {
serviceApp(ws_env)
},
finally = {
websocket_close(ws_env)
}
)
}
#' Run Shiny Example Applications
#'
#' Launch Shiny example applications, and optionally, your system's web browser.
#'
#' @param example The name of the example to run, or \code{NA} (the default) to
#' list the available examples.
#' @param port The TCP port that the application should listen on. Defaults to
#' port 8100.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
#'
#' @export
runExample <- function(example=NA,
port=8100L,
launch.browser=getOption('shiny.launch.browser',
interactive())) {
examplesDir <- system.file('examples', package='shiny')
dir <- resolve(examplesDir, example)
if (is.null(dir)) {
if (is.na(example)) {
errFun <- message
errMsg <- ''
}
else {
errFun <- stop
errMsg <- paste('Example', example, 'does not exist. ')
}
errFun(errMsg,
'Valid examples are "',
paste(list.files(examplesDir), collapse='", "'),
'"')
}
else {
runApp(dir, port = port, launch.browser = launch.browser)
}
}
# This is a wrapper for download.file and has the same interface.
# The only difference is that, if the protocol is https, it changes the
# download settings, depending on platform.
download <- function(url, ...) {
# First, check protocol. If https, check platform:
if (grepl('^https://', url)) {
# If Windows, call setInternet2, then use download.file with defaults.
if (.Platform$OS.type == "windows") {
# If we directly use setInternet2, R CMD CHECK gives a Note on Mac/Linux
mySI2 <- `::`(utils, 'setInternet2')
# Store initial settings
internet2_start <- mySI2(NA)
on.exit(mySI2(internet2_start))
# Needed for https
mySI2(TRUE)
download.file(url, ...)
# If non-Windows, check for curl/wget/lynx, then call download.file with
# appropriate method.
} else {
if (system("wget --help > /dev/null") == 0L)
method <- "wget"
else if (system("curl --help > /dev/null") == 0L)
method <- "curl"
else if (system("lynx -help > /dev/null") == 0L)
method <- "lynx"
else
stop("no download method found")
download.file(url, method = method, ...)
}
} else {
download.file(url, ...)
}
}
#' Run a Shiny application from https://gist.github.com
#'
#' Download and launch a Shiny application that is hosted on GitHub as a gist.
#'
#' @param gist The identifier of the gist. For example, if the gist is
#' https://gist.github.com/3239667, then \code{3239667}, \code{'3239667'}, and
#' \code{'https://gist.github.com/3239667'} are all valid values.
#' @param port The TCP port that the application should listen on. Defaults to
#' port 8100.
#' @param launch.browser If true, the system's default web browser will be
#' launched automatically after the app is started. Defaults to true in
#' interactive sessions only.
#'
#' @export
runGist <- function(gist,
port=8100L,
launch.browser=getOption('shiny.launch.browser',
interactive())) {
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
sprintf('https://gist.github.com/gists/%s/download', gist)
} else if(grepl('^https://gist.github.com/([0-9a-f]+)$', gist)) {
paste(sub('https://gist.github.com/',
'https://gist.github.com/gists/',
gist),
'/download',
sep='')
} else {
stop('Unrecognized gist identifier format')
}
filePath <- tempfile('shinygist', fileext='.tar.gz')
if (download(gistUrl, filePath, mode = "wb", quiet = TRUE) != 0)
stop("Failed to download URL ", gistUrl)
on.exit(unlink(filePath))
dirname <- untar(filePath, list=TRUE)[1]
untar(filePath, exdir=dirname(filePath))
appdir <- file.path(dirname(filePath), dirname)
on.exit(unlink(appdir, recursive = TRUE))
shiny::runApp(appdir, port=port, launch.browser=launch.browser)
}

177
R/shinyui.R Normal file
View File

@@ -0,0 +1,177 @@
#' @export
p <- function(...) tags$p(...)
#' @export
h1 <- function(...) tags$h1(...)
#' @export
h2 <- function(...) tags$h2(...)
#' @export
h3 <- function(...) tags$h3(...)
#' @export
h4 <- function(...) tags$h4(...)
#' @export
h5 <- function(...) tags$h5(...)
#' @export
h6 <- function(...) tags$h6(...)
#' @export
a <- function(...) tags$a(...)
#' @export
br <- function(...) tags$br(...)
#' @export
div <- function(...) tags$div(...)
#' @export
span <- function(...) tags$span(...)
#' @export
pre <- function(...) tags$pre(...)
#' @export
code <- function(...) tags$code(...)
#' @export
img <- function(...) tags$img(...)
#' @export
strong <- function(...) tags$strong(...)
#' @export
em <- function(...) tags$em(...)
#' Include Content Only Once
#'
#' Use \code{singleton} to wrap contents (tag, text, HTML, or lists) that should
#' be included in the generated document only once, yet may appear in the
#' document-generating code more than once. Only the first appearance of the
#' content (in document order) will be used. Useful for custom components that
#' have JavaScript files or stylesheets.
#'
#' @param x A \code{\link{tag}}, text, \code{\link{HTML}}, or list.
#'
#' @export
singleton <- function(x) {
class(x) <- c(class(x), 'shiny.singleton')
return(x)
}
renderPage <- function(ui, connection) {
# provide a filter so we can intercept head tag requests
context <- new.env()
context$head <- character()
context$singletons <- character()
context$filter <- function(content) {
if (inherits(content, 'shiny.singleton')) {
sig <- digest(content, algo='sha1')
if (sig %in% context$singletons)
return(FALSE)
context$singletons <- c(sig, context$singletons)
}
if (isTag(content) && identical(content$name, "head")) {
textConn <- textConnection(NULL, "w")
textConnWriter <- function(text) cat(text, file = textConn)
tagWriteChildren(content, textConnWriter, 1, context)
context$head <- append(context$head, textConnectionValue(textConn))
close(textConn)
return (FALSE)
}
else {
return (TRUE)
}
}
# write ui HTML to a character vector
textConn <- textConnection(NULL, "w")
tagWrite(ui, function(text) cat(text, file = textConn), 0, context)
uiHTML <- textConnectionValue(textConn)
close(textConn)
# write preamble
writeLines(c('<!DOCTYPE html>',
'<html>',
'<head>',
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
' <script src="shared/jquery.js" type="text/javascript"></script>',
' <script src="shared/shiny.js" type="text/javascript"></script>',
' <link rel="stylesheet" type="text/css" href="shared/shiny.css"/>',
context$head,
'</head>',
'<body>',
recursive=TRUE),
con = connection)
# write UI html to connection
writeLines(uiHTML, con = connection)
# write end document
writeLines(c('</body>',
'</html>'),
con = connection)
}
#' Create a Shiny UI handler
#'
#' Register a UI handler by providing a UI definition (created with e.g.
#' \link{pageWithSidebar}) and web server path (typically "/", the default
#' value).
#'
#' @param ui A user-interace definition
#' @param path The web server path to server the UI from
#' @return Called for its side-effect of registering a UI handler
#'
#' @examples
#' el <- div(HTML("I like <u>turtles</u>"))
#' cat(as.character(el))
#'
#' @examples
#' # Define UI
#' shinyUI(pageWithSidebar(
#'
#' # Application title
#' headerPanel("Hello Shiny!"),
#'
#' # Sidebar with a slider input
#' sidebarPanel(
#' sliderInput("obs",
#' "Number of observations:",
#' min = 0,
#' max = 1000,
#' value = 500)
#' ),
#'
#' # Show a plot of the generated distribution
#' mainPanel(
#' plotOutput("distPlot")
#' )
#' ))
#'
#' @export
shinyUI <- function(ui, path='/') {
registerClient({
function(ws, header) {
if (header$RESOURCE != path)
return(NULL)
textConn <- textConnection(NULL, "w")
on.exit(close(textConn))
renderPage(ui, textConn)
html <- paste(textConnectionValue(textConn), collapse='\n')
return(httpResponse(200, content=html))
}
})
}

162
R/shinywrappers.R Normal file
View File

@@ -0,0 +1,162 @@
suppressPackageStartupMessages({
library(caTools)
library(xtable)
})
#' Plot Output
#'
#' Creates a reactive plot that is suitable for assigning to an \code{output}
#' slot.
#'
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
#' the CSS class name \code{shiny-plot-output}.
#'
#' @param func A function that generates a plot.
#' @param width The width of the rendered plot, in pixels; or \code{'auto'} to use
#' the \code{offsetWidth} of the HTML element that is bound to this plot.
#' @param height The height of the rendered plot, in pixels; or \code{'auto'} to use
#' the \code{offsetHeight} of the HTML element that is bound to this plot.
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
#' These can be used to set the width, height, background color, etc.
#'
#' @export
reactivePlot <- function(func, width='auto', height='auto', ...) {
args <- list(...)
return(function(shinyapp, name, ...) {
png.file <- tempfile(fileext='.png')
# Note that these are reactive calls. A change to the width and height
# will inherently cause a reactive plot to redraw (unless width and
# height were explicitly specified).
prefix <- '.shinyout_'
if (width == 'auto')
width <- shinyapp$session$get(paste(prefix, name, '_width', sep=''));
if (height == 'auto')
height <- shinyapp$session$get(paste(prefix, name, '_height', sep=''));
if (width <= 0 || height <= 0)
return(NULL)
do.call(png, c(args, filename=png.file, width=width, height=height))
tryCatch(
func(),
finally=dev.off())
bytes <- file.info(png.file)$size
if (is.na(bytes))
return(NULL)
b64 <- base64encode(readBin(png.file, 'raw', n=bytes))
return(paste("data:image/png;base64,", b64, sep=''))
})
}
#' Table Output
#'
#' Creates a reactive table that is suitable for assigning to an \code{output}
#' slot.
#'
#' The corresponding HTML output tag should be \code{div} and have the CSS class
#' name \code{shiny-html-output}.
#'
#' @param func A function that returns an R object that can be used with
#' \code{\link[xtable]{xtable}}.
#' @param ... Arguments to be passed through to \code{\link[xtable]{xtable}}.
#'
#' @export
reactiveTable <- function(func, ...) {
reactive(function() {
classNames <- getOption('shiny.table.class', 'data table table-bordered table-condensed')
data <- func()
if (is.null(data) || is.na(data))
return("")
return(paste(
capture.output(
print(xtable(data, ...),
type='html',
html.table.attributes=paste('class="',
htmlEscape(classNames, T),
'"',
sep=''))),
collapse="\n"))
})
}
#' Printable Output
#'
#' Makes a reactive version of the given function that also turns its printable
#' result into a string. The reactive function is suitable for assigning to an
#' \code{output} slot.
#'
#' The corresponding HTML output tag can be anything (though \code{pre} is
#' recommended if you need a monospace font and whitespace preserved) and should
#' have the CSS class name \code{shiny-text-output}.
#'
#' The result of executing \code{func} will be printed inside a
#' \code{\link[utils]{capture.output}} call.
#'
#' @param func A function that returns a printable R object.
#'
#' @export
reactivePrint <- function(func) {
reactive(function() {
return(paste(capture.output(print(func())), collapse="\n"))
})
}
#' Text Output
#'
#' Makes a reactive version of the given function that also uses
#' \code{\link[base]{cat}} to turn its result into a single-element character
#' vector.
#'
#' The corresponding HTML output tag can be anything (though \code{pre} is
#' recommended if you need a monospace font and whitespace preserved) and should
#' have the CSS class name \code{shiny-text-output}.
#'
#' The result of executing \code{func} will passed to \code{cat}, inside a
#' \code{\link[utils]{capture.output}} call.
#'
#' @param func A function that returns an R object that can be used as an
#' argument to \code{cat}.
#'
#' @export
reactiveText <- function(func) {
reactive(function() {
return(paste(capture.output(cat(func())), collapse="\n"))
})
}
#' UI Output
#'
#' \bold{Experimental feature.} Makes a reactive version of a function that
#' generates HTML using the Shiny UI library.
#'
#' The corresponding HTML output tag should be \code{div} and have the CSS class
#' name \code{shiny-html-output} (or use \code{\link{uiOutput}}).
#'
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
#' or a list of such objects.
#'
#' @seealso conditionalPanel
#'
#' @export
#' @examples
#' \dontrun{
#' output$moreControls <- reactiveUI(function() {
#' list(
#'
#' )
#' })
#' }
reactiveUI <- function(func) {
reactive(function() {
result <- func()
if (is.null(result) || length(result) == 0)
return(NULL)
return(as.character(result))
})
}

143
R/slider.R Normal file
View File

@@ -0,0 +1,143 @@
hasDecimals <- function(value) {
truncatedValue <- round(value)
return (!identical(value, truncatedValue))
}
#' Animation Options
#'
#' Creates an options object for customizing animations for \link{sliderInput}.
#'
#' @param interval The interval, in milliseconds, between each animation step.
#' @param loop \code{TRUE} to automatically restart the animation when it
#' reaches the end.
#' @param playButton Specifies the appearance of the play button. Valid values
#' are a one-element character vector (for a simple text label), an HTML tag
#' or list of tags (using \code{\link{tag}} and friends), or raw HTML (using
#' \code{\link{HTML}}).
#' @param pauseButton Similar to \code{playButton}, but for the pause button.
#'
#' @export
animationOptions <- function(interval=1000,
loop=FALSE,
playButton=NULL,
pauseButton=NULL) {
list(interval=interval,
loop=loop,
playButton=playButton,
pauseButton=pauseButton)
}
# Create a new slider control (list of slider input element and the script
# tag used to configure it). This is a lower level control that should
# be wrapped in an "input" construct (e.g. sliderInput in bootstrap.R)
#
# this is a wrapper for: https://github.com/egorkhmelev/jslider
# (www/shared/slider contains js, css, and img dependencies)
slider <- function(inputId, min, max, value, step = NULL, ...,
round=FALSE, format='#,##0.#####', locale='us',
ticks=TRUE, animate=FALSE) {
# validate inputId
inputId <- as.character(inputId)
if (!is.character(inputId))
stop("inputId not specified")
# validate numeric inputs
if (!is.numeric(value) || !is.numeric(min) || !is.numeric(max))
stop("min, max, amd value must all be numeric values")
else if (min(value) < min)
stop(paste("slider initial value", value,
"is less than the specified minimum"))
else if (max(value) > max)
stop(paste("slider initial value", value,
"is greater than the specified maximum"))
else if (min > max)
stop(paste("slider maximum is greater than minimum"))
else if (!is.null(step)) {
if (!is.numeric(step))
stop("step is not a numeric value")
if (step > (max - min))
stop("step is greater than range")
}
# step
range <- max - min
if (is.null(step)) {
# short range or decimals means continuous decimal
if (range < 2 || hasDecimals(min) || hasDecimals(max))
step <- range / 250 # ~ one step per pixel
else
step = 1
}
# Default state is to not have ticks
if (identical(ticks, T)) {
# Automatic ticks
tickCount <- (range / step) + 1
if (tickCount <= 26)
ticks <- paste(rep('|', floor(tickCount)), collapse=';')
else {
ticks <- NULL
# # This is a smarter auto-tick algorithm, but to be truly useful
# # we need jslider to be able to space ticks irregularly
# tickSize <- 10^(floor(log10(range/0.39)))
# if ((range / tickSize) == floor(range / tickSize)) {
# ticks <- paste(rep('|', (range / tickSize) + 1), collapse=';')
# }
# else {
# ticks <- NULL
# }
}
}
else if (is.numeric(ticks) && length(ticks) == 1) {
# Use n ticks
ticks <- paste(rep('|', ticks), collapse=';')
}
else if (length(ticks) > 1 && (is.numeric(ticks) || is.character(ticks))) {
# Explicit ticks
ticks <- paste(ticks, collapse=';')
}
else {
ticks <- NULL
}
# build slider
sliderFragment <- list(
singleton(
tags$head(
tags$link(rel="stylesheet",
type="text/css",
href="shared/slider/css/jquery.slider.min.css"),
tags$script(src="shared/slider/js/jquery.slider.min.js")
)
),
tags$input(id=inputId, type="slider",
name=inputId, value=paste(value, collapse=';'), class="jslider",
'data-from'=min, 'data-to'=max, 'data-step'=step,
'data-skin'='plastic', 'data-round'=round, 'data-locale'=locale,
'data-format'=format, 'data-scale'=ticks,
'data-smooth'=FALSE)
)
if (identical(animate, T))
animate <- animationOptions()
if (!is.null(animate) && !identical(animate, F)) {
if (is.null(animate$playButton))
animate$playButton <- 'Play'
if (is.null(animate$pauseButton))
animate$pauseButton <- 'Pause'
sliderFragment[[length(sliderFragment)+1]] <-
tags$div(class='slider-animate-container',
tags$a(href='#',
class='slider-animate-button',
'data-target-id'=inputId,
'data-interval'=animate$interval,
'data-loop'=animate$loop,
tags$span(class='play', animate$playButton),
tags$span(class='pause', animate$pauseButton)))
}
return(sliderFragment)
}

380
R/tags.R Normal file
View File

@@ -0,0 +1,380 @@
htmlEscape <- local({
.htmlSpecials <- list(
`&` = '&amp;',
`<` = '&lt;',
`>` = '&gt;'
)
.htmlSpecialsPattern <- paste(names(.htmlSpecials), collapse='|')
.htmlSpecialsAttrib <- c(
.htmlSpecials,
`'` = '&#39;',
`"` = '&quot;',
`\r` = '&#13;',
`\n` = '&#10;'
)
.htmlSpecialsPatternAttrib <- paste(names(.htmlSpecialsAttrib), collapse='|')
function(text, attribute=T) {
pattern <- if(attribute)
.htmlSpecialsPatternAttrib
else
.htmlSpecialsPattern
# Short circuit in the common case that there's nothing to escape
if (!grepl(pattern, text))
return(text)
specials <- if(attribute)
.htmlSpecialsAttrib
else
.htmlSpecials
for (chr in names(specials)) {
text <- gsub(chr, specials[[chr]], text, fixed=T)
}
return(text)
}
})
isTag <- function(x) {
inherits(x, "shiny.tag")
}
#' @S3method print shiny.tag
print.shiny.tag <- function(x, ...) {
print(as.character(x), ...)
}
#' @S3method format shiny.tag
format.shiny.tag <- function(x, ...) {
as.character.shiny.tag(x)
}
#' @S3method as.character shiny.tag
as.character.shiny.tag <- function(x, ...) {
f = file()
on.exit(close(f))
textWriter <- function(text) {
cat(text, file=f)
}
tagWrite(x, textWriter)
return(HTML(paste(readLines(f), collapse='\n')))
}
#' @S3method print shiny.tag.list
print.shiny.tag.list <- print.shiny.tag
#' @S3method format shiny.tag.list
format.shiny.tag.list <- format.shiny.tag
#' @S3method as.character shiny.tag.list
as.character.shiny.tag.list <- as.character.shiny.tag
normalizeText <- function(text) {
if (!is.null(attr(text, "html")))
text
else
htmlEscape(text, attribute=FALSE)
}
#' @export
tagList <- function(...) {
lst <- list(...)
class(lst) <- c("shiny.tag.list", "list")
return(lst)
}
#' @export
tagAppendChild <- function(tag, child) {
tag$children[[length(tag$children)+1]] <- child
tag
}
#' @export
tag <- function(`_tag_name`, varArgs) {
# create basic tag data structure
tag <- list()
class(tag) <- "shiny.tag"
tag$name <- `_tag_name`
tag$attribs <- list()
tag$children <- list()
# process varArgs
varArgsNames <- names(varArgs)
if (is.null(varArgsNames))
varArgsNames <- character(length=length(varArgs))
if (length(varArgsNames) > 0) {
for (i in 1:length(varArgsNames)) {
# save name and value
name <- varArgsNames[[i]]
value <- varArgs[[i]]
# process attribs
if (nzchar(name))
tag$attribs[[name]] <- value
# process child tags
else if (isTag(value)) {
tag$children[[length(tag$children)+1]] <- value
}
# recursively process lists of children
else if (is.list(value)) {
tagAppendChildren <- function(tag, children) {
for(child in children) {
if (isTag(child))
tag <- tagAppendChild(tag, child)
else if (is.list(child))
tag <- tagAppendChildren(tag, child)
else if (is.character(child))
tag <- tagAppendChild(tag, child)
else
tag <- tagAppendChild(tag, as.character(child))
}
return (tag)
}
tag <- tagAppendChildren(tag, value)
}
# add text
else if (is.character(value)) {
tag <- tagAppendChild(tag, value)
}
# everything else treated as text
else {
tag <- tagAppendChild(tag, as.character(value))
}
}
}
# return the tag
return (tag)
}
tagWriteChildren <- function(tag, textWriter, indent, context) {
for (child in tag$children) {
if (isTag(child)) {
tagWrite(child, textWriter, indent, context)
}
else {
# first call optional filter -- exit function if it returns false
if (is.null(context) || is.null(context$filter) || context$filter(child)) {
child <- normalizeText(child)
indentText <- paste(rep(" ", indent*3), collapse="")
textWriter(paste(indentText, child, "\n", sep=""))
}
}
}
}
tagWrite <- function(tag, textWriter, indent=0, context = NULL) {
# optionally process a list of tags
if (!isTag(tag) && is.list(tag)) {
sapply(tag, function(t) tagWrite(t, textWriter, indent, context))
return (NULL)
}
# first call optional filter -- exit function if it returns false
if (!is.null(context) && !is.null(context$filter) && !context$filter(tag))
return (NULL)
# compute indent text
indentText <- paste(rep(" ", indent*3), collapse="")
# write tag name
textWriter(paste(indentText, "<", tag$name, sep=""))
# write attributes
for (attrib in names(tag$attribs)) {
attribValue <- tag$attribs[[attrib]]
if (!is.na(attribValue)) {
if (is.logical(attribValue))
attribValue <- tolower(attribValue)
text <- htmlEscape(attribValue, attribute=TRUE)
textWriter(paste(" ", attrib,"=\"", text, "\"", sep=""))
}
else {
textWriter(paste(" ", attrib, sep=""))
}
}
# write any children
if (length(tag$children) > 0) {
# special case for a single child text node (skip newlines and indentation)
if ((length(tag$children) == 1) && is.character(tag$children[[1]]) ) {
if (is.null(context) || is.null(context$filter)
|| context$filter(tag$children[[1]])) {
text <- normalizeText(tag$children[[1]])
textWriter(paste(">", text, "</", tag$name, ">\n", sep=""))
}
}
else {
textWriter(">\n")
tagWriteChildren(tag, textWriter, indent+1, context)
textWriter(paste(indentText, "</", tag$name, ">\n", sep=""))
}
}
else {
# only self-close void elements
# (see: http://dev.w3.org/html5/spec/single-page.html#void-elements)
if (tag$name %in% c("area", "base", "br", "col", "command", "embed", "hr",
"img", "input", "keygen", "link", "meta", "param",
"source", "track", "wbr")) {
textWriter("/>\n")
}
else {
textWriter(paste("></", tag$name, ">\n", sep=""))
}
}
}
# environment used to store all available tags
#' @export
tags <- new.env()
tags$a <- function(...) tag("a", list(...))
tags$abbr <- function(...) tag("abbr", list(...))
tags$address <- function(...) tag("address", list(...))
tags$area <- function(...) tag("area", list(...))
tags$article <- function(...) tag("article", list(...))
tags$aside <- function(...) tag("aside", list(...))
tags$audio <- function(...) tag("audio", list(...))
tags$b <- function(...) tag("b", list(...))
tags$base <- function(...) tag("base", list(...))
tags$bdi <- function(...) tag("bdi", list(...))
tags$bdo <- function(...) tag("bdo", list(...))
tags$blockquote <- function(...) tag("blockquote", list(...))
tags$body <- function(...) tag("body", list(...))
tags$br <- function(...) tag("br", list(...))
tags$button <- function(...) tag("button", list(...))
tags$canvas <- function(...) tag("canvas", list(...))
tags$caption <- function(...) tag("caption", list(...))
tags$cite <- function(...) tag("cite", list(...))
tags$code <- function(...) tag("code", list(...))
tags$col <- function(...) tag("col", list(...))
tags$colgroup <- function(...) tag("colgroup", list(...))
tags$command <- function(...) tag("command", list(...))
tags$data <- function(...) tag("data", list(...))
tags$datalist <- function(...) tag("datalist", list(...))
tags$dd <- function(...) tag("dd", list(...))
tags$del <- function(...) tag("del", list(...))
tags$details <- function(...) tag("details", list(...))
tags$dfn <- function(...) tag("dfn", list(...))
tags$div <- function(...) tag("div", list(...))
tags$dl <- function(...) tag("dl", list(...))
tags$dt <- function(...) tag("dt", list(...))
tags$em <- function(...) tag("em", list(...))
tags$embed <- function(...) tag("embed", list(...))
tags$eventsource <- function(...) tag("eventsource", list(...))
tags$fieldset <- function(...) tag("fieldset", list(...))
tags$figcaption <- function(...) tag("figcaption", list(...))
tags$figure <- function(...) tag("figure", list(...))
tags$footer <- function(...) tag("footer", list(...))
tags$form <- function(...) tag("form", list(...))
tags$h1 <- function(...) tag("h1", list(...))
tags$h2 <- function(...) tag("h2", list(...))
tags$h3 <- function(...) tag("h3", list(...))
tags$h4 <- function(...) tag("h4", list(...))
tags$h5 <- function(...) tag("h5", list(...))
tags$h6 <- function(...) tag("h6", list(...))
tags$head <- function(...) tag("head", list(...))
tags$header <- function(...) tag("header", list(...))
tags$hgroup <- function(...) tag("hgroup", list(...))
tags$hr <- function(...) tag("hr", list(...))
tags$html <- function(...) tag("html", list(...))
tags$i <- function(...) tag("i", list(...))
tags$iframe <- function(...) tag("iframe", list(...))
tags$img <- function(...) tag("img", list(...))
tags$input <- function(...) tag("input", list(...))
tags$ins <- function(...) tag("ins", list(...))
tags$kbd <- function(...) tag("kbd", list(...))
tags$keygen <- function(...) tag("keygen", list(...))
tags$label <- function(...) tag("label", list(...))
tags$legend <- function(...) tag("legend", list(...))
tags$li <- function(...) tag("li", list(...))
tags$link <- function(...) tag("link", list(...))
tags$mark <- function(...) tag("mark", list(...))
tags$map <- function(...) tag("map", list(...))
tags$menu <- function(...) tag("menu", list(...))
tags$meta <- function(...) tag("meta", list(...))
tags$meter <- function(...) tag("meter", list(...))
tags$nav <- function(...) tag("nav", list(...))
tags$noscript <- function(...) tag("noscript", list(...))
tags$object <- function(...) tag("object", list(...))
tags$ol <- function(...) tag("ol", list(...))
tags$optgroup <- function(...) tag("optgroup", list(...))
tags$option <- function(...) tag("option", list(...))
tags$output <- function(...) tag("output", list(...))
tags$p <- function(...) tag("p", list(...))
tags$param <- function(...) tag("param", list(...))
tags$pre <- function(...) tag("pre", list(...))
tags$progress <- function(...) tag("progress", list(...))
tags$q <- function(...) tag("q", list(...))
tags$ruby <- function(...) tag("ruby", list(...))
tags$rp <- function(...) tag("rp", list(...))
tags$rt <- function(...) tag("rt", list(...))
tags$s <- function(...) tag("s", list(...))
tags$samp <- function(...) tag("samp", list(...))
tags$script <- function(...) tag("script", list(...))
tags$section <- function(...) tag("section", list(...))
tags$select <- function(...) tag("select", list(...))
tags$small <- function(...) tag("small", list(...))
tags$source <- function(...) tag("source", list(...))
tags$span <- function(...) tag("span", list(...))
tags$strong <- function(...) tag("strong", list(...))
tags$style <- function(...) tag("style", list(...))
tags$sub <- function(...) tag("sub", list(...))
tags$summary <- function(...) tag("summary", list(...))
tags$sup <- function(...) tag("sup", list(...))
tags$table <- function(...) tag("table", list(...))
tags$tbody <- function(...) tag("tbody", list(...))
tags$td <- function(...) tag("td", list(...))
tags$textarea <- function(...) tag("textarea", list(...))
tags$tfoot <- function(...) tag("tfoot", list(...))
tags$th <- function(...) tag("th", list(...))
tags$thead <- function(...) tag("thead", list(...))
tags$time <- function(...) tag("time", list(...))
tags$title <- function(...) tag("title", list(...))
tags$tr <- function(...) tag("tr", list(...))
tags$track <- function(...) tag("track", list(...))
tags$u <- function(...) tag("u", list(...))
tags$ul <- function(...) tag("ul", list(...))
tags$var <- function(...) tag("var", list(...))
tags$video <- function(...) tag("video", list(...))
tags$wbr <- function(...) tag("wbr", list(...))
#' Mark Characters as HTML
#'
#' Marks the given text as HTML, which means the \link{tag} functions will know
#' not to perform HTML escaping on it.
#'
#' @param text The text value to mark with HTML
#' @param ... Any additional values to be converted to character and
#' concatenated together
#' @return The same value, but marked as HTML.
#'
#' @examples
#' el <- div(HTML("I like <u>turtles</u>"))
#' cat(as.character(el))
#'
#' @export
HTML <- function(text, ...) {
htmlText <- c(text, as.character(list(...)))
htmlText <- paste(htmlText, collapse=" ")
attr(htmlText, "html") <- TRUE
htmlText
}

67
R/timer.R Normal file
View File

@@ -0,0 +1,67 @@
# Return the current time, in milliseconds from epoch, with
# unspecified time zone.
now <- function() {
as.numeric(Sys.time()) * 1000
}
TimerCallbacks <- setRefClass(
'TimerCallbacks',
fields = list(
.nextId = 'integer',
.funcs = 'Map',
.times = 'data.frame'
),
methods = list(
initialize = function() {
.nextId <<- 0L
},
schedule = function(millis, func) {
id <- .nextId
.nextId <<- .nextId + 1L
t <- now()
# TODO: Horribly inefficient, use a heap instead
.times <<- rbind(.times, data.frame(time=t+millis,
scheduled=t,
id=id))
.times <<- .times[order(.times$time),]
.funcs$set(as.character(id), func)
return(id)
},
timeToNextEvent = function() {
if (dim(.times)[1] == 0)
return(Inf)
return(.times[1, 'time'] - now())
},
takeElapsed = function() {
t <- now()
elapsed <- .times$time < now()
result <- .times[elapsed,]
.times <<- .times[!elapsed,]
# TODO: Examine scheduled column to check if any funny business
# has occurred with the system clock (e.g. if scheduled
# is later than now())
return(result)
},
executeElapsed = function() {
elapsed <- takeElapsed()
if (length(elapsed) == 0)
return(F)
for (id in elapsed$id) {
thisFunc <- .funcs$remove(as.character(id))
# TODO: Catch exception, and...?
# TODO: Detect NULL, and...?
thisFunc()
}
return(T)
}
)
)
timerCallbacks <- TimerCallbacks$new()

View File

@@ -1,10 +1,38 @@
# Shiny
### A web framework for R (eventually--Ruby for now)
# Shiny
```sh
sudo apt-get install ruby1.9.1 ruby1.9.1-dev
sudo gem install bundler
cd shiny
bundle install --path vendor
./run.sh
```
Shiny is a new package from RStudio that makes it incredibly easy to build interactive web applications with R.
## Features
* Build useful web applications with only a few lines of code&mdash;no JavaScript required.
* 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 [Twitter Bootstrap](http://twitter.github.com/bootstrap).
* 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.
* Fast bidirectional communication between the web browser and R using the [websockets](http://illposed.net/websockets.html) 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!).
## Installation
From an R console:
```r
options(repos=c(RStudio="http://rstudio.org/_packages", getOption("repos")))
install.packages("shiny")
```
## 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.
We hope you enjoy using Shiny. As you learn more and work with the package please [let us know](https://github.com/rstudio/shiny/issues) what problems you encounter and how you'd like to see Shiny evolve.
## License
The shiny package is licensed under the GPLv3. See these files in the inst directory for additional details:
- COPYING - shiny package license (GPLv3)
- NOTICE - Copyright notices for additional included software

View File

@@ -1,15 +0,0 @@
library(digest)
input <- Observable$new(function() {
str <- get.shiny.input('input1')
if (get.shiny.input('addnewline'))
str <- paste(str, "\n", sep='')
return(str)
})
define.shiny.output('md5_hash', function() {
digest(input$get.value(), algo='md5', serialize=F)
})
define.shiny.output('sha1_hash', function() {
digest(input$get.value(), algo='sha1', serialize=F)
})

View File

@@ -1,26 +0,0 @@
<html>
<head>
<script src="shared/jquery-1.7.2.js" type="text/javascript"></script>
<script src="shared/shiny.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
</head>
<body>
<h1>Example 2: Hash Calculation</h1>
<p>
<label>Input:</label><br />
<input name="input1" value="Hello World!"/>
<input type="checkbox" name="addnewline" checked="checked"/> Append newline
</p>
<p>
<label>MD5:</label><br />
<pre id="md5_hash" class="live-text"></pre>
</p>
<p>
<label>SHA-1:</label><br />
<pre id="sha1_hash" class="live-text"></pre>
</p>
</body>
</html>

View File

@@ -1,24 +0,0 @@
data <- Observable$new(function() {
# Choose a distribution function
dist <- switch(get.shiny.input('dist'),
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
# Generate n values from the distribution function
dist(max(1, get.shiny.input('n')))
})
define.shiny.plot('plot1', function() {
dist <- get.shiny.input('dist')
n <- get.shiny.input('n')
hist(data$get.value(),
main=paste('r', dist, '(', n, ')', sep=''))
}, width=600, height=300)
define.shiny.table('table1', function() {
data.frame(x=data$get.value())
})

19
hash.rb
View File

@@ -1,19 +0,0 @@
require 'shiny'
require 'digest/sha1'
require 'digest/md5'
shinyapp = ShinyApp.new
input1 = React::ObservableValue.new {
shinyapp.session.get('input1') + (shinyapp.session.get('addnewline') ? "\n" : '')
}
shinyapp.define_output('md5_hash') do
Digest::MD5.hexdigest(input1.value)
end
shinyapp.define_output('sha1_hash') do
Digest::SHA1.hexdigest(input1.value)
end
shinyapp.run

678
inst/COPYING Normal file
View File

@@ -0,0 +1,678 @@
The shiny package is licensed to you under the GPLv3, the terms of
which are included below. The markdown pacakge includes other open
source software whose license terms can be found in the file NOTICE.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

264
inst/NOTICE Normal file
View File

@@ -0,0 +1,264 @@
The shiny package inludes other open source software components. The following
is a list of these components (full copies of the license agreements used by
these components are included below):
- jQuery
- Bootstrap
- jslider
jQuery License
----------------------------------------------------------------------
Copyright (c) 2012 jQuery Foundation and other contributors,
http://jquery.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Bootstrap License
----------------------------------------------------------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
jslider License
----------------------------------------------------------------------
The MIT License (MIT)
Copyright (c) 2012 Egor Khmelev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,20 @@
library(shiny)
# Define server logic required to generate and plot a random distribution
shinyServer(function(input, output) {
# Function that generates a plot of the distribution. The function
# is wrapped in a call to reactivePlot to indicate that:
#
# 1) It is "reactive" and therefore should be automatically
# re-executed when inputs change
# 2) Its output type is a plot
#
output$distPlot <- reactivePlot(function() {
# generate an rnorm distribution and plot it
dist <- rnorm(input$obs)
hist(dist)
})
})

View File

@@ -0,0 +1,22 @@
library(shiny)
# Define UI for application that plots random distributions
shinyUI(pageWithSidebar(
# Application title
headerPanel("Hello Shiny!"),
# Sidebar with a slider input for number of observations
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 0,
max = 1000,
value = 500)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
))

View File

@@ -0,0 +1,25 @@
library(shiny)
library(datasets)
# Define server logic required to summarize and view the selected dataset
shinyServer(function(input, output) {
# Return the requested dataset
datasetInput <- reactive(function() {
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# Generate a summary of the dataset
output$summary <- reactivePrint(function() {
dataset <- datasetInput()
summary(dataset)
})
# Show the first "n" observations
output$view <- reactiveTable(function() {
head(datasetInput(), n = input$obs)
})
})

View File

@@ -0,0 +1,25 @@
library(shiny)
# Define UI for dataset viewer application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Shiny Text"),
# Sidebar with controls to select a dataset and specify the number
# of observations to view
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10)
),
# Show a summary of the dataset and an HTML table with the requested
# number of observations
mainPanel(
verbatimTextOutput("summary"),
tableOutput("view")
)
))

View File

@@ -0,0 +1,50 @@
library(shiny)
library(datasets)
# Define server logic required to summarize and view the selected dataset
shinyServer(function(input, output) {
# By declaring databaseInput as a reactive function we ensure that:
#
# 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)
# 3) When the inputs change and the function is re-executed, the
# new result is compared to the previous result; if the two are
# identical, then the callers are not notified
#
datasetInput <- reactive(function() {
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# The output$caption is computed based on a reactive function that
# returns input$caption. When the user changes the "caption" field:
#
# 1) This function is automatically called to recompute the output
# 2) The new caption is pushed back to the browser for re-display
#
# Note that because the data-oriented reactive functions below don't
# depend on input$caption, those functions are NOT called when
# input$caption changes.
output$caption <- reactiveText(function() {
input$caption
})
# The output$summary depends on the datasetInput reactive function,
# so will be re-executed whenever datasetInput is re-executed
# (i.e. whenever the input$dataset changes)
output$summary <- reactivePrint(function() {
dataset <- datasetInput()
summary(dataset)
})
# The output$view depends on both the databaseInput reactive function
# and input$obs, so will be re-executed whenever input$dataset or
# input$obs is changed.
output$view <- reactiveTable(function() {
head(datasetInput(), n = input$obs)
})
})

View File

@@ -0,0 +1,32 @@
library(shiny)
# Define UI for dataset viewer application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Reactivity"),
# Sidebar with controls to provide a caption, select a dataset, and
# specify the number of observations to view. Note that changes made
# to the caption in the textInput control are updated in the output
# area immediately as you type
sidebarPanel(
textInput("caption", "Caption:", "Data Summary"),
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10)
),
# Show the caption, a summary of the dataset and an HTML table with
# the requested number of observations
mainPanel(
h3(textOutput("caption")),
verbatimTextOutput("summary"),
tableOutput("view")
)
))

View File

@@ -0,0 +1,32 @@
library(shiny)
library(datasets)
# We tweak the "am" field to have nicer factor labels. Since this doesn't
# rely on any user inputs we can do this once at startup and then use the
# value throughout the lifetime of the application
mpgData <- mtcars
mpgData$am <- factor(mpgData$am, labels = c("Automatic", "Manual"))
# Define server logic required to plot various variables against mpg
shinyServer(function(input, output) {
# Compute the forumla text in a reactive function since it is
# shared by the output$caption and output$mpgPlot functions
formulaText <- reactive(function() {
paste("mpg ~", input$variable)
})
# Return the formula text for printing as a caption
output$caption <- reactiveText(function() {
formulaText()
})
# Generate a plot of the requested variable against mpg and only
# include outliers if requested
output$mpgPlot <- reactivePlot(function() {
boxplot(as.formula(formulaText()),
data = mpgData,
outline = input$outliers)
})
})

26
inst/examples/04_mpg/ui.R Normal file
View File

@@ -0,0 +1,26 @@
library(shiny)
# Define UI for miles per gallon application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Miles Per Gallon"),
# Sidebar with controls to select the variable to plot against mpg
# and to specify whether outliers should be included
sidebarPanel(
selectInput("variable", "Variable:",
list("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear")),
checkboxInput("outliers", "Show outliers", FALSE)
),
# Show the caption and plot of the requested variable against mpg
mainPanel(
h3(textOutput("caption")),
plotOutput("mpgPlot")
)
))

View File

@@ -0,0 +1,28 @@
library(shiny)
# Define server logic for slider examples
shinyServer(function(input, output) {
# Reactive function to compose a data frame containing all of the values
sliderValues <- reactive(function() {
# Compose data frame
data.frame(
Name = c("Integer",
"Decimal",
"Range",
"Custom Format",
"Animation"),
Value = as.character(c(input$integer,
input$decimal,
paste(input$range, collapse=' '),
input$format,
input$animation)),
stringsAsFactors=FALSE)
})
# Show the values using an HTML table
output$values <- reactiveTable(function() {
sliderValues()
})
})

View File

@@ -0,0 +1,37 @@
library(shiny)
# Define UI for slider demo application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Sliders"),
# Sidebar with sliders that demonstrate various available options
sidebarPanel(
# Simple integer interval
sliderInput("integer", "Integer:",
min=0, max=1000, value=500),
# Decimal interval with step value
sliderInput("decimal", "Decimal:",
min = 0, max = 1, value = 0.5, step= 0.1),
# Specification of range within an interval
sliderInput("range", "Range:",
min = 1, max = 1000, value = c(200,500)),
# Provide a custom currency format for value display, with basic animation
sliderInput("format", "Custom Format:",
min = 0, max = 10000, value = 0, step = 2500,
format="$#,##0", locale="us", animate=TRUE),
# Animation with custom interval (in ms) to control speed, plus looping
sliderInput("animation", "Looping Animation:", 1, 2000, 1, step = 10,
animate=animationOptions(interval=300, loop=T))
),
# Show a table summarizing the values entered
mainPanel(
tableOutput("values")
)
))

View File

@@ -0,0 +1,42 @@
library(shiny)
# Define server logic for random distribution application
shinyServer(function(input, output) {
# Reactive function to generate the requested distribution. This is
# called whenever the inputs change. The output functions defined
# below then all use the value computed from this function
data <- reactive(function() {
dist <- switch(input$dist,
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
dist(input$n)
})
# Generate a plot of the data. Also uses the inputs to build the
# plot label. Note that the dependencies on both the inputs and
# the data reactive function are both tracked, and all functions
# are called in the sequence implied by the dependency graph
output$plot <- reactivePlot(function() {
dist <- input$dist
n <- input$n
hist(data(),
main=paste('r', dist, '(', n, ')', sep=''))
})
# Generate a summary of the data
output$summary <- reactivePrint(function() {
summary(data())
})
# Generate an HTML table view of the data
output$table <- reactiveTable(function() {
data.frame(x=data())
})
})

View File

@@ -0,0 +1,36 @@
library(shiny)
# Define UI for random distribution application
shinyUI(pageWithSidebar(
# Application title
headerPanel("Tabsets"),
# Sidebar with controls to select the random distribution type
# and number of observations to generate. Note the use of the br()
# element to introduce extra vertical spacing
sidebarPanel(
radioButtons("dist", "Distribution type:",
list("Normal" = "norm",
"Uniform" = "unif",
"Log-normal" = "lnorm",
"Exponential" = "exp")),
br(),
sliderInput("n",
"Number of observations:",
value = 500,
min = 1,
max = 1000)
),
# Show a tabset that includes a plot, summary, and table view
# of the generated distribution
mainPanel(
tabsetPanel(
tabPanel("Plot", plotOutput("plot")),
tabPanel("Summary", verbatimTextOutput("summary")),
tabPanel("Table", tableOutput("table"))
)
)
))

View File

@@ -0,0 +1,25 @@
library(shiny)
library(datasets)
# Define server logic required to summarize and view the selected dataset
shinyServer(function(input, output) {
# Return the requested dataset
datasetInput <- reactive(function() {
switch(input$dataset,
"rock" = rock,
"pressure" = pressure,
"cars" = cars)
})
# Generate a summary of the dataset
output$summary <- reactivePrint(function() {
dataset <- datasetInput()
summary(dataset)
})
# Show the first "n" observations
output$view <- reactiveTable(function() {
head(datasetInput(), n = input$obs)
})
})

View File

@@ -0,0 +1,39 @@
library(shiny)
# Define UI for dataset viewer application
shinyUI(pageWithSidebar(
# Application title.
headerPanel("More Widgets"),
# Sidebar with controls to select a dataset and specify the number
# of observations to view. The helpText function is also used to
# include clarifying text. Most notably, the inclusion of a
# submitButton defers the rendering of output until the user
# explicitly clicks the button (rather than doing it immediately
# when inputs change). This is useful if the computations required
# to render output are inordinately time-consuming.
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Number of observations to view:", 10),
helpText("Note: while the data view will show only the specified",
"number of observations, the summary will still be based",
"on the full dataset."),
submitButton("Update View")
),
# Show a summary of the dataset and an HTML table with the requested
# number of observations. Note the use of the h4 function to provide
# an additional header above each output section.
mainPanel(
h4("Summary"),
verbatimTextOutput("summary"),
h4("Observations"),
tableOutput("view")
)
))

View File

@@ -0,0 +1,42 @@
library(shiny)
# Define server logic for random distribution application
shinyServer(function(input, output) {
# Reactive function to generate the requested distribution. This is
# called whenever the inputs change. The output functions defined
# below then all used the value computed from this function
data <- reactive(function() {
dist <- switch(input$dist,
norm = rnorm,
unif = runif,
lnorm = rlnorm,
exp = rexp,
rnorm)
dist(input$n)
})
# Generate a plot of the data. Also uses the inputs to build the
# plot label. Note that the dependencies on both the inputs and
# the data reactive function are both tracked, and all functions
# are called in the sequence implied by the dependency graph
output$plot <- reactivePlot(function() {
dist <- input$dist
n <- input$n
hist(data(),
main=paste('r', dist, '(', n, ')', sep=''))
})
# Generate a summary of the data
output$summary <- reactivePrint(function() {
summary(data())
})
# Generate an HTML table view of the data
output$table <- reactiveTable(function() {
data.frame(x=data())
})
})

View File

@@ -1,12 +1,15 @@
<html>
<head>
<script src="shared/jquery-1.7.2.js" type="text/javascript"></script>
<script src="shared/shiny.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
</head>
<body>
<h1>Example 3: Distributions</h1>
<head>
<script src="shared/jquery.js" type="text/javascript"></script>
<script src="shared/shiny.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="shared/shiny.css"/>
</head>
<body>
<h1>HTML UI</h1>
<p>
<label>Distribution type:</label><br />
<select name="dist">
@@ -14,17 +17,22 @@
<option value="unif">Uniform</option>
<option value="lnorm">Log-normal</option>
<option value="exp">Exponential</option>
</select>
</select>
</p>
<p>
<label>Number of observations:</label><br />
<input type="numeric" name="n" value="500" />
</p>
<label>Number of observations:</label><br />
<input type="number" name="n" value="500" min="1" max="1000" />
<div id="plot1" class="live-plot"></div>
</p>
<pre id="summary" class="shiny-text-output"></pre>
<div id="table1" class="live-html"></div>
<div id="plot" class="shiny-plot-output"
style="width: 100%; height: 400px"></div>
<div id="table" class="shiny-html-output"></div>
</body>
</html>
</html>

View File

@@ -0,0 +1,18 @@
library(shiny)
shinyServer(function(input, output) {
output$contents <- reactiveTable(function() {
# input$file1 will be NULL initially. After the user selects and uploads a
# file, it will be a data frame with 'name', 'size', 'type', and 'data'
# columns. The 'data' column will contain the local filenames where the data
# can be found.
inFile <- input$file1
if (is.null(inFile))
return(NULL)
read.csv(inFile$data, header=input$header, sep=input$sep, quote=input$quote)
})
})

View File

@@ -0,0 +1,24 @@
library(shiny)
shinyUI(pageWithSidebar(
headerPanel("CSV Viewer"),
sidebarPanel(
fileInput('file1', 'Choose CSV File',
accept=c('text/csv', 'text/comma-separated-values,text/plain')),
tags$hr(),
checkboxInput('header', 'Header', TRUE),
radioButtons('sep', 'Separator',
list(Comma=',',
Semicolon=';',
Tab='\t'),
'Comma'),
radioButtons('quote', 'Quote',
list(None='',
'Double Quote'='"',
'Single Quote'="'"),
'Double Quote')
),
mainPanel(
tableOutput('contents')
)
))

12
inst/www/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>No UI defined</h1>
<p>Shiny couldn't find any UI for this application. We looked in:</p>
<ul>
<li><code>www/index.html</code></li>
<li><code>ui.R</code></li>
</ul>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

2027
inst/www/shared/bootstrap/js/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

56
inst/www/shared/shiny.css Normal file
View File

@@ -0,0 +1,56 @@
body.disconnected {
background-color: #999;
opacity: 0.5;
}
table.data {
width: auto;
}
table.data td[align=right] {
font-family: monospace;
text-align: right;
}
.shiny-output-error {
color: red;
}
.jslider {
/* Fix jslider running into the control above it */
margin-top: 18px;
}
.jslider-value {
/* Remove box around jslider values on colored bg */
background-color: transparent !important;
}
.recalculating {
opacity: 0.3;
transition: opacity 250ms ease 500ms;
-moz-transition: opacity 250ms ease 500ms;
-webkit-transition: opacity 250ms ease 500ms;
-o-transition: opacity 250ms ease 500ms;
}
span.jslider {
margin-bottom: 12px;
}
.slider-animate-container {
text-align: right;
margin-top: -9px;
}
.slider-animate-button {
opacity: 0.5;
}
.slider-animate-button .pause {
display: none;
}
.slider-animate-button.playing .pause {
display: inline;
}
.slider-animate-button .play {
display: inline;
}
.slider-animate-button.playing .play {
display: none;
}

1542
inst/www/shared/shiny.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
.jslider .jslider-bg i,.jslider .jslider-pointer{background:url(../img/jslider.png) no-repeat 0 0}.jslider{display:block;width:100%;height:1em;position:relative;top:.6em;font-family:Arial,sans-serif}.jslider table{width:100%;border-collapse:collapse;border:0}.jslider td,.jslider th{padding:0;vertical-align:top;text-align:left;border:0}.jslider table,.jslider table tr,.jslider table tr td{width:100%;vertical-align:top}.jslider .jslider-bg{position:relative}.jslider .jslider-bg i{height:5px;position:absolute;font-size:0;top:0}.jslider .jslider-bg .l{width:50%;background-position:0 0;left:0}.jslider .jslider-bg .r{width:50%;left:50%;background-position:right 0}.jslider .jslider-bg .v{position:absolute;width:60%;left:20%;top:0;height:5px;background-position:0 -20px}.jslider .jslider-pointer{width:13px;height:15px;background-position:0 -40px;position:absolute;left:20%;top:-4px;margin-left:-6px;cursor:pointer;cursor:hand}.jslider .jslider-pointer-hover{background-position:-20px -40px}.jslider .jslider-pointer-to{left:80%}.jslider .jslider-label{font-size:9px;line-height:12px;color:black;opacity:.4;white-space:nowrap;padding:0 2px;position:absolute;top:-18px;left:0}.jslider .jslider-label-to{left:auto;right:0}.jslider .jslider-value{font-size:9px;white-space:nowrap;padding:1px 2px 0;position:absolute;top:-19px;left:20%;background:white;line-height:12px;-moz-border-radius:2px;-webkit-border-radius:2px;-o-border-radius:2px;border-radius:2px}.jslider .jslider-value-to{left:80%}.jslider .jslider-label small,.jslider .jslider-value small{position:relative;top:-0.4em}.jslider .jslider-scale{position:relative;top:9px}.jslider .jslider-scale span{position:absolute;height:5px;border-left:1px solid #999;font-size:0}.jslider .jslider-scale ins{font-size:9px;text-decoration:none;position:absolute;left:0;top:5px;color:#999}.jslider-single .jslider-pointer-to,.jslider-single .jslider-value-to,.jslider-single .jslider-bg .v,.jslider-limitless .jslider-label{display:none}.jslider_blue .jslider-bg i,.jslider_blue .jslider-pointer{background-image:url(../img/jslider.blue.png)}.jslider_plastic .jslider-bg i,.jslider_plastic .jslider-pointer{background-image:url(../img/jslider.plastic.png)}.jslider_round .jslider-bg i,.jslider_round .jslider-pointer{background-image:url(../img/jslider.round.png)}.jslider_round .jslider-pointer{width:17px;height:17px;top:-6px;margin-left:-8px}.jslider_round_plastic .jslider-bg i,.jslider_round_plastic .jslider-pointer{background-image:url(../img/jslider.round.plastic.png)}.jslider_round_plastic .jslider-pointer{width:18px;height:18px;top:-7px;margin-left:-8px}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long

View File

@@ -1,172 +0,0 @@
module React
class Context
private
@@next_id = 0
@@current_context = nil
@@pending_invalidate = []
public
def self.current!
return current || raise("No current context")
end
def self.current
@@current_context
end
attr_reader :id
def initialize
# The ID can used to identify/sort/dedupe contexts
@id = @@next_id += 1
# Indicates whether this context is invalidated, i.e. its
# callbacks have been called or are about to be called
@invalidated = false
# List of callbacks to be called after invalidation
@callbacks = []
end
# Run a block with this context as the current context. The
# original current context will be restored after the block
# is executed.
def run
old_ctx = @@current_context
@@current_context = self
begin
return yield
ensure
@@current_context = old_ctx
end
end
def invalidate
return if @invalidated
@invalidated = true
@@pending_invalidate << self
end
# Register a callback to be called after this context is
# invalidated (or immediately if it's already invalidated).
# The callback takes one argument, the context.
def on_invalidate(&callback)
if @invalidated
callback.call(self)
else
@callbacks << callback
end
end
def execute_callbacks
@callbacks.each {|callback| callback.call(self)}
end
# Execute all callbacks on invalidated contexts. Will do this
# repeatedly if the callbacks themselves cause more invalidations.
def self.flush
while !@@pending_invalidate.empty?
contexts = @@pending_invalidate
@@pending_invalidate = []
contexts.each {|context| context.execute_callbacks}
end
end
end
class Session
def initialize
# Key is variable name, value is variable value
@values = Hash.new
# Key is variable name, value is { Context IDs => Contexts }
@dependencies = Hash.new
end
def get(name)
cur_ctx = React::Context.current!
@dependencies[name] = @dependencies[name] || Hash.new
if !@dependencies[name].has_key?(cur_ctx.id)
@dependencies[name][cur_ctx.id] = cur_ctx
cur_ctx.on_invalidate do
@dependencies[name].delete(cur_ctx.id)
end
end
return @values[name]
end
def set(name, value)
if @values.has_key?(name) && @values[name] == value
return
end
@values[name] = value
if @dependencies[name]
@dependencies[name].each_value {|ctx| ctx.invalidate}
end
end
end
# Stores (and caches) a single dependent value in a context
class ObservableValue
def initialize(&valueProc)
@valueProc = valueProc
@dependencies = Hash.new
@initialized = false
end
def value
if !@initialized
@initialized = true
update_value
end
cur_ctx = React::Context.current!
@dependencies[cur_ctx.id] = cur_ctx
cur_ctx.on_invalidate do
@dependencies.delete cur_ctx.id
end
@value
end
private
def update_value
old_value = @value
ctx = Context.new
ctx.on_invalidate do
update_value
end
ctx.run do
@value = @valueProc.call
end
if old_value != @value
@dependencies.each_value {|dep_ctx| dep_ctx.invalidate}
end
end
end
# Runs the given proc whenever its dependencies change
class Observer
def initialize(&proc)
@proc = proc
run
end
def run
ctx = React::Context.new
ctx.on_invalidate do
run
end
ctx.run &@proc
end
end
end

View File

@@ -1,156 +0,0 @@
require 'eventmachine'
require 'evma_httpserver'
require 'em-websocket'
require 'pathname'
require 'json'
require 'react'
class WebServer < EM::Connection
include EM::HttpServer
def post_init
super
no_environment_strings
@basepath = File.join(Dir.pwd, 'www')
end
def resolve_path(path)
# It's not a valid path if it doesn't start with /
return nil if path !~ /^\//
abspath = File.join(@basepath, "./#{path}")
# Resolves '..', etc.
abspath = Pathname.new(abspath).cleanpath.to_s
return false if abspath[0...(@basepath.size + 1)] != @basepath + '/'
return false if !File.exist?(abspath)
return abspath
end
def process_http_request
# the http request details are available via the following instance variables:
# @http_protocol
# @http_request_method
# @http_cookie
# @http_if_none_match
# @http_content_type
# @http_path_info
# @http_request_uri
# @http_query_string
# @http_post_content
# @http_headers
response = EM::DelegatedHttpResponse.new(self)
path = @http_path_info
path = '/index.html' if path == '/'
resolved_path = resolve_path(path)
if !resolved_path
response.status = 404
response.content_type 'text/html'
response.content = '<h1>404 Not Found</h1>'
else
response.status = 200
response.content_type case resolved_path
when /\.html?$/
'text/html'
when /\.js$/
'text/javascript'
when /\.css$/
'text/css'
when /\.png$/
'image/png'
when /\.jpg$/
'image/jpeg'
when /\.gif$/
'image/gif'
end
response.content = File.read(resolved_path)
end
response.send_response
end
end
def run_shiny_app(shinyapp)
EventMachine.run do
EventMachine.start_server '0.0.0.0', 8100, WebServer
puts "Listening on port 8100"
EventMachine::WebSocket.start(:host => '0.0.0.0', :port => 8101) do |ws|
shinyapp.websocket = ws
ws.onclose { exit(0) }
ws.onmessage do |msg|
begin
puts "RECV: #{msg}"
msg_obj = JSON.parse(msg)
case msg_obj['method']
when 'init'
msg_obj['data'].each do |k, v|
shinyapp.session.set(k, v)
end
React::Context.flush
shinyapp.instantiate_outputs
when 'update'
msg_obj['data'].each do |k, v|
shinyapp.session.set(k, v)
end
end
React::Context.flush
shinyapp.flush_output
rescue Exception => e
puts "ERROR: #{e}"
puts e.backtrace.collect {|x| "\t#{x}"}
raise
end
end
end
end
end
class ShinyApp
attr_reader :session
def initialize
@session = React::Session.new
@outputs = {}
@invalidated_output_values = Hash.new
end
def websocket=(value)
@websocket = value
end
def define_output(name, &proc)
@outputs[name] = proc
end
def instantiate_outputs
@outputs.keys.each do |name|
proc = @outputs.delete(name)
React::Observer.new do
value = proc.call
@invalidated_output_values[name] = value
end
end
end
def flush_output
return if @invalidated_output_values.empty?
data = @invalidated_output_values
@invalidated_output_values = Hash.new
puts "SEND: #{JSON.generate(data)}"
@websocket.send(JSON.generate(data))
end
def run
run_shiny_app self
end
end

24
man/HTML.Rd Normal file
View File

@@ -0,0 +1,24 @@
\name{HTML}
\alias{HTML}
\title{Mark Characters as HTML}
\usage{
HTML(text, ...)
}
\arguments{
\item{text}{The text value to mark with HTML}
\item{...}{Any additional values to be converted to
character and concatenated together}
}
\value{
The same value, but marked as HTML.
}
\description{
Marks the given text as HTML, which means the \link{tag}
functions will know not to perform HTML escaping on it.
}
\examples{
el <- div(HTML("I like <u>turtles</u>"))
cat(as.character(el))
}

36
man/addResourcePath.Rd Normal file
View File

@@ -0,0 +1,36 @@
\name{addResourcePath}
\alias{addResourcePath}
\title{Resource Publishing}
\usage{
addResourcePath(prefix, directoryPath)
}
\arguments{
\item{prefix}{The URL prefix (without slashes). Valid
characters are a-z, A-Z, 0-9, hyphen, and underscore; and
must begin with a-z or A-Z. For example, a value of 'foo'
means that any request paths that begin with '/foo' will
be mapped to the given directory.}
\item{directoryPath}{The directory that contains the
static resources to be served.}
}
\description{
Adds a directory of static resources to Shiny's web
server, with the given path prefix. Primarily intended
for package authors to make supporting JavaScript/CSS
files available to their components.
}
\details{
You can call \code{addResourcePath} multiple times for a
given \code{prefix}; only the most recent value will be
retained. If the normalized \code{directoryPath} is
different than the directory that's currently mapped to
the \code{prefix}, a warning will be issued.
}
\examples{
addResourcePath('datasets', system.file('data', package='datasets'))
}
\seealso{
\code{\link{singleton}}
}

28
man/animationOptions.Rd Normal file
View File

@@ -0,0 +1,28 @@
\name{animationOptions}
\alias{animationOptions}
\title{Animation Options}
\usage{
animationOptions(interval = 1000, loop = FALSE,
playButton = NULL, pauseButton = NULL)
}
\arguments{
\item{interval}{The interval, in milliseconds, between
each animation step.}
\item{loop}{\code{TRUE} to automatically restart the
animation when it reaches the end.}
\item{playButton}{Specifies the appearance of the play
button. Valid values are a one-element character vector
(for a simple text label), an HTML tag or list of tags
(using \code{\link{tag}} and friends), or raw HTML (using
\code{\link{HTML}}).}
\item{pauseButton}{Similar to \code{playButton}, but for
the pause button.}
}
\description{
Creates an options object for customizing animations for
\link{sliderInput}.
}

26
man/bootstrapPage.Rd Normal file
View File

@@ -0,0 +1,26 @@
\name{bootstrapPage}
\alias{bootstrapPage}
\title{Create a Twitter Bootstrap page}
\usage{
bootstrapPage(...)
}
\arguments{
\item{...}{The contents of the document body.}
}
\value{
A UI defintion that can be passed to the \link{shinyUI}
function.
}
\description{
Create a Shiny UI page that loads the CSS and JavaScript
for \href{http://getbootstrap.com}{Twitter Bootstrap},
and has no content in the page body (other than what you
provide).
}
\details{
This function is primarily intended for users who are
proficient in HTML/CSS, and know how to lay out pages in
Bootstrap. Most users should use template functions like
\code{\link{pageWithSidebar}}.
}

79
man/builder.Rd Normal file
View File

@@ -0,0 +1,79 @@
\name{builder}
\alias{p}
\alias{h1}
\alias{h2}
\alias{h3}
\alias{h4}
\alias{h5}
\alias{h6}
\alias{a}
\alias{br}
\alias{div}
\alias{span}
\alias{pre}
\alias{code}
\alias{img}
\alias{strong}
\alias{em}
\alias{tags}
\usage{
p(...)
h1(...)
h2(...)
h3(...)
h4(...)
h5(...)
h6(...)
a(...)
br(...)
div(...)
span(...)
pre(...)
code(...)
img(...)
strong(...)
em(...)
tags
}
\title{HTML Builder Functions}
\arguments{
\item{...}{Attributes and children of the element. Named arguments
become attributes, and positional arguments become children. Valid
children are tags, single-character character vectors (which become
text nodes), and raw HTML (see \code{\link{HTML}}). You can also
pass lists that contain tags, text nodes, and HTML.}
}
\description{
Simple functions for constructing HTML documents.
}
\details{
The \code{tags} environment contains convenience functions for all
valid HTML5 tags. To generate tags that are not part of the HTML5
specification, you can use the \link{tag} function.
Dedicated functions are available for the most common HTML tags
that do not conflict with common R functions.
The result from these functions is a tag object, which can be
converted using \code{as.character}.
}
\examples{
doc <- tags$html(
tags$head(
tags$title('My first page')
),
tags$body(
h1('My first heading'),
p('My first paragraph, with some ',
strong('bold'),
' text.'),
div(id='myDiv', class='simpleDiv',
'Here is a div with some attributes.')
)
)
cat(as.character(doc))
}

39
man/checkboxGroupInput.Rd Normal file
View File

@@ -0,0 +1,39 @@
\name{checkboxGroupInput}
\alias{checkboxGroupInput}
\title{Checkbox Group Input Control}
\usage{
checkboxGroupInput(inputId, label, choices,
selected = NULL)
}
\arguments{
\item{inputId}{Input variable to assign the control's
value to.}
\item{label}{Display label for the control.}
\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.}
\item{selected}{Names of items that should be initially
selected, if any.}
}
\value{
A list of HTML elements that can be added to a UI
definition.
}
\description{
Create a group of checkboxes that can be used to toggle
multiple choices independently. The server will receive
the input as a character vector of the selected values.
}
\examples{
checkboxGroupInput("variable", "Variable:",
list("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear"))
}
\seealso{
\code{\link{checkboxInput}}
}

29
man/checkboxInput.Rd Normal file
View File

@@ -0,0 +1,29 @@
\name{checkboxInput}
\alias{checkboxInput}
\title{Checkbox Input Control}
\usage{
checkboxInput(inputId, label, value = FALSE)
}
\arguments{
\item{inputId}{Input variable to assign the control's
value to.}
\item{label}{Display label for the control.}
\item{value}{Initial value (\code{TRUE} or
\code{FALSE}).}
}
\value{
A checkbox control that can be added to a UI definition.
}
\description{
Create a checkbox that can be used to specify logical
values.
}
\examples{
checkboxInput("outliers", "Show outliers", FALSE)
}
\seealso{
\code{\link{checkboxGroupInput}}
}

54
man/conditionalPanel.Rd Normal file
View File

@@ -0,0 +1,54 @@
\name{conditionalPanel}
\alias{conditionalPanel}
\title{Conditional Panel}
\usage{
conditionalPanel(condition, ...)
}
\arguments{
\item{condition}{A JavaScript expression that will be
evaluated repeatedly to determine whether the panel
should be displayed.}
\item{...}{Elements to include in the panel.}
}
\description{
Creates a panel that is visible or not, depending on the
value of a JavaScript expression. The JS expression is
evaluated once at startup and whenever Shiny detects a
relevant change in input/output.
}
\details{
In the JS expression, you can refer to \code{input} and
\code{output} JavaScript objects that contain the current
values of input and output. For example, if you have an
input with an id of \code{foo}, then you can use
\code{input.foo} to read its value. (Be sure not to
modify the input/output objects, as this may cause
unpredictable behavior.)
}
\examples{
sidebarPanel(
selectInput(
"plotType", "Plot Type",
list(Scatter = "scatter",
Histogram = "hist")),
# Only show this panel if the plot type is a histogram
conditionalPanel(
condition = "input.plotType == 'hist'",
selectInput(
"breaks", "Breaks",
list("Sturges",
"Scott",
"Freedman-Diaconis",
"[Custom]" = "custom")),
# Only show this panel if Custom is selected
conditionalPanel(
condition = "input.breaks == 'custom'",
sliderInput("breakCount", "Break Count", min=1, max=1000, value=10)
)
)
)
}

27
man/fileInput.Rd Normal file
View File

@@ -0,0 +1,27 @@
\name{fileInput}
\alias{fileInput}
\title{File Upload Control}
\usage{
fileInput(inputId, label, multiple = FALSE,
accept = NULL)
}
\arguments{
\item{inputId}{Input variable to assign the control's
value to.}
\item{label}{Display label for the control.}
\item{multiple}{Whether the user should be allowed to
select and upload multiple files at once.}
\item{accept}{A character vector of MIME types; gives the
browser a hint of what kind of files the server is
expecting.}
}
\description{
Create a file upload control that can be used to upload
one or more files. \bold{Experimental feature. Only works
in some browsers (primarily tested on Chrome and
Firefox).}
}

20
man/headerPanel.Rd Normal file
View File

@@ -0,0 +1,20 @@
\name{headerPanel}
\alias{headerPanel}
\title{Create a header panel}
\usage{
headerPanel(title)
}
\arguments{
\item{title}{An application title to display}
}
\value{
A headerPanel that can be passed to
\link{pageWithSidebar}
}
\description{
Create a header panel containing an application title.
}
\examples{
headerPanel("Hello Shiny!")
}

24
man/helpText.Rd Normal file
View File

@@ -0,0 +1,24 @@
\name{helpText}
\alias{helpText}
\title{Create a help text element}
\usage{
helpText(text, ...)
}
\arguments{
\item{text}{Help text string}
\item{...}{Additional help text strings}
}
\value{
A help text element that can be added to a UI definition.
}
\description{
Create help text which can be added to an input form to
provide additional explanation or context.
}
\examples{
helpText("Note: while the data view will show only",
"the specified number of observations, the",
"summary will be based on the full dataset.")
}

30
man/htmlOutput.Rd Normal file
View File

@@ -0,0 +1,30 @@
\name{htmlOutput}
\alias{htmlOutput}
\alias{uiOutput}
\title{Create an HTML output element}
\usage{
htmlOutput(outputId)
uiOutput(outputId)
}
\arguments{
\item{outputId}{output variable to read the value from}
}
\value{
An HTML output element that can be included in a panel
}
\description{
Render a reactive output variable as HTML within an
application page. The text will be included within an
HTML \code{div} tag, and is presumed to contain HTML
content which should not be escaped.
}
\details{
\code{uiOutput} is intended to be used with
\code{reactiveUI} on the server side. It is currently
just an alias for \code{htmlOutput}.
}
\examples{
htmlOutput("summary")
}

15
man/invalidateLater.Rd Normal file
View File

@@ -0,0 +1,15 @@
\name{invalidateLater}
\alias{invalidateLater}
\title{Scheduled Invalidation}
\usage{
invalidateLater(millis)
}
\arguments{
\item{millis}{Approximate milliseconds to wait before
invalidating the current reactive context.}
}
\description{
Schedules the current reactive context to be invalidated
in the given number of milliseconds.
}

24
man/mainPanel.Rd Normal file
View File

@@ -0,0 +1,24 @@
\name{mainPanel}
\alias{mainPanel}
\title{Create a main panel}
\usage{
mainPanel(...)
}
\arguments{
\item{...}{Ouput elements to include in the main panel}
}
\value{
A main panel that can be passed to \link{pageWithSidebar}
}
\description{
Create a main panel containing output elements that can
in turn be passed to \link{pageWithSidebar}.
}
\examples{
# Show the caption and plot of the requested variable against mpg
mainPanel(
h3(textOutput("caption")),
plotOutput("mpgPlot")
)
}

34
man/numericInput.Rd Normal file
View File

@@ -0,0 +1,34 @@
\name{numericInput}
\alias{numericInput}
\title{Create a numeric input control}
\usage{
numericInput(inputId, label, value, min = NA, max = NA,
step = NA)
}
\arguments{
\item{inputId}{Input variable to assign the control's
value to}
\item{label}{Display label for the control}
\item{value}{Initial value}
\item{min}{Minimum allowed value}
\item{max}{Maximum allowed value}
\item{step}{Interval to use when stepping between min and
max}
}
\value{
A numeric input control that can be added to a UI
definition.
}
\description{
Create an input control for entry of numeric values
}
\examples{
numericInput("obs", "Observations:", 10,
min = 1, max = 100)
}

47
man/pageWithSidebar.Rd Normal file
View File

@@ -0,0 +1,47 @@
\name{pageWithSidebar}
\alias{pageWithSidebar}
\title{Create a page with a sidebar}
\usage{
pageWithSidebar(headerPanel, sidebarPanel, mainPanel)
}
\arguments{
\item{headerPanel}{The \link{headerPanel} with the
application title}
\item{sidebarPanel}{The \link{sidebarPanel} containing
input controls}
\item{mainPanel}{The \link{mainPanel} containing outputs}
}
\value{
A UI defintion that can be passed to the \link{shinyUI}
function
}
\description{
Create a Shiny UI that contains a header with the
application title, a sidebar for input controls, and a
main area for output.
}
\examples{
# Define UI
shinyUI(pageWithSidebar(
# Application title
headerPanel("Hello Shiny!"),
# Sidebar with a slider input
sidebarPanel(
sliderInput("obs",
"Number of observations:",
min = 0,
max = 1000,
value = 500)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
))
}

26
man/plotOutput.Rd Normal file
View File

@@ -0,0 +1,26 @@
\name{plotOutput}
\alias{plotOutput}
\title{Create a plot output element}
\usage{
plotOutput(outputId, width = "100\%", height = "400px")
}
\arguments{
\item{outputId}{output variable to read the plot from}
\item{width}{Plot width}
\item{height}{Plot height}
}
\value{
A plot output element that can be included in a panel
}
\description{
Render a \link{reactivePlot} within an application page.
}
\examples{
# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
}

35
man/radioButtons.Rd Normal file
View File

@@ -0,0 +1,35 @@
\name{radioButtons}
\alias{radioButtons}
\title{Create radio buttons}
\usage{
radioButtons(inputId, label, choices, selected = NULL)
}
\arguments{
\item{inputId}{Input variable to assign the control's
value to}
\item{label}{Display label for the control}
\item{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)}
\item{selected}{Name of initially selected item (if not
specified then defaults to the first item)}
}
\value{
A set of radio buttons that can be added to a UI
definition.
}
\description{
Create a set of radio buttons used to select an item from
a list.
}
\examples{
radioButtons("dist", "Distribution type:",
list("Normal" = "norm",
"Uniform" = "unif",
"Log-normal" = "lnorm",
"Exponential" = "exp"))
}

35
man/reactive.Rd Normal file
View File

@@ -0,0 +1,35 @@
\name{reactive}
\alias{reactive}
\title{Create a Reactive Function}
\usage{
reactive(x)
}
\arguments{
\item{x}{The value or function to make reactive. The
function must not have any parameters.}
}
\value{
A reactive function. (Note that reactive functions can
only be called from within other reactive functions.)
}
\description{
Wraps a normal function to create a reactive function.
Conceptually, a reactive function is a function whose
result will change over time.
}
\details{
Reactive functions are functions that can read reactive
values and call other reactive functions. Whenever a
reactive value changes, any reactive functions that
depended on it are marked as "invalidated" and will
automatically re-execute if necessary. If a reactive
function is marked as invalidated, any other reactive
functions that recently called it are also marked as
invalidated. In this way, invalidations ripple through
the functions that depend on each other.
See the
\href{http://rstudio.github.com/shiny/tutorial/}{Shiny
tutorial} for more information about reactive functions.
}

31
man/reactivePlot.Rd Normal file
View File

@@ -0,0 +1,31 @@
\name{reactivePlot}
\alias{reactivePlot}
\title{Plot Output}
\usage{
reactivePlot(func, width = "auto", height = "auto", ...)
}
\arguments{
\item{func}{A function that generates a plot.}
\item{width}{The width of the rendered plot, in pixels;
or \code{'auto'} to use the \code{offsetWidth} of the
HTML element that is bound to this plot.}
\item{height}{The height of the rendered plot, in pixels;
or \code{'auto'} to use the \code{offsetHeight} of the
HTML element that is bound to this plot.}
\item{...}{Arguments to be passed through to
\code{\link[grDevices]{png}}. These can be used to set
the width, height, background color, etc.}
}
\description{
Creates a reactive plot that is suitable for assigning to
an \code{output} slot.
}
\details{
The corresponding HTML output tag should be \code{div} or
\code{img} and have the CSS class name
\code{shiny-plot-output}.
}

26
man/reactivePrint.Rd Normal file
View File

@@ -0,0 +1,26 @@
\name{reactivePrint}
\alias{reactivePrint}
\title{Printable Output}
\usage{
reactivePrint(func)
}
\arguments{
\item{func}{A function that returns a printable R
object.}
}
\description{
Makes a reactive version of the given function that also
turns its printable result into a string. The reactive
function is suitable for assigning to an \code{output}
slot.
}
\details{
The corresponding HTML output tag can be anything (though
\code{pre} is recommended if you need a monospace font
and whitespace preserved) and should have the CSS class
name \code{shiny-text-output}.
The result of executing \code{func} will be printed
inside a \code{\link[utils]{capture.output}} call.
}

22
man/reactiveTable.Rd Normal file
View File

@@ -0,0 +1,22 @@
\name{reactiveTable}
\alias{reactiveTable}
\title{Table Output}
\usage{
reactiveTable(func, ...)
}
\arguments{
\item{func}{A function that returns an R object that can
be used with \code{\link[xtable]{xtable}}.}
\item{...}{Arguments to be passed through to
\code{\link[xtable]{xtable}}.}
}
\description{
Creates a reactive table that is suitable for assigning
to an \code{output} slot.
}
\details{
The corresponding HTML output tag should be \code{div}
and have the CSS class name \code{shiny-html-output}.
}

26
man/reactiveText.Rd Normal file
View File

@@ -0,0 +1,26 @@
\name{reactiveText}
\alias{reactiveText}
\title{Text Output}
\usage{
reactiveText(func)
}
\arguments{
\item{func}{A function that returns an R object that can
be used as an argument to \code{cat}.}
}
\description{
Makes a reactive version of the given function that also
uses \code{\link[base]{cat}} to turn its result into a
single-element character vector.
}
\details{
The corresponding HTML output tag can be anything (though
\code{pre} is recommended if you need a monospace font
and whitespace preserved) and should have the CSS class
name \code{shiny-text-output}.
The result of executing \code{func} will passed to
\code{cat}, inside a \code{\link[utils]{capture.output}}
call.
}

35
man/reactiveTimer.Rd Normal file
View File

@@ -0,0 +1,35 @@
\name{reactiveTimer}
\alias{reactiveTimer}
\title{Timer}
\usage{
reactiveTimer(intervalMs = 1000)
}
\arguments{
\item{intervalMs}{How often to fire, in milliseconds}
}
\value{
A no-parameter function that can be called from a
reactive context, in order to cause that context to be
invalidated the next time the timer interval elapses.
Calling the returned function also happens to yield the
current time (as in \code{\link{Sys.time}}).
}
\description{
Creates a reactive timer with the given interval. A
reactive timer is like a reactive value, except reactive
values are triggered when they are set, while reactive
timers are triggered simply by the passage of time.
}
\details{
\link[=reactive]{Reactive functions} and observers that
want to be invalidated by the timer need to call the
timer function that \code{reactiveTimer} returns, even if
the current time value is not actually needed.
See \code{\link{invalidateLater}} as a safer and simpler
alternative.
}
\seealso{
invalidateLater
}

33
man/reactiveUI.Rd Normal file
View File

@@ -0,0 +1,33 @@
\name{reactiveUI}
\alias{reactiveUI}
\title{UI Output}
\usage{
reactiveUI(func)
}
\arguments{
\item{func}{A function that returns a Shiny tag object,
\code{\link{HTML}}, or a list of such objects.}
}
\description{
\bold{Experimental feature.} Makes a reactive version of
a function that generates HTML using the Shiny UI
library.
}
\details{
The corresponding HTML output tag should be \code{div}
and have the CSS class name \code{shiny-html-output} (or
use \code{\link{uiOutput}}).
}
\examples{
\dontrun{
output$moreControls <- reactiveUI(function() {
list(
)
})
}
}
\seealso{
conditionalPanel
}

38
man/repeatable.Rd Normal file
View File

@@ -0,0 +1,38 @@
\name{repeatable}
\alias{repeatable}
\title{Make a random number generator repeatable}
\usage{
repeatable(rngfunc,
seed = runif(1, 0, .Machine$integer.max))
}
\arguments{
\item{rngfunc}{The function that is affected by the R
session's seed.}
\item{seed}{The seed to set every time the resulting
function is called.}
}
\value{
A repeatable version of the function that was passed in.
}
\description{
Given a function that generates random data, returns a
wrapped version of that function that always uses the
same seed when called. The seed to use can be passed in
explicitly if desired; otherwise, a random number is
used.
}
\note{
When called, the returned function attempts to preserve
the R session's current seed by snapshotting and
restoring \code{\link[base]{.Random.seed}}.
}
\examples{
rnormA <- repeatable(rnorm)
rnormB <- repeatable(rnorm)
rnormA(3) # [1] 1.8285879 -0.7468041 -0.4639111
rnormA(3) # [1] 1.8285879 -0.7468041 -0.4639111
rnormA(5) # [1] 1.8285879 -0.7468041 -0.4639111 -1.6510126 -1.4686924
rnormB(5) # [1] -0.7946034 0.2568374 -0.6567597 1.2451387 -0.8375699
}

26
man/runApp.Rd Normal file
View File

@@ -0,0 +1,26 @@
\name{runApp}
\alias{runApp}
\title{Run Shiny Application}
\usage{
runApp(appDir = getwd(), port = 8100L,
launch.browser = getOption("shiny.launch.browser", interactive()))
}
\arguments{
\item{appDir}{The directory of the application. Should
contain \code{server.R}, plus, either \code{ui.R} or a
\code{www} directory that contains the file
\code{index.html}. Defaults to the working directory.}
\item{port}{The TCP port that the application should
listen on. Defaults to port 8100.}
\item{launch.browser}{If true, the system's default web
browser will be launched automatically after the app is
started. Defaults to true in interactive sessions only.}
}
\description{
Runs a Shiny application. This function normally does not
return; interrupt R to stop the application (usually by
pressing Ctrl+C or Esc).
}

23
man/runExample.Rd Normal file
View File

@@ -0,0 +1,23 @@
\name{runExample}
\alias{runExample}
\title{Run Shiny Example Applications}
\usage{
runExample(example = NA, port = 8100L,
launch.browser = getOption("shiny.launch.browser", interactive()))
}
\arguments{
\item{example}{The name of the example to run, or
\code{NA} (the default) to list the available examples.}
\item{port}{The TCP port that the application should
listen on. Defaults to port 8100.}
\item{launch.browser}{If true, the system's default web
browser will be launched automatically after the app is
started. Defaults to true in interactive sessions only.}
}
\description{
Launch Shiny example applications, and optionally, your
system's web browser.
}

26
man/runGist.Rd Normal file
View File

@@ -0,0 +1,26 @@
\name{runGist}
\alias{runGist}
\title{Run a Shiny application from https://gist.github.com}
\usage{
runGist(gist, port = 8100L,
launch.browser = getOption("shiny.launch.browser", interactive()))
}
\arguments{
\item{gist}{The identifier of the gist. For example, if
the gist is https://gist.github.com/3239667, then
\code{3239667}, \code{'3239667'}, and
\code{'https://gist.github.com/3239667'} are all valid
values.}
\item{port}{The TCP port that the application should
listen on. Defaults to port 8100.}
\item{launch.browser}{If true, the system's default web
browser will be launched automatically after the app is
started. Defaults to true in interactive sessions only.}
}
\description{
Download and launch a Shiny application that is hosted on
GitHub as a gist.
}

40
man/selectInput.Rd Normal file
View File

@@ -0,0 +1,40 @@
\name{selectInput}
\alias{selectInput}
\title{Create a select list input control}
\usage{
selectInput(inputId, label, choices, selected = NULL,
multiple = FALSE)
}
\arguments{
\item{inputId}{Input variable to assign the control's
value to}
\item{label}{Display label for the control}
\item{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.}
\item{selected}{Name of initially selected item (or
multiple names if \code{multiple = TRUE}). If not
specified then defaults to the first item for
single-select lists and no items for multiple select
lists.}
\item{multiple}{Is selection of multiple items allowed?}
}
\value{
A select list control that can be added to a UI
definition.
}
\description{
Create a select list that can be used to choose a single
or multiple items from a list of values.
}
\examples{
selectInput("variable", "Variable:",
list("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear"))
}

41
man/shiny-package.Rd Normal file
View File

@@ -0,0 +1,41 @@
\name{shiny-package}
\alias{shiny-package}
\alias{shiny}
\docType{package}
\title{
Web Application Framework for R
}
\description{
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 powerful applications with minimal effort.
The Shiny tutorial at \url{http://rstudio.github.com/shiny/tutorial}
explains the framework in-depth, walks you through
building a simple application, and includes extensive annotated
examples.
}
\details{
\tabular{ll}{
Package: \tab shiny\cr
Type: \tab Package\cr
Version: \tab 0.1.0\cr
Date: \tab 2012-07-28\cr
License: \tab GPL-3\cr
Depends: \tab R (>= 2.14.1), methods, websockets (>= 1.1.4), caTools, RJSONIO, xtable\cr
Imports: \tab stats, tools, utils, datasets\cr
URL: \tab https://github.com/rstudio/shiny, http://rstudio.github.com/shiny/tutorial\cr
BugReports: \tab https://github.com/rstudio/shiny/issues\cr
}
}
\author{
RStudio, Inc.
Maintainer: Joe Cheng <joe@rstudio.org>
}
\keyword{ package }

41
man/shinyServer.Rd Normal file
View File

@@ -0,0 +1,41 @@
\name{shinyServer}
\alias{shinyServer}
\title{Define Server Functionality}
\usage{
shinyServer(func)
}
\arguments{
\item{func}{The server function for this application. See
the details section for more information.}
}
\description{
Defines the server-side logic of the Shiny application.
This generally involves creating functions that map user
inputs to various kinds of output.
}
\details{
Call \code{shinyServer} from your application's
\code{server.R} file, passing in a "server function" that
provides the server-side logic of your application.
The server function will be called when each client (web
browser) first loads the Shiny application's page. It
must take an \code{input} and an \code{output} parameter.
Any return value will be ignored.
See the
\href{http://rstudio.github.com/shiny/tutorial/}{tutorial}
for more on how to write a server function.
}
\examples{
\dontrun{
# A very simple Shiny app that takes a message from the user
# and outputs an uppercase version of it.
shinyServer(function(input, output) {
output$uppercase <- reactiveText(function() {
toupper(input$message)
})
})
}
}

24
man/shinyUI.Rd Normal file
View File

@@ -0,0 +1,24 @@
\name{shinyUI}
\alias{shinyUI}
\title{Create a Shiny UI handler}
\usage{
shinyUI(ui, path = "/")
}
\arguments{
\item{ui}{A user-interace definition}
\item{path}{The web server path to server the UI from}
}
\value{
Called for its side-effect of registering a UI handler
}
\description{
Register a UI handler by providing a UI definition
(created with e.g. \link{pageWithSidebar}) and web server
path (typically "/", the default value).
}
\examples{
el <- div(HTML("I like <u>turtles</u>"))
cat(as.character(el))
}

27
man/sidebarPanel.Rd Normal file
View File

@@ -0,0 +1,27 @@
\name{sidebarPanel}
\alias{sidebarPanel}
\title{Create a sidebar panel}
\usage{
sidebarPanel(...)
}
\arguments{
\item{...}{UI elements to include on the sidebar}
}
\value{
A sidebar that can be passed to \link{pageWithSidebar}
}
\description{
Create a sidebar panel containing input controls that can
in turn be passed to \link{pageWithSidebar}.
}
\examples{
# Sidebar with controls to select a dataset and specify
# the number of observations to view
sidebarPanel(
selectInput("dataset", "Choose a dataset:",
choices = c("rock", "pressure", "cars")),
numericInput("obs", "Observations:", 10)
)
}

20
man/singleton.Rd Normal file
View File

@@ -0,0 +1,20 @@
\name{singleton}
\alias{singleton}
\title{Include Content Only Once}
\usage{
singleton(x)
}
\arguments{
\item{x}{A \code{\link{tag}}, text, \code{\link{HTML}},
or list.}
}
\description{
Use \code{singleton} to wrap contents (tag, text, HTML,
or lists) that should be included in the generated
document only once, yet may appear in the
document-generating code more than once. Only the first
appearance of the content (in document order) will be
used. Useful for custom components that have JavaScript
files or stylesheets.
}

Some files were not shown because too many files have changed in this diff Show More