mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-11 16:08:19 -05:00
Compare commits
2313 Commits
rubyesque
...
patch/v0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b789d3f6e0 | ||
|
|
453536268a | ||
|
|
8b841cb7d2 | ||
|
|
24a8f8f38b | ||
|
|
90c00bed2f | ||
|
|
054c911a1f | ||
|
|
c2d5432a5d | ||
|
|
dd64b70f5b | ||
|
|
a69dbeb10f | ||
|
|
976a768446 | ||
|
|
5612cec91f | ||
|
|
46996eb81c | ||
|
|
12990f9fb2 | ||
|
|
77ff988232 | ||
|
|
8df98c29b8 | ||
|
|
7554f8395b | ||
|
|
6fc0bac106 | ||
|
|
d252feddc9 | ||
|
|
8c2645498d | ||
|
|
528acc4aa4 | ||
|
|
2ce45eab06 | ||
|
|
074c24aa10 | ||
|
|
79e4007732 | ||
|
|
742ce6673c | ||
|
|
4a74f588b9 | ||
|
|
f876dc066c | ||
|
|
65aaf386c2 | ||
|
|
4eae6bd362 | ||
|
|
4f7cfd6bd4 | ||
|
|
1136ad09ee | ||
|
|
4a67bd945c | ||
|
|
a2841f7cf2 | ||
|
|
4fa6e9dafe | ||
|
|
7ebd7959f9 | ||
|
|
4386015cff | ||
|
|
5cd014f7e6 | ||
|
|
30d0bfbdf0 | ||
|
|
48cfeca220 | ||
|
|
5fefc48a0b | ||
|
|
42f2ae16ec | ||
|
|
3d47b0201f | ||
|
|
7607b1215f | ||
|
|
eae2b40898 | ||
|
|
8e253046d8 | ||
|
|
b0e17f02b5 | ||
|
|
3f3c131737 | ||
|
|
2b227fcca5 | ||
|
|
639e55b537 | ||
|
|
8386404b25 | ||
|
|
a093afb630 | ||
|
|
7208688128 | ||
|
|
b94d406bd9 | ||
|
|
79188b7d62 | ||
|
|
85b2fc503d | ||
|
|
57f33109b2 | ||
|
|
d039547886 | ||
|
|
55621b7826 | ||
|
|
854730f258 | ||
|
|
9c4f73f314 | ||
|
|
e1fa491af7 | ||
|
|
b909a3e05c | ||
|
|
4159c539e7 | ||
|
|
d1bd232ec7 | ||
|
|
a47898a2c4 | ||
|
|
06b69e516a | ||
|
|
312c89aaee | ||
|
|
a5b1f020ae | ||
|
|
e9cd1bef43 | ||
|
|
7b17ce5de1 | ||
|
|
ffb1b06bf4 | ||
|
|
21e9ffec97 | ||
|
|
7cfaa2adfc | ||
|
|
4e80e35976 | ||
|
|
e9f78f6ace | ||
|
|
4d074c6fa9 | ||
|
|
0b3c2e198e | ||
|
|
48cd7200cc | ||
|
|
d925702b98 | ||
|
|
55cbd72a47 | ||
|
|
fbf1ba172f | ||
|
|
abb722f405 | ||
|
|
2c121dc2c3 | ||
|
|
dbcdbda2ef | ||
|
|
03784ba82e | ||
|
|
04768ad8fa | ||
|
|
29943408e5 | ||
|
|
dbadc05bef | ||
|
|
2e6347b380 | ||
|
|
6c65b1ffde | ||
|
|
358416687f | ||
|
|
1dc061e1c6 | ||
|
|
e7f2572f42 | ||
|
|
463ee7e027 | ||
|
|
667bc95d02 | ||
|
|
3f8cc71814 | ||
|
|
a9a6e96a6a | ||
|
|
02caf05aaf | ||
|
|
da88678a43 | ||
|
|
1e8a9de60a | ||
|
|
1a6181cb15 | ||
|
|
ece69342b8 | ||
|
|
fe601d631b | ||
|
|
42d2e5aaef | ||
|
|
bac7ab7f01 | ||
|
|
c6ac1474d4 | ||
|
|
5a9c4ad8f3 | ||
|
|
babc59f85c | ||
|
|
982a4361cd | ||
|
|
15e53ca55e | ||
|
|
ceb428b8bd | ||
|
|
b7fe3ed745 | ||
|
|
817b614e61 | ||
|
|
3321e95b54 | ||
|
|
fd5f536d14 | ||
|
|
fdbcb32be7 | ||
|
|
a774cee1d9 | ||
|
|
1c46f227d2 | ||
|
|
6191c96347 | ||
|
|
12918fdfee | ||
|
|
8cea246fc8 | ||
|
|
41455a008b | ||
|
|
be22d5c95d | ||
|
|
b298a62e46 | ||
|
|
839045ed66 | ||
|
|
a4a5c61fef | ||
|
|
dd6aa9e4cf | ||
|
|
e299b51779 | ||
|
|
d3f0129c3f | ||
|
|
ae938c66cc | ||
|
|
8d662f4f89 | ||
|
|
a1caa41fe9 | ||
|
|
6028263681 | ||
|
|
30f9291496 | ||
|
|
2400a44ded | ||
|
|
952556a9d1 | ||
|
|
2e18b5c64b | ||
|
|
cb499c6105 | ||
|
|
7f87bcddc1 | ||
|
|
6798917bc9 | ||
|
|
cf3f32ac37 | ||
|
|
a41e1dafc2 | ||
|
|
3ac5124e5c | ||
|
|
83a472794b | ||
|
|
af78d62b76 | ||
|
|
27a0708909 | ||
|
|
5ab58f86ba | ||
|
|
2c0bfdccf9 | ||
|
|
581618370b | ||
|
|
bdf217c45a | ||
|
|
42d6594159 | ||
|
|
fac1750b63 | ||
|
|
6907d192f5 | ||
|
|
0380f36489 | ||
|
|
1524dc2680 | ||
|
|
c95cc0a52b | ||
|
|
a876c5a888 | ||
|
|
bdd925886a | ||
|
|
4d3d292add | ||
|
|
48f03e79e2 | ||
|
|
e3af622a36 | ||
|
|
dcb300ab50 | ||
|
|
95ffd46a63 | ||
|
|
6f5d4a8620 | ||
|
|
62855cc969 | ||
|
|
bdfe9dfab2 | ||
|
|
465ddf1a01 | ||
|
|
18a4ac1653 | ||
|
|
76d55144c5 | ||
|
|
665590a2f9 | ||
|
|
81aa9a31c6 | ||
|
|
082490708f | ||
|
|
20ca4f8260 | ||
|
|
eda057eb07 | ||
|
|
aed31c0eba | ||
|
|
ea585458c7 | ||
|
|
e226e1c045 | ||
|
|
ef330dd613 | ||
|
|
1225560ccd | ||
|
|
eef184e6ef | ||
|
|
50337eb731 | ||
|
|
77f5e7d581 | ||
|
|
02118bac76 | ||
|
|
8b41be238a | ||
|
|
8791e70c67 | ||
|
|
34a4b8f2d2 | ||
|
|
54c936b010 | ||
|
|
ed8d95e055 | ||
|
|
25e8d080af | ||
|
|
4599a7ab4e | ||
|
|
5a106e0e6d | ||
|
|
e4a211ba02 | ||
|
|
7ab373c942 | ||
|
|
18f1ebf715 | ||
|
|
2c02f44b26 | ||
|
|
8ae8168252 | ||
|
|
c504f0b166 | ||
|
|
23da559d26 | ||
|
|
8b3ca12658 | ||
|
|
0f70b5662c | ||
|
|
475541cac0 | ||
|
|
007d6ad9c3 | ||
|
|
79db8d91ab | ||
|
|
a0c6feb386 | ||
|
|
1c72601123 | ||
|
|
c6bcdd1ae1 | ||
|
|
e0acdba626 | ||
|
|
a73e72f267 | ||
|
|
ee6682e7c8 | ||
|
|
6c36ea5753 | ||
|
|
3f798f1843 | ||
|
|
4a806db8ae | ||
|
|
9c12125512 | ||
|
|
51130793fe | ||
|
|
d39c93ee7e | ||
|
|
e1cfb29fa0 | ||
|
|
4dde16d836 | ||
|
|
1c57ef5931 | ||
|
|
0f642fe3ad | ||
|
|
46844b516a | ||
|
|
b9cef228d9 | ||
|
|
fd71d04000 | ||
|
|
bedb811621 | ||
|
|
173736fd69 | ||
|
|
21eea27c4a | ||
|
|
b39f04fbc3 | ||
|
|
04f898be21 | ||
|
|
defedda891 | ||
|
|
bb2c8e5fd2 | ||
|
|
7410bb9e9a | ||
|
|
fd3907596b | ||
|
|
221a233ea4 | ||
|
|
389adda8ec | ||
|
|
835b65740b | ||
|
|
b24abffd0d | ||
|
|
2ddf578f07 | ||
|
|
61b83c3e34 | ||
|
|
3454ddbb2a | ||
|
|
194e4ca70d | ||
|
|
074981a709 | ||
|
|
f157200a9f | ||
|
|
f70fcddaad | ||
|
|
aa2129b60e | ||
|
|
3794541828 | ||
|
|
b1a6dc65ba | ||
|
|
040ae293fb | ||
|
|
b74e93f3a4 | ||
|
|
dd3cc29af1 | ||
|
|
72b9a93088 | ||
|
|
4446f0a1f3 | ||
|
|
35802c12f6 | ||
|
|
f5fd30883a | ||
|
|
f8e720d6dc | ||
|
|
9f8f04caec | ||
|
|
69aec85882 | ||
|
|
e84012ec38 | ||
|
|
128df6a2bf | ||
|
|
7b76951461 | ||
|
|
38d87450e6 | ||
|
|
b9c6b84ff5 | ||
|
|
5ba1d57b1f | ||
|
|
a4d567e44d | ||
|
|
19a62ba7e9 | ||
|
|
65d6c7b558 | ||
|
|
8c828e7881 | ||
|
|
b711780688 | ||
|
|
855cdc8eda | ||
|
|
c930cce914 | ||
|
|
424696cf96 | ||
|
|
ea4ef45289 | ||
|
|
d2e13e6ac6 | ||
|
|
70ff6658eb | ||
|
|
3e1732b9c0 | ||
|
|
4eb18dc797 | ||
|
|
6e7adfadb0 | ||
|
|
a866224f7b | ||
|
|
77c50d6880 | ||
|
|
4290e2e7b0 | ||
|
|
4e8632de73 | ||
|
|
2792fe82a3 | ||
|
|
ab6df9f8a9 | ||
|
|
425489c74b | ||
|
|
9f04517801 | ||
|
|
9954639096 | ||
|
|
cd78364c17 | ||
|
|
563b986591 | ||
|
|
fe5552773d | ||
|
|
b7acaf9519 | ||
|
|
deffc90531 | ||
|
|
f54a6e8513 | ||
|
|
ab4dc641af | ||
|
|
dfe725dd78 | ||
|
|
dbe1e24561 | ||
|
|
075557929a | ||
|
|
d994db9c74 | ||
|
|
c462c61ac9 | ||
|
|
1ef9719059 | ||
|
|
3df0e7899c | ||
|
|
927972d889 | ||
|
|
00f409d21d | ||
|
|
6aea95b486 | ||
|
|
e4dd78b9a4 | ||
|
|
d940d5a597 | ||
|
|
5f1ea21be1 | ||
|
|
278851224d | ||
|
|
0161bc5a4e | ||
|
|
9cf5168d3f | ||
|
|
f066a4bdf4 | ||
|
|
0009756ed9 | ||
|
|
481e5437f1 | ||
|
|
238f54e011 | ||
|
|
180525f538 | ||
|
|
9a1958d19a | ||
|
|
e1469a1e44 | ||
|
|
85bde2b00d | ||
|
|
b20a15b4ed | ||
|
|
a67161f521 | ||
|
|
8d621e87a0 | ||
|
|
a560034614 | ||
|
|
b329c1f94c | ||
|
|
06459c0f52 | ||
|
|
2655c2511d | ||
|
|
fa9fdb01b0 | ||
|
|
4896948d38 | ||
|
|
133a37817e | ||
|
|
0cdee5ed7a | ||
|
|
2c21989b47 | ||
|
|
17f796847d | ||
|
|
02578dbf3a | ||
|
|
9f86e84830 | ||
|
|
1ae65bb999 | ||
|
|
ca72f5fa99 | ||
|
|
bc9b537d84 | ||
|
|
d9336d0ba2 | ||
|
|
eb537eaa24 | ||
|
|
f1d814ba04 | ||
|
|
760d360e96 | ||
|
|
5431fa1405 | ||
|
|
eb809e388b | ||
|
|
0205ad7a4f | ||
|
|
9599b14aa8 | ||
|
|
d72585e3d1 | ||
|
|
735a15d7c6 | ||
|
|
0984e6ed90 | ||
|
|
6004a4250a | ||
|
|
b39fcc75d0 | ||
|
|
a401553c13 | ||
|
|
a312abff31 | ||
|
|
92c1722469 | ||
|
|
e48136d31e | ||
|
|
b9d6f6fd9c | ||
|
|
66c8dfe44d | ||
|
|
5910c936b9 | ||
|
|
a2645061fe | ||
|
|
7b887d2fd5 | ||
|
|
8d2367ed82 | ||
|
|
075583f4be | ||
|
|
ce86e0a54a | ||
|
|
9308f8ea69 | ||
|
|
cc8b425adc | ||
|
|
25cf7955c9 | ||
|
|
d393865efc | ||
|
|
9d877320d8 | ||
|
|
1f72bef393 | ||
|
|
99c1069229 | ||
|
|
28718429e3 | ||
|
|
e145021760 | ||
|
|
b7dd51c594 | ||
|
|
9ebc3e5eec | ||
|
|
4db60d7b27 | ||
|
|
5d85199ae3 | ||
|
|
088c9e5450 | ||
|
|
bd51d1b2a2 | ||
|
|
0de7b6f63a | ||
|
|
85706eac84 | ||
|
|
96b646b94a | ||
|
|
753f6fc671 | ||
|
|
3f186747ed | ||
|
|
e6748c0793 | ||
|
|
2383796c49 | ||
|
|
fb38ab4ef2 | ||
|
|
1242a62bca | ||
|
|
55ceb12277 | ||
|
|
6dd4e7676d | ||
|
|
4abb58b239 | ||
|
|
385dab6036 | ||
|
|
2e9291decf | ||
|
|
623036f03b | ||
|
|
98075e9d9d | ||
|
|
b80ce0e222 | ||
|
|
b0162efb1f | ||
|
|
7b3e9ab031 | ||
|
|
b35e866d95 | ||
|
|
2d273d1550 | ||
|
|
6aef3eb879 | ||
|
|
61b0393657 | ||
|
|
30bf6c0714 | ||
|
|
fc64b115d3 | ||
|
|
bdc3f24ca6 | ||
|
|
b28300b184 | ||
|
|
5c1f9d03e4 | ||
|
|
ed368a225d | ||
|
|
2f17f392d5 | ||
|
|
b00a6e526e | ||
|
|
75031df45b | ||
|
|
39148a99d0 | ||
|
|
d72f5ee2ad | ||
|
|
c6ea459a68 | ||
|
|
e8525e5513 | ||
|
|
9046b85ca4 | ||
|
|
19257b76d2 | ||
|
|
fd8990b294 | ||
|
|
eb34f5e058 | ||
|
|
f8ced7e065 | ||
|
|
bf0a2d3c6c | ||
|
|
f5878326f7 | ||
|
|
1f0b439168 | ||
|
|
3464e11152 | ||
|
|
b5f99be635 | ||
|
|
3153b35c2a | ||
|
|
c450f9abc2 | ||
|
|
912434b259 | ||
|
|
5f61fc658a | ||
|
|
a268b74636 | ||
|
|
82a1bf7afd | ||
|
|
4392676667 | ||
|
|
c47ec2c3c0 | ||
|
|
e756fc8daa | ||
|
|
4629262a1a | ||
|
|
741aec198b | ||
|
|
c3e5b4fa36 | ||
|
|
d022907bcd | ||
|
|
8a7603a45c | ||
|
|
2ade82d883 | ||
|
|
3d8c38b558 | ||
|
|
0f2d0a33b4 | ||
|
|
d4ce1499cc | ||
|
|
9ab7895772 | ||
|
|
ee9f79e287 | ||
|
|
ad61dafb9a | ||
|
|
b81361ff6d | ||
|
|
52417c0a60 | ||
|
|
000beeb737 | ||
|
|
7cce94000b | ||
|
|
7a4bdb4191 | ||
|
|
279a8ff989 | ||
|
|
ed06a05ff0 | ||
|
|
a56ac24cc2 | ||
|
|
c8efd31d08 | ||
|
|
d8875151e8 | ||
|
|
6082431161 | ||
|
|
846c23a5a2 | ||
|
|
9a5faa92c4 | ||
|
|
f149be26ce | ||
|
|
c6d6ca5a5c | ||
|
|
98dc8069e3 | ||
|
|
32f5d68e51 | ||
|
|
855347a5d3 | ||
|
|
78c22cb74a | ||
|
|
8626a3efea | ||
|
|
d7e0d64ac2 | ||
|
|
e26b1e54c8 | ||
|
|
594024472d | ||
|
|
5f60d09d0b | ||
|
|
a05abda25c | ||
|
|
daa8e44180 | ||
|
|
837a26daee | ||
|
|
1223d3a2b3 | ||
|
|
a4d7eaf709 | ||
|
|
4b12d5527b | ||
|
|
e7553d6f15 | ||
|
|
b795d7de4e | ||
|
|
e732afd64c | ||
|
|
40b25e3826 | ||
|
|
b4d80b467a | ||
|
|
62ef377dd1 | ||
|
|
b85b1e95b4 | ||
|
|
f2ab7001d2 | ||
|
|
7fa6a2e9ef | ||
|
|
c499bbaa9f | ||
|
|
f2b16c414f | ||
|
|
2a73e28b19 | ||
|
|
9252d1290c | ||
|
|
049bd85b29 | ||
|
|
f95a0fa1d3 | ||
|
|
416466a0e1 | ||
|
|
bfd97c5484 | ||
|
|
9c1badd6aa | ||
|
|
929cb29c5b | ||
|
|
1e788962ef | ||
|
|
26f8c11526 | ||
|
|
a9b3922061 | ||
|
|
00ebe4fab2 | ||
|
|
1bee6aece7 | ||
|
|
be4eff413d | ||
|
|
e56571840b | ||
|
|
f931222256 | ||
|
|
25ff7d430d | ||
|
|
d9cf3ceb9b | ||
|
|
c37a84ab4d | ||
|
|
cf97fc9b95 | ||
|
|
03a8651cfd | ||
|
|
b206525f8e | ||
|
|
443850b778 | ||
|
|
6962fbf6f7 | ||
|
|
a2a7173e30 | ||
|
|
698ad2b890 | ||
|
|
dd43a82042 | ||
|
|
a25515dc2b | ||
|
|
e1a8e119bf | ||
|
|
4712b72019 | ||
|
|
2d162b0d4e | ||
|
|
1ea4b4d0b5 | ||
|
|
c75c7019a8 | ||
|
|
67ab5fbc8a | ||
|
|
31a5bee228 | ||
|
|
bcd7195bf5 | ||
|
|
322a9d397a | ||
|
|
71e6646ea0 | ||
|
|
b505507c35 | ||
|
|
76535c1da5 | ||
|
|
9edd94e1c0 | ||
|
|
32449b3c55 | ||
|
|
76c3d38e11 | ||
|
|
0e8ed7d770 | ||
|
|
5af4743f2f | ||
|
|
ab86232294 | ||
|
|
0202daf551 | ||
|
|
3b062de156 | ||
|
|
65786c4d41 | ||
|
|
d3bb742308 | ||
|
|
f98aa3f12b | ||
|
|
70881bb367 | ||
|
|
50adb5fdf5 | ||
|
|
c05701fc19 | ||
|
|
5e68b5f3e3 | ||
|
|
5f46d97409 | ||
|
|
7f07e47488 | ||
|
|
3999f4d3a5 | ||
|
|
b3d6220b01 | ||
|
|
7d88775b5a | ||
|
|
2ca5d3e0df | ||
|
|
3f34030a12 | ||
|
|
11dfa3d9aa | ||
|
|
6923a11038 | ||
|
|
33d78fcf29 | ||
|
|
2b3cfdf18b | ||
|
|
aa7a3f7013 | ||
|
|
1e1b3c8f5f | ||
|
|
a7cac36974 | ||
|
|
3af800e522 | ||
|
|
cf135dd658 | ||
|
|
4e9d113896 | ||
|
|
af8e81d208 | ||
|
|
83fa1ea4a6 | ||
|
|
a72f8b7b65 | ||
|
|
1b940a3117 | ||
|
|
5012831e7b | ||
|
|
85fb08e9a5 | ||
|
|
7ac84b6a91 | ||
|
|
3a72526016 | ||
|
|
c3e78f41b0 | ||
|
|
fdea9dddee | ||
|
|
2d3dd7d91d | ||
|
|
2487245e94 | ||
|
|
a4f12691ce | ||
|
|
e7e83eb8bb | ||
|
|
e8ffb68c08 | ||
|
|
283f69cb65 | ||
|
|
0fb4ab2dcf | ||
|
|
063ac989be | ||
|
|
82fb11a7b5 | ||
|
|
d238d61906 | ||
|
|
22fdc90159 | ||
|
|
b6cf4c4375 | ||
|
|
8aa32fff34 | ||
|
|
76d6ffea4a | ||
|
|
ea8ca8ea1e | ||
|
|
ed9ca04c58 | ||
|
|
093fbaa178 | ||
|
|
5f4c8cf176 | ||
|
|
c7ee37804c | ||
|
|
e0c31aa5af | ||
|
|
ab1494777d | ||
|
|
c01fafb605 | ||
|
|
5be2ffc0b2 | ||
|
|
3ede9396da | ||
|
|
e400a7a15e | ||
|
|
2939006a9a | ||
|
|
df975a0b6b | ||
|
|
bda86de632 | ||
|
|
5738a4ba48 | ||
|
|
38cfa46131 | ||
|
|
9525df1381 | ||
|
|
d393577ba8 | ||
|
|
f7c2a07b70 | ||
|
|
adf69b4890 | ||
|
|
593d22b640 | ||
|
|
7706eebafb | ||
|
|
91bd5127fb | ||
|
|
b87a0d7f25 | ||
|
|
0f431ed384 | ||
|
|
d520921b13 | ||
|
|
8d4b9076f7 | ||
|
|
93ebbcaf04 | ||
|
|
ecde1580fd | ||
|
|
8f73bb222c | ||
|
|
1108e04eff | ||
|
|
0f1a8f3358 | ||
|
|
77de4df0ff | ||
|
|
0564de37ee | ||
|
|
8c584ae0e0 | ||
|
|
30c80279c8 | ||
|
|
3f0ab9a88a | ||
|
|
6da3fcf446 | ||
|
|
26746ca303 | ||
|
|
64158eac43 | ||
|
|
9e3a7f5eda | ||
|
|
0a4f3db8ae | ||
|
|
c095085d84 | ||
|
|
41cc4e68e3 | ||
|
|
21fb7959a5 | ||
|
|
3aa22127e0 | ||
|
|
a679a37ffa | ||
|
|
649857ec28 | ||
|
|
4a6136b918 | ||
|
|
6e1c468a80 | ||
|
|
cadcd0c5e8 | ||
|
|
a531c306f1 | ||
|
|
b0a9449335 | ||
|
|
b6deb87cae | ||
|
|
7319c88674 | ||
|
|
d36317c563 | ||
|
|
1917202bd0 | ||
|
|
a8338912e0 | ||
|
|
bfea3201e8 | ||
|
|
a9bb440e6c | ||
|
|
f0942d58e8 | ||
|
|
aa093659a0 | ||
|
|
cccdbc05a5 | ||
|
|
dfa32b7be4 | ||
|
|
c7e0bd037a | ||
|
|
6c711b76b0 | ||
|
|
9c914f10c4 | ||
|
|
eda56d118a | ||
|
|
02c7351c6d | ||
|
|
ab618235f1 | ||
|
|
e4239c924b | ||
|
|
ffead9ed70 | ||
|
|
36aefadced | ||
|
|
75ccfe38ce | ||
|
|
e3cb3fe2e4 | ||
|
|
983e7e9b75 | ||
|
|
3db47c076c | ||
|
|
eeff285b33 | ||
|
|
029595f8ea | ||
|
|
ea2ec27724 | ||
|
|
f6bf4a416f | ||
|
|
af978a68e3 | ||
|
|
89dc1323e1 | ||
|
|
a4b5f63deb | ||
|
|
feaa6ccff4 | ||
|
|
7159293337 | ||
|
|
4a5b31e3a7 | ||
|
|
6f1dc89fb3 | ||
|
|
29dd405fe5 | ||
|
|
0f0b0cd3d8 | ||
|
|
262528e36a | ||
|
|
597e86dd57 | ||
|
|
b604dba948 | ||
|
|
1837a64bd2 | ||
|
|
9b413de4d8 | ||
|
|
3d77cbd677 | ||
|
|
62176c3218 | ||
|
|
d7a01c32cc | ||
|
|
cc493fd545 | ||
|
|
6b8679454d | ||
|
|
1b68d61e54 | ||
|
|
418de862e6 | ||
|
|
413653858e | ||
|
|
f0886a7556 | ||
|
|
0e2666948f | ||
|
|
d2fc851816 | ||
|
|
e1fb29c8c5 | ||
|
|
fe3158fdd6 | ||
|
|
721b26f80b | ||
|
|
d3ecfb22ee | ||
|
|
27a98020c9 | ||
|
|
ab56b72f39 | ||
|
|
8063f66958 | ||
|
|
bf270b9adb | ||
|
|
972db08740 | ||
|
|
6326c7cbaa | ||
|
|
4152ace514 | ||
|
|
038221408c | ||
|
|
9f76def7ce | ||
|
|
1b83770c5c | ||
|
|
3458d924ca | ||
|
|
0749b9500c | ||
|
|
b1dfc18a8c | ||
|
|
7b25c282c0 | ||
|
|
a128ceaf2d | ||
|
|
f266cab580 | ||
|
|
23bf9aaf17 | ||
|
|
1983f60ec6 | ||
|
|
27f8909406 | ||
|
|
9988206911 | ||
|
|
31fe1fdfa6 | ||
|
|
77b125ce2d | ||
|
|
6e68e07aa2 | ||
|
|
86bb010a93 | ||
|
|
4a623b596b | ||
|
|
bcf098ea7d | ||
|
|
4bfb226fb5 | ||
|
|
691615108b | ||
|
|
858ab00e36 | ||
|
|
7023f5b145 | ||
|
|
eb4fabeac6 | ||
|
|
a5e09f9ce4 | ||
|
|
c2fe4e8b6d | ||
|
|
5d22648d34 | ||
|
|
066fd15184 | ||
|
|
fe90c230d5 | ||
|
|
0b5ae92136 | ||
|
|
1c5565aaee | ||
|
|
69c177a3ec | ||
|
|
0645b3f65b | ||
|
|
9e7471fcc0 | ||
|
|
c520f53799 | ||
|
|
0bf1386802 | ||
|
|
b2ab3797aa | ||
|
|
ede0ca8bd1 | ||
|
|
81e35f0cc3 | ||
|
|
237522a1f7 | ||
|
|
2f94e1d2c9 | ||
|
|
2689dd32bb | ||
|
|
ad5e703b8f | ||
|
|
d3bc2e9279 | ||
|
|
0cd1644cf1 | ||
|
|
f02b405c12 | ||
|
|
baa7036799 | ||
|
|
431aecaf00 | ||
|
|
f31bb56ea6 | ||
|
|
cf3b805c46 | ||
|
|
517283ca58 | ||
|
|
f416b7ba47 | ||
|
|
973190b7a1 | ||
|
|
f536a9d3d3 | ||
|
|
1348ec3bcf | ||
|
|
9a250a4861 | ||
|
|
6450927192 | ||
|
|
7c9dbdc802 | ||
|
|
8d460afe2d | ||
|
|
6c44c2cf24 | ||
|
|
cea550ebba | ||
|
|
911a352ee6 | ||
|
|
3fadfbe06e | ||
|
|
5bf362927f | ||
|
|
4da5ca5ba9 | ||
|
|
d747005b30 | ||
|
|
03a395107d | ||
|
|
58ef4ccabf | ||
|
|
71ed082bb5 | ||
|
|
0819ac8124 | ||
|
|
0cdd223172 | ||
|
|
571393f146 | ||
|
|
c85868c652 | ||
|
|
a7a6f3b020 | ||
|
|
3a0a11d55a | ||
|
|
7eb8ddf372 | ||
|
|
87af63644a | ||
|
|
0a9dd18070 | ||
|
|
f82b061ba7 | ||
|
|
c17509e2a0 | ||
|
|
cb383d4f62 | ||
|
|
451f950d0d | ||
|
|
bd0eae0961 | ||
|
|
53a401f847 | ||
|
|
b288f5ca19 | ||
|
|
7a2fc19c4f | ||
|
|
046d712d6a | ||
|
|
e829aaecf1 | ||
|
|
9ab2f5338e | ||
|
|
d7bda924be | ||
|
|
07eb2e51b7 | ||
|
|
dfafa7ae40 | ||
|
|
dde266768c | ||
|
|
01c81675f7 | ||
|
|
71972eb362 | ||
|
|
eb9f5f9025 | ||
|
|
eb4d4d7437 | ||
|
|
1cb5e09109 | ||
|
|
cc82fff5d3 | ||
|
|
3212e59dcc | ||
|
|
44a795bf18 | ||
|
|
376e6f35a2 | ||
|
|
3b324e9532 | ||
|
|
a0df8f3490 | ||
|
|
6c14789362 | ||
|
|
880a12b914 | ||
|
|
93d69400e6 | ||
|
|
d4829e49ea | ||
|
|
1c56be3a6b | ||
|
|
07a0dfddc7 | ||
|
|
b86f9086ef | ||
|
|
343ca12c6f | ||
|
|
af3c4f84b6 | ||
|
|
3679e8795f | ||
|
|
39b4805a76 | ||
|
|
3bdcdf96d4 | ||
|
|
b54e5d33bc | ||
|
|
85e020a513 | ||
|
|
5b6268f5bc | ||
|
|
063b58eebb | ||
|
|
f8b38e4683 | ||
|
|
18e85c32b4 | ||
|
|
831fba9a53 | ||
|
|
b1f233cd8c | ||
|
|
3d0caba695 | ||
|
|
79c92f1f8e | ||
|
|
87f26e47bb | ||
|
|
9d8d04ae28 | ||
|
|
a42f046ff8 | ||
|
|
01c24a578b | ||
|
|
6b82354129 | ||
|
|
b00fbda1ae | ||
|
|
bab200ff03 | ||
|
|
357e81aeca | ||
|
|
3189c748b5 | ||
|
|
2700643cbf | ||
|
|
b0f95cd9e0 | ||
|
|
ff628ac0b2 | ||
|
|
f21aefe9e9 | ||
|
|
8babbd69d8 | ||
|
|
11bf02eb56 | ||
|
|
f5fa7d6d4b | ||
|
|
77bff6e6c2 | ||
|
|
e84a76cebd | ||
|
|
342265be94 | ||
|
|
62ec9291d8 | ||
|
|
dee6fbcb8f | ||
|
|
72fa9a2dcb | ||
|
|
ca27a9e31a | ||
|
|
18d0f45cf9 | ||
|
|
424fd515a4 | ||
|
|
00b40d64a1 | ||
|
|
3a7d0a5a9f | ||
|
|
57a02318e3 | ||
|
|
8f6d8cf0d6 | ||
|
|
5b6605b296 | ||
|
|
4d83596595 | ||
|
|
7e12a281f5 | ||
|
|
c63c10e48a | ||
|
|
155554f0b7 | ||
|
|
26b0836756 | ||
|
|
a87dc9bab2 | ||
|
|
9c1555a110 | ||
|
|
fbda2db884 | ||
|
|
2a229774ef | ||
|
|
137e5b13ef | ||
|
|
7920d66cd0 | ||
|
|
9f2dae7f3b | ||
|
|
ffde0ad1f5 | ||
|
|
2c2658a8ec | ||
|
|
6f2f8f6f7a | ||
|
|
4b6dcdd1b0 | ||
|
|
de346fd6c3 | ||
|
|
bf9d7c2012 | ||
|
|
143803f86d | ||
|
|
311143451d | ||
|
|
c9030f401d | ||
|
|
8668ddce74 | ||
|
|
7a495357f7 | ||
|
|
13864a811d | ||
|
|
5b65e4b250 | ||
|
|
dfe4a80501 | ||
|
|
bf82b9742a | ||
|
|
829a466f72 | ||
|
|
1206c70c42 | ||
|
|
3c32c349b9 | ||
|
|
0709f08d65 | ||
|
|
50f78c6e40 | ||
|
|
7e7afc6d38 | ||
|
|
1130eadac8 | ||
|
|
959fc2bbb2 | ||
|
|
f8ae505011 | ||
|
|
cd183a1926 | ||
|
|
bb2796fbc3 | ||
|
|
5de7103890 | ||
|
|
a78c91ba7e | ||
|
|
fca50da57b | ||
|
|
61f2c908b1 | ||
|
|
4c096ac068 | ||
|
|
2c95678be1 | ||
|
|
1a643cecf3 | ||
|
|
aa10b2e8c4 | ||
|
|
0b9317d047 | ||
|
|
4d58f05f38 | ||
|
|
6e879c8156 | ||
|
|
b6ee67aa41 | ||
|
|
07bed0c7c7 | ||
|
|
d2bd59d149 | ||
|
|
7bdac5a44e | ||
|
|
51f5db4374 | ||
|
|
e395ae6555 | ||
|
|
1df9c498cf | ||
|
|
57b3b919a5 | ||
|
|
00c6bbb297 | ||
|
|
b6536a0af3 | ||
|
|
d08a2507fa | ||
|
|
8bc8829577 | ||
|
|
c843e6f68c | ||
|
|
84583e5501 | ||
|
|
4548562138 | ||
|
|
32c170b10a | ||
|
|
97dafa0a55 | ||
|
|
0be1ee46f2 | ||
|
|
34c9ab7643 | ||
|
|
59dbca250f | ||
|
|
4028dbfda1 | ||
|
|
b9dbf610b0 | ||
|
|
d443810520 | ||
|
|
fcd941d33d | ||
|
|
9c063fa37c | ||
|
|
2720cfe346 | ||
|
|
c39e38081e | ||
|
|
3deb4c3f42 | ||
|
|
6945091238 | ||
|
|
c758c4785a | ||
|
|
19269a20fb | ||
|
|
45669cacb1 | ||
|
|
840bc52aae | ||
|
|
bbc36e349f | ||
|
|
a4325adcdd | ||
|
|
23f39649d0 | ||
|
|
87b09a534e | ||
|
|
39f0e5ae0c | ||
|
|
62aaab0926 | ||
|
|
cddfe999aa | ||
|
|
fcbb658ac2 | ||
|
|
3bbf06ba49 | ||
|
|
d9be6f1d2e | ||
|
|
5d70e68a0b | ||
|
|
529f2325b2 | ||
|
|
314d433f86 | ||
|
|
12ea950c5f | ||
|
|
f4d12220ca | ||
|
|
6a9cba90f4 | ||
|
|
6873e1f1cb | ||
|
|
fa0a91a75d | ||
|
|
020bb659c5 | ||
|
|
b1d6687fb0 | ||
|
|
f67e17b287 | ||
|
|
81bd57c5ea | ||
|
|
d803bae874 | ||
|
|
14606f4087 | ||
|
|
599fdc7ee5 | ||
|
|
722e205db5 | ||
|
|
f67849eb47 | ||
|
|
662ca4e40a | ||
|
|
aa61be74d8 | ||
|
|
10296fcd6b | ||
|
|
f8bf146b6c | ||
|
|
52f104c517 | ||
|
|
6c1fc224f0 | ||
|
|
6b9ae3a8b3 | ||
|
|
07f73030c6 | ||
|
|
47130c79ee | ||
|
|
f3a3bdfe4f | ||
|
|
e5e54fe4c1 | ||
|
|
29c0f9a43a | ||
|
|
0b78229c77 | ||
|
|
c2a1d70070 | ||
|
|
260ecd1d9f | ||
|
|
3dce2e761a | ||
|
|
80a54200ce | ||
|
|
51227d438a | ||
|
|
6fb4199d37 | ||
|
|
6ba46aff6b | ||
|
|
5da34d0646 | ||
|
|
f215088939 | ||
|
|
df34dcdb0c | ||
|
|
89f464af99 | ||
|
|
3f6f02f7d2 | ||
|
|
0d861e5389 | ||
|
|
b290c8700c | ||
|
|
81b6fbe263 | ||
|
|
b3af293f66 | ||
|
|
b187485172 | ||
|
|
b449d9759c | ||
|
|
d9d63a3a2e | ||
|
|
fd7b54fb77 | ||
|
|
887f8a606d | ||
|
|
7e3717243f | ||
|
|
221849aa3a | ||
|
|
b52d40ab28 | ||
|
|
3ed68ffd92 | ||
|
|
cc3cd2c141 | ||
|
|
5e30f7efc4 | ||
|
|
35090251ef | ||
|
|
338afb4893 | ||
|
|
194d8a05f8 | ||
|
|
93e276bd9b | ||
|
|
a69517519c | ||
|
|
f646b1efb4 | ||
|
|
fc9bedacc0 | ||
|
|
795eeee809 | ||
|
|
6d7818962e | ||
|
|
068517c933 | ||
|
|
5b030200df | ||
|
|
c732122966 | ||
|
|
d7eb9b2d18 | ||
|
|
b8b09adda1 | ||
|
|
07c8f0c4b7 | ||
|
|
2bd201de63 | ||
|
|
0b7e118a37 | ||
|
|
a546769225 | ||
|
|
81745f932d | ||
|
|
4415bf31d2 | ||
|
|
5c1bcb41d8 | ||
|
|
b659c4c2bb | ||
|
|
65adc8a405 | ||
|
|
4141f78717 | ||
|
|
80cb02d206 | ||
|
|
a5a4510a1e | ||
|
|
95c30649d3 | ||
|
|
8e5cbde08c | ||
|
|
6df8632e29 | ||
|
|
3c1218fff1 | ||
|
|
69c0414791 | ||
|
|
d63f83fcbb | ||
|
|
75c3bf0c2f | ||
|
|
c9a8ab2389 | ||
|
|
2c467c00e1 | ||
|
|
c63ec5a1f2 | ||
|
|
e886558cbb | ||
|
|
8dd6dabe50 | ||
|
|
c090c6adf9 | ||
|
|
84da0befcd | ||
|
|
267751c8b9 | ||
|
|
8add9f7188 | ||
|
|
a100b0991b | ||
|
|
9ce9c5e535 | ||
|
|
b2d004ca1a | ||
|
|
657d50f9a3 | ||
|
|
60e355c4f5 | ||
|
|
adb444a60f | ||
|
|
e7e13ff70d | ||
|
|
a1e81db597 | ||
|
|
f23f2ff0a0 | ||
|
|
c1b18098f1 | ||
|
|
31c39592e3 | ||
|
|
82a1dad22a | ||
|
|
1ecec24727 | ||
|
|
607841e947 | ||
|
|
e234b403ae | ||
|
|
80ce7a36f8 | ||
|
|
705a8666be | ||
|
|
9167905118 | ||
|
|
bdeb6734d8 | ||
|
|
9a7b042594 | ||
|
|
7aea256fd8 | ||
|
|
857b5e6932 | ||
|
|
1a2d675439 | ||
|
|
0c749643de | ||
|
|
09bb1548f9 | ||
|
|
5ffe531844 | ||
|
|
fab24a3200 | ||
|
|
899d5e9d1d | ||
|
|
ba510884f2 | ||
|
|
78e8df8e17 | ||
|
|
deba1609c3 | ||
|
|
88d2425ca3 | ||
|
|
7117f9e058 | ||
|
|
c21c407416 | ||
|
|
4b4ad42063 | ||
|
|
474d514c7d | ||
|
|
6239466da8 | ||
|
|
7746d75582 | ||
|
|
642c9ded08 | ||
|
|
e0ae931ddd | ||
|
|
0d7727a405 | ||
|
|
28f689498a | ||
|
|
eb8fec7f2d | ||
|
|
2e16fa1d70 | ||
|
|
1b856c4909 | ||
|
|
585ad30af1 | ||
|
|
c0cdc4083c | ||
|
|
9b9db4f161 | ||
|
|
84a1d8d25e | ||
|
|
d3115a3bf3 | ||
|
|
964789e9a6 | ||
|
|
eeded51ff8 | ||
|
|
8f24f1b4d6 | ||
|
|
ad910a295a | ||
|
|
cf14c6b1e9 | ||
|
|
49da114caa | ||
|
|
b8376ebbf7 | ||
|
|
29701d7295 | ||
|
|
16279695a9 | ||
|
|
999fc86bc6 | ||
|
|
0276d533fb | ||
|
|
b77fc34a7b | ||
|
|
60c450d57e | ||
|
|
73411c75db | ||
|
|
8d146f7dff | ||
|
|
5c34aa0bb5 | ||
|
|
2b2ed8162d | ||
|
|
9770bd8005 | ||
|
|
4e020818ae | ||
|
|
58471c6971 | ||
|
|
2a2e02bf56 | ||
|
|
75d8cee766 | ||
|
|
1aed36bd16 | ||
|
|
00ce58ed18 | ||
|
|
d11aa1a61c | ||
|
|
56a62d3b4d | ||
|
|
e6dd668657 | ||
|
|
f60a64c8db | ||
|
|
eff1c298c9 | ||
|
|
358b0a122b | ||
|
|
c0f7ba9d46 | ||
|
|
c4edae8196 | ||
|
|
398dab808c | ||
|
|
3530871560 | ||
|
|
1ba26fdb98 | ||
|
|
a3b85b4e3e | ||
|
|
e37a5d0394 | ||
|
|
e5a8e77e2a | ||
|
|
314b59798f | ||
|
|
e9ae16e534 | ||
|
|
c971ca0ce2 | ||
|
|
0ad9a5f9c6 | ||
|
|
c31d91668a | ||
|
|
f5c196d717 | ||
|
|
3b90eed89f | ||
|
|
9828c8b787 | ||
|
|
b3e997134f | ||
|
|
f560baa69b | ||
|
|
8cf5f00c87 | ||
|
|
482c3895d3 | ||
|
|
fc0d4bde35 | ||
|
|
33ed89a036 | ||
|
|
0a5953c104 | ||
|
|
77f6be1a8b | ||
|
|
5bd3f9a571 | ||
|
|
ef59119663 | ||
|
|
45baca7018 | ||
|
|
9b1edb7a97 | ||
|
|
31c071d086 | ||
|
|
ecf4c5c104 | ||
|
|
35fbfece0d | ||
|
|
b7721e42d3 | ||
|
|
386346cee9 | ||
|
|
bbecccc45e | ||
|
|
1a8f84c134 | ||
|
|
66181fdcdf | ||
|
|
b9c05e8a9c | ||
|
|
9c22d6c12a | ||
|
|
f3cedbbd6f | ||
|
|
3f3a660ca1 | ||
|
|
1c6ded8416 | ||
|
|
aa63fdb26f | ||
|
|
3932330ce6 | ||
|
|
3b946b1c69 | ||
|
|
14df829f18 | ||
|
|
788d024be6 | ||
|
|
c20b56e089 | ||
|
|
287f4f239e | ||
|
|
dce66945ec | ||
|
|
92bd1d5200 | ||
|
|
06d2df8211 | ||
|
|
36256856b5 | ||
|
|
a771ae853c | ||
|
|
ef4e10bbb1 | ||
|
|
0dbe4d936e | ||
|
|
731fee11d4 | ||
|
|
6759df52c3 | ||
|
|
914b997076 | ||
|
|
0b8a2fea72 | ||
|
|
fb2538135c | ||
|
|
b4c547c278 | ||
|
|
b243bc846b | ||
|
|
6b8f6162b6 | ||
|
|
158db1532b | ||
|
|
6abfdb59c6 | ||
|
|
009d1f9ced | ||
|
|
555fba6598 | ||
|
|
f9017b72a7 | ||
|
|
99c3c2fc80 | ||
|
|
32381679f2 | ||
|
|
3d031265d1 | ||
|
|
026cda0071 | ||
|
|
fb41ed5a86 | ||
|
|
8a08468a73 | ||
|
|
f600cb4f2c | ||
|
|
f754f028dc | ||
|
|
41b292b45b | ||
|
|
af9be9cae8 | ||
|
|
ccfaea64c5 | ||
|
|
a86fc96730 | ||
|
|
cf51af17fd | ||
|
|
8c1b6a5cf0 | ||
|
|
bcecb8cd76 | ||
|
|
557790b0e5 | ||
|
|
8eb5a45718 | ||
|
|
7b64cef73b | ||
|
|
106203170e | ||
|
|
174d2bfc11 | ||
|
|
abda9c7f97 | ||
|
|
8e95260df9 | ||
|
|
5af1ae1920 | ||
|
|
f0eb9d48c9 | ||
|
|
0ac284009e | ||
|
|
fcf963639e | ||
|
|
ba8c0fb1d5 | ||
|
|
08fe74675b | ||
|
|
f5e7fdf8aa | ||
|
|
f6e447d049 | ||
|
|
531b21c012 | ||
|
|
a057456d5a | ||
|
|
0f043b39f5 | ||
|
|
dc0701e21d | ||
|
|
712f18f4e8 | ||
|
|
e0a82b4aaf | ||
|
|
a7f238ae0b | ||
|
|
0d99b6de7a | ||
|
|
6f627fca96 | ||
|
|
339fbc482b | ||
|
|
72e3ee1d77 | ||
|
|
a9750fb088 | ||
|
|
d80e3b0824 | ||
|
|
46df7a9ea0 | ||
|
|
b851ce49f7 | ||
|
|
d9afde3e15 | ||
|
|
b38c57f308 | ||
|
|
93e7e2e06e | ||
|
|
5ac09180a5 | ||
|
|
3819ca3a62 | ||
|
|
00426b4c9b | ||
|
|
58d8cefcc0 | ||
|
|
c8bb122557 | ||
|
|
bcef603a36 | ||
|
|
639b4d392a | ||
|
|
6d5f06a61d | ||
|
|
3e00e2ad58 | ||
|
|
cad2be5e53 | ||
|
|
58fe5f263f | ||
|
|
79ec6845f8 | ||
|
|
0f81ba8307 | ||
|
|
a30543b035 | ||
|
|
5c4473a1d9 | ||
|
|
7ff47c8c51 | ||
|
|
2544e29be3 | ||
|
|
d7bf564e8f | ||
|
|
0f135f881a | ||
|
|
2f1bb5e1c0 | ||
|
|
02b5f96eee | ||
|
|
3e77871539 | ||
|
|
676affdd03 | ||
|
|
5caf41c067 | ||
|
|
58f9e89fab | ||
|
|
8776f0f4a5 | ||
|
|
84a8c27926 | ||
|
|
f061e3486e | ||
|
|
da23995343 | ||
|
|
4e0a61bd9b | ||
|
|
d3e2fa5df5 | ||
|
|
00b111c974 | ||
|
|
bd265c00a0 | ||
|
|
ef53a63766 | ||
|
|
688c7f1a1c | ||
|
|
2989922253 | ||
|
|
6f6619a5ab | ||
|
|
1594c228e8 | ||
|
|
fb44f52aa9 | ||
|
|
045ead1728 | ||
|
|
4404463e53 | ||
|
|
b79b61c8c8 | ||
|
|
467048a0fc | ||
|
|
2a1edffce3 | ||
|
|
ce833c39d5 | ||
|
|
721a74eee6 | ||
|
|
8f9f4f894c | ||
|
|
842765dad0 | ||
|
|
b23cc47d95 | ||
|
|
85baa596d0 | ||
|
|
e371fff110 | ||
|
|
dffe6b4f39 | ||
|
|
dbf684f385 | ||
|
|
7b20fd91ef | ||
|
|
300c25ded1 | ||
|
|
284e814d2a | ||
|
|
d5cdaddeea | ||
|
|
8635b395a1 | ||
|
|
c2cf4e72f8 | ||
|
|
92def0f71d | ||
|
|
bccae9d71c | ||
|
|
90b6a2f82b | ||
|
|
0462df7de2 | ||
|
|
d4b29ab08d | ||
|
|
09f2dfe181 | ||
|
|
317d013a0b | ||
|
|
e26b2dcd43 | ||
|
|
4676dbc740 | ||
|
|
778f869ddb | ||
|
|
a7e9b1f76d | ||
|
|
096e56aaa8 | ||
|
|
d1bcc557f0 | ||
|
|
e041fab319 | ||
|
|
3394e36325 | ||
|
|
a501458e5a | ||
|
|
da08eef5ef | ||
|
|
0ea714552a | ||
|
|
8cba584e52 | ||
|
|
878f07d2cf | ||
|
|
d297de732f | ||
|
|
c41d4d32b9 | ||
|
|
5d34134888 | ||
|
|
f968ec4cac | ||
|
|
fdb256a534 | ||
|
|
62a2b57613 | ||
|
|
90f5ebfa58 | ||
|
|
66aecee519 | ||
|
|
f0c661d6e2 | ||
|
|
a27f5b4c15 | ||
|
|
95b69f0003 | ||
|
|
6925f0bf7a | ||
|
|
6c1a9ed83b | ||
|
|
3ebef79313 | ||
|
|
57393806b0 | ||
|
|
721d8cfa49 | ||
|
|
9e5b68444f | ||
|
|
9f6c619401 | ||
|
|
1b47e40a3a | ||
|
|
3fc14102e5 | ||
|
|
d907992c39 | ||
|
|
ae2f35c6c5 | ||
|
|
ddd804041d | ||
|
|
2478cbdb6f | ||
|
|
80e992a9fc | ||
|
|
e4b8e08e89 | ||
|
|
6c556b8a72 | ||
|
|
9659c19b23 | ||
|
|
aa6e9d9bf2 | ||
|
|
812f0ac32c | ||
|
|
296b312950 | ||
|
|
29a06406ea | ||
|
|
261cb7d3cd | ||
|
|
ba5a57ac07 | ||
|
|
c0ebe9d7a1 | ||
|
|
2b83012786 | ||
|
|
b9761288bd | ||
|
|
a711e83398 | ||
|
|
2705385681 | ||
|
|
4b2f3dd070 | ||
|
|
876acf2839 | ||
|
|
d0446f068c | ||
|
|
a9396d1e2f | ||
|
|
e87102e586 | ||
|
|
8b213f8d7c | ||
|
|
9a7dc5ba86 | ||
|
|
9acb3f83f8 | ||
|
|
1c676211ee | ||
|
|
89728164eb | ||
|
|
d0c4093f5a | ||
|
|
d7f680fb19 | ||
|
|
48279e060c | ||
|
|
10dd0d07dc | ||
|
|
d82dc4cf77 | ||
|
|
2b9553e4da | ||
|
|
ac112ea287 | ||
|
|
4f6b099615 | ||
|
|
d4dcb162d0 | ||
|
|
46054f513b | ||
|
|
261f67df50 | ||
|
|
22f9b2affe | ||
|
|
06c392c066 | ||
|
|
3d67b3bc17 | ||
|
|
6cefab5d8a | ||
|
|
cc261de37b | ||
|
|
20712641a7 | ||
|
|
e4d0b16fd5 | ||
|
|
f8c25791e9 | ||
|
|
704aa433d4 | ||
|
|
3bd1003164 | ||
|
|
8dd55d7506 | ||
|
|
6252d778c1 | ||
|
|
942248b9e6 | ||
|
|
4793449105 | ||
|
|
428e3bc0fc | ||
|
|
bf2c80cfcf | ||
|
|
06b0685a57 | ||
|
|
231ea25968 | ||
|
|
8658eeddb2 | ||
|
|
d0769eed97 | ||
|
|
b1fcd1f7c8 | ||
|
|
db1259b3e0 | ||
|
|
1a5f42b753 | ||
|
|
75d061a7fa | ||
|
|
9fb4c4140b | ||
|
|
0306877fb9 | ||
|
|
86e3b05a3f | ||
|
|
a4e8907c95 | ||
|
|
916ad6535a | ||
|
|
c129309937 | ||
|
|
0088e9ae77 | ||
|
|
79806b5ad5 | ||
|
|
7d59fbfc36 | ||
|
|
e645bdf249 | ||
|
|
0bedf26849 | ||
|
|
a153c5b4ce | ||
|
|
44f1f3e9ae | ||
|
|
8c82fa86c6 | ||
|
|
d4da934d6a | ||
|
|
56cc664c26 | ||
|
|
eaa0bdfc62 | ||
|
|
c538e9c6d4 | ||
|
|
54b9af0299 | ||
|
|
c7d5b9211c | ||
|
|
7ca22a8718 | ||
|
|
4e37b32976 | ||
|
|
9725b23db1 | ||
|
|
2e60f2b2ce | ||
|
|
004776a522 | ||
|
|
92fa1dde79 | ||
|
|
464821c4e2 | ||
|
|
e95483236a | ||
|
|
a9b97a85ad | ||
|
|
6170befc90 | ||
|
|
5ecb85cb6d | ||
|
|
d2fc04f45d | ||
|
|
fb4da933d4 | ||
|
|
7483900db2 | ||
|
|
9f78dbf200 | ||
|
|
ef9b9bdd6d | ||
|
|
1937aa43ba | ||
|
|
293ea66784 | ||
|
|
e98d8f4ced | ||
|
|
418d2afb2a | ||
|
|
a4c1a6187f | ||
|
|
123ca34040 | ||
|
|
6b3224116c | ||
|
|
635e0c9788 | ||
|
|
dd33a0e0ec | ||
|
|
191deeaba6 | ||
|
|
245072f7a2 | ||
|
|
6b858512b6 | ||
|
|
b857a01c30 | ||
|
|
94c9a3e05b | ||
|
|
8928d2c488 | ||
|
|
25bd5654aa | ||
|
|
83d5b96adf | ||
|
|
7eb90c5718 | ||
|
|
4b1af75724 | ||
|
|
8d07ab6527 | ||
|
|
ce4ea7e7a9 | ||
|
|
50ab5e7517 | ||
|
|
431c1d7f66 | ||
|
|
a55090dc2f | ||
|
|
d76cdb73b0 | ||
|
|
2594664330 | ||
|
|
f9ed075db6 | ||
|
|
099ced4f94 | ||
|
|
13d2513930 | ||
|
|
2211b1c65e | ||
|
|
1fd37ca2b2 | ||
|
|
7070e3748d | ||
|
|
dfaef908c2 | ||
|
|
67540c763b | ||
|
|
14269bd4d9 | ||
|
|
131663032c | ||
|
|
8ac71165e9 | ||
|
|
346758d3f0 | ||
|
|
d3e7f130fb | ||
|
|
aef8837b5d | ||
|
|
dc0832adba | ||
|
|
c0cd269322 | ||
|
|
0ad3ff655e | ||
|
|
ef45a62cc9 | ||
|
|
b79abbdea9 | ||
|
|
a9e4ce005d | ||
|
|
987f2b2a55 | ||
|
|
930e2d1d9d | ||
|
|
f4ada70e56 | ||
|
|
97e658709d | ||
|
|
ec2992cd2d | ||
|
|
619208565b | ||
|
|
dcd689d2ea | ||
|
|
e94de15f83 | ||
|
|
6af7de51a5 | ||
|
|
559c6722ff | ||
|
|
aab2cce978 | ||
|
|
f4a4af0fa4 | ||
|
|
6934838974 | ||
|
|
1aadd25cb5 | ||
|
|
0caf944668 | ||
|
|
6452f62b88 | ||
|
|
e061dfd808 | ||
|
|
4da53ef219 | ||
|
|
347e44f04d | ||
|
|
8997fa7242 | ||
|
|
19ba6efb82 | ||
|
|
d10cbc9984 | ||
|
|
6c7d9ded00 | ||
|
|
6d04e89d7d | ||
|
|
2beb24147d | ||
|
|
16c5f4e377 | ||
|
|
03a6f1753c | ||
|
|
9fb61d8446 | ||
|
|
bc3322d3c9 | ||
|
|
06c7bf7514 | ||
|
|
4c89a000e4 | ||
|
|
86d61e0b44 | ||
|
|
6407390d72 | ||
|
|
648120cabf | ||
|
|
ce5b3f290a | ||
|
|
5308ca1806 | ||
|
|
6df6d408d2 | ||
|
|
b60d6ccdd8 | ||
|
|
de01c9685e | ||
|
|
31d2ecc9fd | ||
|
|
2f8502aec6 | ||
|
|
d377b04dad | ||
|
|
40cc78ae1e | ||
|
|
268f1e8472 | ||
|
|
004b7c782d | ||
|
|
33b293f0aa | ||
|
|
ad584a98ad | ||
|
|
e2509eddb2 | ||
|
|
b9f72d0e78 | ||
|
|
c839bb2db3 | ||
|
|
30161369a8 | ||
|
|
c65aa9732e | ||
|
|
bc72b8fd1c | ||
|
|
f089531bd1 | ||
|
|
8d8ea53804 | ||
|
|
89e405e927 | ||
|
|
ca984a6630 | ||
|
|
fa39a55eca | ||
|
|
c3a1ba2f2d | ||
|
|
86e291f250 | ||
|
|
dd1d4439a9 | ||
|
|
cbfde18f8c | ||
|
|
e2c2e23d2a | ||
|
|
40cc5d5242 | ||
|
|
9765194ace | ||
|
|
628465e6b5 | ||
|
|
58706df120 | ||
|
|
b19225c747 | ||
|
|
c304889e61 | ||
|
|
05a9204678 | ||
|
|
ed8537bb0b | ||
|
|
6a9ae10fcf | ||
|
|
05358904bf | ||
|
|
1a6901c3e3 | ||
|
|
7aaba8244b | ||
|
|
8c45dcde88 | ||
|
|
6c155b04b2 | ||
|
|
cd8ad9a2ec | ||
|
|
a5db7d0246 | ||
|
|
b84b467b96 | ||
|
|
0812aaac88 | ||
|
|
194d2f911e | ||
|
|
e360b36b8a | ||
|
|
b6f66dd287 | ||
|
|
0a4bb48cd3 | ||
|
|
15d62d4a91 | ||
|
|
5b13c44ef9 | ||
|
|
0a4250f3b4 | ||
|
|
f79223ed58 | ||
|
|
2d28218a2a | ||
|
|
35974f2ee1 | ||
|
|
1f73323fb9 | ||
|
|
a3d0736eec | ||
|
|
4bdd486c00 | ||
|
|
c3895c9bd7 | ||
|
|
e9ddd89b32 | ||
|
|
88a8f2d609 | ||
|
|
a5dc5c89e8 | ||
|
|
3a15a35137 | ||
|
|
b644640804 | ||
|
|
aaa4f66671 | ||
|
|
07e021199e | ||
|
|
6b2ca7dc80 | ||
|
|
091d62803e | ||
|
|
547999bae0 | ||
|
|
99013f7998 | ||
|
|
fc396800db | ||
|
|
6d03ae57ac | ||
|
|
4a0aa57355 | ||
|
|
7db737494c | ||
|
|
b285501c44 | ||
|
|
2f9b29994f | ||
|
|
917434cb6b | ||
|
|
28a52bb658 | ||
|
|
82bc19374c | ||
|
|
0b23f30bb7 | ||
|
|
64a62d7aed | ||
|
|
de31cf8e7d | ||
|
|
3484f9afb3 | ||
|
|
81df0ff390 | ||
|
|
d403ec7399 | ||
|
|
6ac77835df | ||
|
|
b113119a9a | ||
|
|
b713057614 | ||
|
|
4268570166 | ||
|
|
ead508c0d0 | ||
|
|
f8e1be8565 | ||
|
|
360f1af32f | ||
|
|
49a08d14c3 | ||
|
|
d897df6a30 | ||
|
|
ba4f3a1553 | ||
|
|
6ba9534da4 | ||
|
|
c16ef96754 | ||
|
|
e728491aa2 | ||
|
|
ce356fa266 | ||
|
|
5e46323ca3 | ||
|
|
0a7d047246 | ||
|
|
3fa534a3eb | ||
|
|
25990f59d8 | ||
|
|
c6405f70d3 | ||
|
|
acae6c2c49 | ||
|
|
141fdc2197 | ||
|
|
a7ed8a006f | ||
|
|
b1a0ebd531 | ||
|
|
e8021acccd | ||
|
|
39b0da2a3f | ||
|
|
fd3d18f6c5 | ||
|
|
ecc27d1674 | ||
|
|
7d0514ab36 | ||
|
|
44c3024c00 | ||
|
|
253c92bab7 | ||
|
|
c10850118d | ||
|
|
4f017e9173 | ||
|
|
5ed46c82cb | ||
|
|
64391e906d | ||
|
|
47b4ee07ab | ||
|
|
3000cbf763 | ||
|
|
76b3d314a8 | ||
|
|
ba646de0ad | ||
|
|
395f746a05 | ||
|
|
f7e57cd398 | ||
|
|
3ea6d97ed2 | ||
|
|
affc0d8b67 | ||
|
|
c637e310e9 | ||
|
|
6ee7dcdd51 | ||
|
|
23470267fe | ||
|
|
4a92bb91df | ||
|
|
69522c422c | ||
|
|
bc5e3524eb | ||
|
|
479297fc35 | ||
|
|
516feafcfb | ||
|
|
a135c82ab5 | ||
|
|
10996f1cbd | ||
|
|
23b060e1f5 | ||
|
|
622ff3a256 | ||
|
|
5d457b6834 | ||
|
|
f10f76d127 | ||
|
|
58f3382daf | ||
|
|
0e1139446e | ||
|
|
f433216fae | ||
|
|
ed680baaac | ||
|
|
e0a9d908ed | ||
|
|
bfa4a46bd5 | ||
|
|
03f3ff991e | ||
|
|
619b4824f0 | ||
|
|
021af0186b | ||
|
|
d3caad8b8d | ||
|
|
ec6bec3326 | ||
|
|
dd54740d36 | ||
|
|
8f65156bda | ||
|
|
96c7df5afa | ||
|
|
0c19105fbf | ||
|
|
4145d83248 | ||
|
|
6490705e2a | ||
|
|
10d2432df5 | ||
|
|
815db72671 | ||
|
|
6d0ba61c54 | ||
|
|
5f61267f75 | ||
|
|
94ee42cebb | ||
|
|
b6795e5c63 | ||
|
|
ef85d063c2 | ||
|
|
59755971e5 | ||
|
|
c5ab831a87 | ||
|
|
6715dc2a5d | ||
|
|
af6de64ec0 | ||
|
|
1ac2448f90 | ||
|
|
b5f34b30d3 | ||
|
|
01f4e080df | ||
|
|
d55335e70b | ||
|
|
a8c1dc4bc6 | ||
|
|
2897059503 | ||
|
|
d491f9df5a | ||
|
|
bc40318e40 | ||
|
|
3935434f04 | ||
|
|
4cf1f2de94 | ||
|
|
73156c6780 | ||
|
|
bc52bafa8d | ||
|
|
5c9007b242 | ||
|
|
5857e3f75e | ||
|
|
e202831013 | ||
|
|
4cbbfccb6d | ||
|
|
21f3b1cf34 | ||
|
|
f7b384e9b6 | ||
|
|
1e6ab47ee4 | ||
|
|
78341ea2f1 | ||
|
|
3f8a4d4273 | ||
|
|
bae517c9f8 | ||
|
|
c88ccbf9bc | ||
|
|
5e40f5d509 | ||
|
|
46389131bc | ||
|
|
c6a344d0d9 | ||
|
|
bcc2c377a0 | ||
|
|
bb6afc847e | ||
|
|
e0193151db | ||
|
|
42a80bad8e | ||
|
|
6e3e77f65d | ||
|
|
e155f022a0 | ||
|
|
db65aab347 | ||
|
|
a180c5f357 | ||
|
|
1c0279f17c | ||
|
|
8866eb292b | ||
|
|
6fdda3391e | ||
|
|
fdb8dd4e5b | ||
|
|
9a1d3783ee | ||
|
|
3841d9e322 | ||
|
|
e392eadf8a | ||
|
|
f743d5d0b5 | ||
|
|
4a76bf59ef | ||
|
|
205b29e2f5 | ||
|
|
d511b82264 | ||
|
|
aaae112e60 | ||
|
|
955fd6207f | ||
|
|
4e56c96612 | ||
|
|
dd046f3442 | ||
|
|
5a947f83a1 | ||
|
|
b87b8b54fd | ||
|
|
233c0537a1 | ||
|
|
63d4798a50 | ||
|
|
6c47517684 | ||
|
|
c58b1a0143 | ||
|
|
f489d9131b | ||
|
|
f0109c5588 | ||
|
|
c16becba56 | ||
|
|
4605788696 | ||
|
|
87908313cc | ||
|
|
9cc2eba7b8 | ||
|
|
2459cee57b | ||
|
|
0bf6ce57ed | ||
|
|
7041424f96 | ||
|
|
9509285c16 | ||
|
|
e55ee0e65d | ||
|
|
9ea70497c2 | ||
|
|
3389b9e9fd | ||
|
|
76d4d54639 | ||
|
|
1b692b6c37 | ||
|
|
40d8cef1a2 | ||
|
|
23550c0062 | ||
|
|
949bd940ee | ||
|
|
79bdb9eed5 | ||
|
|
a141f08298 | ||
|
|
dee43a3911 | ||
|
|
ef227d0139 | ||
|
|
cbcf9ce645 | ||
|
|
0e5af2b16c | ||
|
|
85ca3a3b27 | ||
|
|
fc5f5f3b6c | ||
|
|
716fd8c0b9 | ||
|
|
a517393c43 | ||
|
|
c2311faffe | ||
|
|
fe453b0d66 | ||
|
|
7e75b0fc02 | ||
|
|
11b0a0a73d | ||
|
|
82fdb5c3eb | ||
|
|
3f1d532c8b | ||
|
|
f258b00aa7 | ||
|
|
4e71b9576d | ||
|
|
f36567a5cd | ||
|
|
924ebb6c7f | ||
|
|
6e7e8eb44a | ||
|
|
308c583254 | ||
|
|
97b2f7e5ca | ||
|
|
3ea88a07d9 | ||
|
|
588f8bb96a | ||
|
|
c93c0dd721 | ||
|
|
fc59c254fd | ||
|
|
2f8b6a150f | ||
|
|
db60ac5c17 | ||
|
|
e1f09853c5 | ||
|
|
24656713a5 | ||
|
|
7dd0269292 | ||
|
|
8b87cea7aa | ||
|
|
c7559a6946 | ||
|
|
945c6080ad | ||
|
|
44590965d1 | ||
|
|
7ab64d678f | ||
|
|
e406a76b62 | ||
|
|
e26f175a8f | ||
|
|
d4ab84745d | ||
|
|
32dbc3101e | ||
|
|
0a924eb718 | ||
|
|
a284327bfc | ||
|
|
2ea38d6ecc | ||
|
|
6a34bbfddd | ||
|
|
58323ada4b | ||
|
|
5fd723cb80 | ||
|
|
5c626e6957 | ||
|
|
5d949842eb | ||
|
|
b595c17d78 | ||
|
|
b84973ba2b | ||
|
|
61be49e7b2 | ||
|
|
8faf5659ee | ||
|
|
cc9267a646 | ||
|
|
55838bb032 | ||
|
|
67619ac5e8 | ||
|
|
952b342859 | ||
|
|
c7149c460d | ||
|
|
fd0613ea0e | ||
|
|
36d2dddc59 | ||
|
|
63c5b05584 | ||
|
|
4b235e5b87 | ||
|
|
6c51fffdaa | ||
|
|
5d6d638c85 | ||
|
|
90eb515167 | ||
|
|
17526711a2 | ||
|
|
cf0118e090 | ||
|
|
868d6fec42 | ||
|
|
851f5854bf | ||
|
|
eb5428c971 | ||
|
|
81188df7ef | ||
|
|
9fd365cc41 | ||
|
|
999df6e40f | ||
|
|
076d069568 | ||
|
|
2738648197 | ||
|
|
36013009a1 | ||
|
|
1b60233862 | ||
|
|
2cba10dd05 | ||
|
|
b3944127ea | ||
|
|
f1674378ca | ||
|
|
6f0191e1cf | ||
|
|
1848844be6 | ||
|
|
8b6362c749 | ||
|
|
d860d13361 | ||
|
|
4b077dbf4c | ||
|
|
40f73bbfe2 | ||
|
|
f455706d7c | ||
|
|
23e9672476 | ||
|
|
36f992f95f | ||
|
|
b2c6d526ab | ||
|
|
fe1e833677 | ||
|
|
8df1b9e8e5 | ||
|
|
38b0f71b01 | ||
|
|
29d2f115f8 | ||
|
|
0f677b4891 | ||
|
|
2f7dd04168 | ||
|
|
ed3b667985 | ||
|
|
6ae1d8c158 | ||
|
|
404bced97b | ||
|
|
5af49c8a82 | ||
|
|
85aa98e8e2 | ||
|
|
330d102f62 | ||
|
|
32b33a7910 | ||
|
|
17c6a0f28a | ||
|
|
7341eed1cf | ||
|
|
ff99fbfbc9 | ||
|
|
9f67fdc771 | ||
|
|
521143a16b | ||
|
|
2622a25b12 | ||
|
|
a91e925221 | ||
|
|
6c3289d5a5 | ||
|
|
988a91ac06 | ||
|
|
aa7c913e9a | ||
|
|
56db9feaa4 | ||
|
|
5ace0f13c9 | ||
|
|
076e6c9479 | ||
|
|
8277b1192e | ||
|
|
150b978b0e | ||
|
|
6c72096bfe | ||
|
|
87c18cea80 | ||
|
|
e658734084 | ||
|
|
ec4f350baa | ||
|
|
095f583211 | ||
|
|
3c864cf6d2 | ||
|
|
eb4b21ce9f | ||
|
|
ff5349fd90 | ||
|
|
1f34ffa85d | ||
|
|
e98cab1f7c | ||
|
|
aabc9659a2 | ||
|
|
8d8d308f7a | ||
|
|
3ebd4595c6 | ||
|
|
7e1168946f | ||
|
|
134689d8aa | ||
|
|
56282f9cbb | ||
|
|
b4713741b1 | ||
|
|
e42fe3bd61 | ||
|
|
4fd2dade60 | ||
|
|
e12b03504c | ||
|
|
153156c1fa | ||
|
|
3ecc69da2b | ||
|
|
07ad29da41 | ||
|
|
7d0de0b26f | ||
|
|
77fab9c78f | ||
|
|
3a8f3272c7 | ||
|
|
2d44cbac1b | ||
|
|
893d72677b | ||
|
|
979eca4066 | ||
|
|
258d13e746 | ||
|
|
779531da5d | ||
|
|
31d71006d7 | ||
|
|
64ca66c062 | ||
|
|
6e1a2b3427 | ||
|
|
f585235192 | ||
|
|
9355643554 | ||
|
|
ccc6055926 | ||
|
|
6639446bb8 | ||
|
|
e2925c585f | ||
|
|
6c76b0473c | ||
|
|
e1e19632a5 | ||
|
|
3e5364d5c0 | ||
|
|
6c98de4c8b | ||
|
|
9613dde4d2 | ||
|
|
d47df2e538 | ||
|
|
6fcacd5159 | ||
|
|
11b39cb020 | ||
|
|
d81f132db6 | ||
|
|
095697e789 | ||
|
|
62d98c3137 | ||
|
|
e80d5dc172 | ||
|
|
421e29db2d | ||
|
|
9e6e53583c | ||
|
|
3f59a7d84e | ||
|
|
21ffd788ab | ||
|
|
8dadfea724 | ||
|
|
00ce52ecf7 | ||
|
|
50ac13d3fd | ||
|
|
58318fec46 | ||
|
|
a49941113e | ||
|
|
595801cb99 | ||
|
|
0b469f09df | ||
|
|
1e1f4e4a47 | ||
|
|
c63e2ae7c8 | ||
|
|
d3d3fa990e | ||
|
|
21980b7e71 | ||
|
|
844ca0d387 | ||
|
|
972ae35300 | ||
|
|
57bfb8eb96 | ||
|
|
ed6e6a9fb2 | ||
|
|
ed402267b6 | ||
|
|
6eec570828 | ||
|
|
22fc1e3f0b | ||
|
|
ae9bd868f1 | ||
|
|
a887012aca | ||
|
|
bc73048ab9 | ||
|
|
c89dd6c379 | ||
|
|
9662debe5e | ||
|
|
057262d917 | ||
|
|
b6723a6219 | ||
|
|
068f3e0a43 | ||
|
|
95635a8c47 | ||
|
|
3ec2071820 | ||
|
|
1696db3044 | ||
|
|
e1a1eab2b3 | ||
|
|
f7865f3358 | ||
|
|
6d5f8ed5f3 | ||
|
|
96a737379f | ||
|
|
d73feec013 | ||
|
|
2ccead1da5 | ||
|
|
8885f2717e | ||
|
|
4448ffc777 | ||
|
|
022d10c598 | ||
|
|
8e6b7043bd | ||
|
|
66eaaff598 | ||
|
|
478c6c134f | ||
|
|
b5d333ba6c | ||
|
|
81723d55ac | ||
|
|
fb784ce962 | ||
|
|
5a37380900 | ||
|
|
b6300f3a5c | ||
|
|
a3e8a2d623 | ||
|
|
7b3a4bdc39 | ||
|
|
cc0b5e5e0f | ||
|
|
5c3f7d8f94 | ||
|
|
8c3f8cd450 | ||
|
|
046582711a | ||
|
|
15756ec92d | ||
|
|
fc49abc9fb | ||
|
|
4a9ff27f3e | ||
|
|
790e6f370f | ||
|
|
16ccc1321d | ||
|
|
8648c94dd4 | ||
|
|
dc4eb720ae | ||
|
|
0b891ad557 | ||
|
|
e96193ae28 | ||
|
|
3ff9075959 | ||
|
|
c03842056c | ||
|
|
6df226b21c | ||
|
|
7dfa7d7426 | ||
|
|
b8b1a891cf | ||
|
|
7df0e8b0f9 | ||
|
|
ff072ae9d9 | ||
|
|
f81ca39741 | ||
|
|
3db1f2a98c | ||
|
|
4865df9be1 | ||
|
|
0c16f2c334 | ||
|
|
d01149620f | ||
|
|
ab9401f390 | ||
|
|
3223c17b74 | ||
|
|
404035bcf0 | ||
|
|
a0185bb0b4 | ||
|
|
1a591cd9f1 | ||
|
|
e9b81b2033 | ||
|
|
cbfc1e8ed1 | ||
|
|
cb63338805 | ||
|
|
bcdc82ccee | ||
|
|
76a4cf6c34 | ||
|
|
872f23b0f0 | ||
|
|
e61f7405fd | ||
|
|
0714871b56 | ||
|
|
8a89fb2a1a | ||
|
|
036544e3ed | ||
|
|
7a6784d809 | ||
|
|
ed9301705b | ||
|
|
21f9694574 | ||
|
|
3a0b11b89d | ||
|
|
d5272e3e74 | ||
|
|
b5197869db | ||
|
|
5f775db40a | ||
|
|
9b84b83627 | ||
|
|
b0d9b5762a | ||
|
|
8d9fd402be | ||
|
|
73a44a4f8e | ||
|
|
a7dd62249e | ||
|
|
42fac871fb | ||
|
|
2782bf6735 | ||
|
|
f2a1ce4977 | ||
|
|
c8969c4cc0 | ||
|
|
cfefb4a07c | ||
|
|
653509368b | ||
|
|
51b269f329 | ||
|
|
c92d2cc32e | ||
|
|
cb4091895a | ||
|
|
b96bc5a710 | ||
|
|
4ace825c58 | ||
|
|
589e0f7bb5 | ||
|
|
347b9e1d7a | ||
|
|
363633b01f | ||
|
|
575350842a | ||
|
|
d49e8d172f | ||
|
|
642d153202 | ||
|
|
8cf7d8b4cb | ||
|
|
b0b7cfa3a5 | ||
|
|
3692d5f008 | ||
|
|
2344dc04a5 | ||
|
|
cf37072bed | ||
|
|
cc5c933e7d | ||
|
|
ad1737f16b | ||
|
|
2ac1895a6e | ||
|
|
4dc7630adc | ||
|
|
66a3d68755 | ||
|
|
ce9213cdc1 | ||
|
|
99b1ed51a6 | ||
|
|
c7dcff56c7 | ||
|
|
fcdb8f08b8 | ||
|
|
daa03999b6 | ||
|
|
cd7c5dc048 | ||
|
|
09f0f85b9c | ||
|
|
8aee7563f0 | ||
|
|
6d6c8cecd6 | ||
|
|
334f6c8757 | ||
|
|
8455343fee | ||
|
|
d234ab016f | ||
|
|
a312b46e97 | ||
|
|
ff06c7997b | ||
|
|
3dc6d84d1f | ||
|
|
ef74483ebb | ||
|
|
d8cf7bcbf8 | ||
|
|
33336a7ad2 | ||
|
|
79865b39d1 | ||
|
|
375125e992 | ||
|
|
ebc5a992dc | ||
|
|
da01184fc9 | ||
|
|
e0a6a6c558 | ||
|
|
93ec81bc57 | ||
|
|
29295fa8a7 | ||
|
|
74e7130bee | ||
|
|
30cd83662a | ||
|
|
5f8f3ca328 | ||
|
|
5744f1c7ee | ||
|
|
ba05f03359 | ||
|
|
43c9ed0655 | ||
|
|
43fa8f53d3 | ||
|
|
258dad0389 | ||
|
|
5d5eaa2065 | ||
|
|
1329136792 | ||
|
|
c6cbcff9ce | ||
|
|
ed2e637596 | ||
|
|
c97aecf9ff | ||
|
|
9672088158 | ||
|
|
995908d3c6 | ||
|
|
74314457ba | ||
|
|
d64c99ed28 | ||
|
|
38bf13e9bf | ||
|
|
4101c1efd0 | ||
|
|
f095700485 | ||
|
|
4ff1c95083 | ||
|
|
c3e14933e2 | ||
|
|
1b3cf52a17 | ||
|
|
e2f8163b21 | ||
|
|
54d3e1a5e1 | ||
|
|
57e088f6e1 | ||
|
|
c759dcd7df | ||
|
|
033eb41a1d | ||
|
|
77f6e138ac | ||
|
|
c5c70b0f49 | ||
|
|
6b37e026fd | ||
|
|
731018082b | ||
|
|
a7eab9f00e | ||
|
|
0d3aebc077 | ||
|
|
fb37e3254d | ||
|
|
6d9da1260a | ||
|
|
0d749f333a | ||
|
|
338463057c | ||
|
|
35c131f661 | ||
|
|
da6771eaae | ||
|
|
fbf3623343 | ||
|
|
2d43817b2f | ||
|
|
01905c51dd | ||
|
|
84494b8a0a | ||
|
|
aded289558 | ||
|
|
f1462fa0d2 | ||
|
|
5cfd546b2a | ||
|
|
3b38792481 | ||
|
|
31b347e8dd | ||
|
|
d87149ab5d | ||
|
|
fd2f4789d3 | ||
|
|
0d8d35743d | ||
|
|
b5a65040b3 | ||
|
|
d44289f036 | ||
|
|
cb4b45aff1 | ||
|
|
0f4851e77d | ||
|
|
42fe86e024 | ||
|
|
3bb0ebb98f | ||
|
|
391310faa5 | ||
|
|
ab0552f409 | ||
|
|
8a6f59e350 | ||
|
|
8e859e53c2 | ||
|
|
a44e475451 | ||
|
|
f958839af1 | ||
|
|
f741851250 | ||
|
|
acd68b5de8 | ||
|
|
466ea7277f | ||
|
|
c80072a62e | ||
|
|
bc0a37e8da | ||
|
|
a323f40da2 | ||
|
|
ee05e6ba03 | ||
|
|
ae9ef5c13f | ||
|
|
fcc90df31c | ||
|
|
d6b6719b54 | ||
|
|
21e8af827f | ||
|
|
5e5d233d83 | ||
|
|
214fd92b12 | ||
|
|
3687790730 | ||
|
|
d0f86078aa | ||
|
|
649cb69466 | ||
|
|
2f342e7664 | ||
|
|
e4fccc2f84 | ||
|
|
61bd2d356b | ||
|
|
66ddb6ce0a | ||
|
|
573b3b1dfd | ||
|
|
560bd3ca85 | ||
|
|
1f5fe5b570 | ||
|
|
d18d2df417 | ||
|
|
91731a86bf | ||
|
|
7108761e8f | ||
|
|
0fe8bacf73 | ||
|
|
ef1afb482f | ||
|
|
134a3de256 | ||
|
|
71975546cb | ||
|
|
b4c02f42f7 | ||
|
|
da7210f43f | ||
|
|
8b4d62e374 | ||
|
|
b68da2c3d3 | ||
|
|
b2db41c7f4 | ||
|
|
c4922d1655 | ||
|
|
94ca77e697 | ||
|
|
c1d076ef79 | ||
|
|
39c69a4aff | ||
|
|
5a0921ed74 | ||
|
|
68c668615f | ||
|
|
e1d5876ae6 | ||
|
|
741910407f | ||
|
|
39d4befc54 | ||
|
|
d13505ce91 | ||
|
|
8c6d586fb0 | ||
|
|
f66c2967dd | ||
|
|
ef44a2295f | ||
|
|
6186231041 | ||
|
|
25ec5550b5 | ||
|
|
1f93610a95 | ||
|
|
01cde51a71 | ||
|
|
7d054c11de | ||
|
|
98f717d5b4 | ||
|
|
6f315144cc | ||
|
|
9ba8f569db | ||
|
|
51f169571f | ||
|
|
b6a9ffb4c7 | ||
|
|
346612aac1 | ||
|
|
205144d92d | ||
|
|
af2e321f45 | ||
|
|
e22a20701b | ||
|
|
f3edde8f81 | ||
|
|
f405a0c905 | ||
|
|
4907df497f | ||
|
|
e551c42f32 | ||
|
|
0c1a235cc1 | ||
|
|
5384b3a8c0 | ||
|
|
0acb5f5857 | ||
|
|
cee124a4d6 | ||
|
|
084b983b44 | ||
|
|
bf15948275 | ||
|
|
8796875128 | ||
|
|
af9c2b1449 | ||
|
|
f0d6b6f558 | ||
|
|
3778e01d7c | ||
|
|
70ebad0410 | ||
|
|
7cf58bd864 | ||
|
|
5858483fca | ||
|
|
fb94d2a99c | ||
|
|
55b5441f00 | ||
|
|
bf397e496c | ||
|
|
a78ae8ca4a | ||
|
|
c635b92991 | ||
|
|
53d406f640 | ||
|
|
701f4b743b | ||
|
|
7466baf1b2 | ||
|
|
13ecf8ef21 | ||
|
|
c946a3973a | ||
|
|
615f265c00 | ||
|
|
4177ba7840 | ||
|
|
393593b2d2 | ||
|
|
e736c3949a | ||
|
|
e1509e7db3 | ||
|
|
49150b07fd | ||
|
|
1d8f1b4c6a | ||
|
|
833f0c67cf | ||
|
|
4b559b5a94 | ||
|
|
55c8d60cfb | ||
|
|
0e129379e9 | ||
|
|
7e3f704285 | ||
|
|
d8a595ac70 | ||
|
|
c13cb9b723 | ||
|
|
8cc83855b9 | ||
|
|
faebbf5753 | ||
|
|
3e297bad1f | ||
|
|
f56949dd0b | ||
|
|
04081ec2d3 | ||
|
|
442f3d93c6 | ||
|
|
b41d9bff51 | ||
|
|
7e1cd68cb4 | ||
|
|
47675633d2 | ||
|
|
8e59834989 | ||
|
|
a87c3cdb88 | ||
|
|
b2f9903e18 | ||
|
|
a48c8056f2 | ||
|
|
dfd6b85296 | ||
|
|
f3aed1bd53 | ||
|
|
41716d160b | ||
|
|
bd87be2f7e | ||
|
|
9bd4ad6e47 | ||
|
|
9bd0c01bdd | ||
|
|
7dc6b4035a | ||
|
|
3a65b9e0e5 | ||
|
|
569b98c724 | ||
|
|
3de022ba05 | ||
|
|
b697718826 | ||
|
|
a16f7b34ab | ||
|
|
0660ddbfbf | ||
|
|
f1a4bf4dd7 | ||
|
|
06c319d1aa | ||
|
|
2d89749c9b | ||
|
|
696bee13af | ||
|
|
c5b835186c | ||
|
|
ea3c1dacea | ||
|
|
7de29090db | ||
|
|
d982d15fbc | ||
|
|
4455810b5b | ||
|
|
00a8372a74 | ||
|
|
108dd4ff24 | ||
|
|
8a687851f2 | ||
|
|
52394d61bf | ||
|
|
270d97f3db | ||
|
|
c5b7e549ec | ||
|
|
891a93a7a3 | ||
|
|
13c7800c8c | ||
|
|
e89f5de680 | ||
|
|
c4fdd04fb4 | ||
|
|
500501497f | ||
|
|
4106161753 | ||
|
|
8ce5a23c4b | ||
|
|
5c524af472 | ||
|
|
4b1123c4e4 | ||
|
|
c3268d0362 | ||
|
|
f3fa9883aa | ||
|
|
8cf7ec9738 | ||
|
|
7c3a92662f | ||
|
|
e05358db1d | ||
|
|
74d450703c | ||
|
|
aee4f3780c | ||
|
|
3aa0702ff8 | ||
|
|
cc51dbd4e6 | ||
|
|
228a83e0a7 | ||
|
|
ee1ed1e9e5 | ||
|
|
6a394cc30e |
16
.Rbuildignore
Normal file
16
.Rbuildignore
Normal file
@@ -0,0 +1,16 @@
|
||||
^\.Rproj\.user$
|
||||
^\.git$
|
||||
^examples$
|
||||
^shiny\.Rproj$
|
||||
^shiny\.sh$
|
||||
^shiny\.cmd$
|
||||
^run\.R$
|
||||
^\.gitignore$
|
||||
^res$
|
||||
^man-roxygen$
|
||||
^\.travis\.yml$
|
||||
^staticdocs$
|
||||
^tools$
|
||||
^srcjs$
|
||||
^CONTRIBUTING.md$
|
||||
^cran-comments.md$
|
||||
2
.Rinstignore
Normal file
2
.Rinstignore
Normal file
@@ -0,0 +1,2 @@
|
||||
^tools$
|
||||
^Rmd$
|
||||
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/NEWS merge=union
|
||||
/inst/www/shared/shiny.js -merge -diff
|
||||
*.min.js -merge -diff
|
||||
*.js.map -merge -diff
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,4 +1,10 @@
|
||||
vendor/ruby
|
||||
\.bundle/
|
||||
\.DS_Store
|
||||
.DS_Store
|
||||
.Rproj.user
|
||||
.Rhistory
|
||||
.Rprofile
|
||||
*.o
|
||||
*.so
|
||||
/src-i386/
|
||||
/src-x86_64/
|
||||
shinyapps/
|
||||
README.html
|
||||
|
||||
12
.travis.yml
Normal file
12
.travis.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
language: r
|
||||
warnings_are_errors: true
|
||||
|
||||
r_binary_packages:
|
||||
- Rcpp
|
||||
- cairo
|
||||
- knitr
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: change
|
||||
on_failure: change
|
||||
10
CONTRIBUTING.md
Normal file
10
CONTRIBUTING.md
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
We welcome contributions to the **shiny** package. To submit a contribution:
|
||||
|
||||
1. [Fork](https://github.com/rstudio/shiny/fork) the repository and make your changes.
|
||||
|
||||
2. Ensure that you have signed the [individual](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioIndividualContributorAgreement.pdf) or [corporate](http://www.rstudio.com/wp-content/uploads/2014/06/RStudioCorporateContributorAgreement.pdf) contributor agreement as appropriate. You can send the signed copy to jj@rstudio.com.
|
||||
|
||||
3. Submit a [pull request](https://help.github.com/articles/using-pull-requests).
|
||||
|
||||
We'll try to be as responsive as possible in reviewing and accepting pull requests. We appreciate your contributions!
|
||||
116
DESCRIPTION
Normal file
116
DESCRIPTION
Normal file
@@ -0,0 +1,116 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Title: Web Application Framework for R
|
||||
Version: 0.12.1
|
||||
Date: 2015-06-11
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@rstudio.com"),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@rstudio.com"),
|
||||
person("JJ", "Allaire", role = "aut", email = "jj@rstudio.com"),
|
||||
person("Yihui", "Xie", role = "aut", email = "yihui@rstudio.com"),
|
||||
person("Jonathan", "McPherson", role = "aut", email = "jonathan@rstudio.com"),
|
||||
person(family = "RStudio", role = "cph"),
|
||||
person(family = "jQuery Foundation", role = "cph",
|
||||
comment = "jQuery library and jQuery UI library"),
|
||||
person(family = "jQuery contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt"),
|
||||
person(family = "jQuery UI contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery UI library; authors listed in inst/www/shared/jqueryui/1.10.4/AUTHORS.txt"),
|
||||
person("Mark", "Otto", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person("Jacob", "Thornton", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person(family = "Bootstrap contributors", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person(family = "Twitter, Inc", role = "cph",
|
||||
comment = "Bootstrap library"),
|
||||
person("Alexander", "Farkas", role = c("ctb", "cph"),
|
||||
comment = "html5shiv library"),
|
||||
person("Scott", "Jehl", role = c("ctb", "cph"),
|
||||
comment = "Respond.js library"),
|
||||
person("Stefan", "Petre", role = c("ctb", "cph"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
person("Andrew", "Rowls", role = c("ctb", "cph"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
person("Dave", "Gandy", role = c("ctb", "cph"),
|
||||
comment = "Font-Awesome font"),
|
||||
person("Brian", "Reavis", role = c("ctb", "cph"),
|
||||
comment = "selectize.js library"),
|
||||
person("Kristopher Michael", "Kowal", role = c("ctb", "cph"),
|
||||
comment = "es5-shim library"),
|
||||
person(family = "es5-shim contributors", role = c("ctb", "cph"),
|
||||
comment = "es5-shim library"),
|
||||
person("Denis", "Ineshin", role = c("ctb", "cph"),
|
||||
comment = "ion.rangeSlider library"),
|
||||
person(family = "SpryMedia Limited", role = c("ctb", "cph"),
|
||||
comment = "DataTables library"),
|
||||
person("John", "Fraser", role = c("ctb", "cph"),
|
||||
comment = "showdown.js library"),
|
||||
person("John", "Gruber", role = c("ctb", "cph"),
|
||||
comment = "showdown.js library"),
|
||||
person("Ivan", "Sagalaev", role = c("ctb", "cph"),
|
||||
comment = "highlight.js library"),
|
||||
person(family = "R Core Team", role = c("ctb", "cph"),
|
||||
comment = "tar implementation from R")
|
||||
)
|
||||
Description: Makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive pre-built widgets make it possible to build
|
||||
beautiful, responsive, and powerful applications with minimal effort.
|
||||
License: GPL-3 | file LICENSE
|
||||
Depends:
|
||||
R (>= 3.0.0)
|
||||
Imports:
|
||||
utils,
|
||||
httpuv (>= 1.3.2),
|
||||
mime (>= 0.3),
|
||||
jsonlite (>= 0.9.16),
|
||||
xtable,
|
||||
digest,
|
||||
htmltools (>= 0.2.6),
|
||||
R6 (>= 2.0)
|
||||
Suggests:
|
||||
datasets,
|
||||
Cairo (>= 1.5-5),
|
||||
testthat,
|
||||
knitr (>= 1.6),
|
||||
markdown,
|
||||
ggplot2
|
||||
URL: http://shiny.rstudio.com
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Collate:
|
||||
'app.R'
|
||||
'bootstrap-layout.R'
|
||||
'map.R'
|
||||
'globals.R'
|
||||
'utils.R'
|
||||
'bootstrap.R'
|
||||
'cache.R'
|
||||
'fileupload.R'
|
||||
'stack.R'
|
||||
'graph.R'
|
||||
'hooks.R'
|
||||
'html-deps.R'
|
||||
'htmltools.R'
|
||||
'image-interact-opts.R'
|
||||
'image-interact.R'
|
||||
'imageutils.R'
|
||||
'jqueryui.R'
|
||||
'middleware-shiny.R'
|
||||
'middleware.R'
|
||||
'priorityqueue.R'
|
||||
'progress.R'
|
||||
'react.R'
|
||||
'reactive-domains.R'
|
||||
'reactives.R'
|
||||
'render-plot.R'
|
||||
'run-url.R'
|
||||
'server.R'
|
||||
'shiny.R'
|
||||
'shinyui.R'
|
||||
'shinywrappers.R'
|
||||
'showcase.R'
|
||||
'slider.R'
|
||||
'tar.R'
|
||||
'timer.R'
|
||||
'update-input.R'
|
||||
5
Gemfile
5
Gemfile
@@ -1,5 +0,0 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'em-websocket'
|
||||
gem 'eventmachine_httpserver'
|
||||
gem 'json'
|
||||
18
Gemfile.lock
18
Gemfile.lock
@@ -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
|
||||
204
NAMESPACE
Normal file
204
NAMESPACE
Normal file
@@ -0,0 +1,204 @@
|
||||
# Generated by roxygen2 (4.1.1): do not edit by hand
|
||||
|
||||
S3method("$",reactivevalues)
|
||||
S3method("$",shinyoutput)
|
||||
S3method("$<-",reactivevalues)
|
||||
S3method("$<-",shinyoutput)
|
||||
S3method("[",reactivevalues)
|
||||
S3method("[",shinyoutput)
|
||||
S3method("[<-",reactivevalues)
|
||||
S3method("[<-",shinyoutput)
|
||||
S3method("[[",reactivevalues)
|
||||
S3method("[[",shinyoutput)
|
||||
S3method("[[<-",reactivevalues)
|
||||
S3method("[[<-",shinyoutput)
|
||||
S3method("names<-",reactivevalues)
|
||||
S3method(as.list,reactivevalues)
|
||||
S3method(as.shiny.appobj,character)
|
||||
S3method(as.shiny.appobj,list)
|
||||
S3method(as.shiny.appobj,shiny.appobj)
|
||||
S3method(as.tags,shiny.appobj)
|
||||
S3method(as.tags,shiny.render.function)
|
||||
S3method(names,reactivevalues)
|
||||
S3method(print,reactive)
|
||||
S3method(print,shiny.appobj)
|
||||
S3method(str,reactivevalues)
|
||||
export(HTML)
|
||||
export(Progress)
|
||||
export(a)
|
||||
export(absolutePanel)
|
||||
export(actionButton)
|
||||
export(actionLink)
|
||||
export(addResourcePath)
|
||||
export(animationOptions)
|
||||
export(as.shiny.appobj)
|
||||
export(basicPage)
|
||||
export(bootstrapPage)
|
||||
export(br)
|
||||
export(brushOpts)
|
||||
export(brushedPoints)
|
||||
export(checkboxGroupInput)
|
||||
export(checkboxInput)
|
||||
export(clickOpts)
|
||||
export(code)
|
||||
export(column)
|
||||
export(conditionalPanel)
|
||||
export(createWebDependency)
|
||||
export(dataTableOutput)
|
||||
export(dateInput)
|
||||
export(dateRangeInput)
|
||||
export(dblclickOpts)
|
||||
export(div)
|
||||
export(downloadButton)
|
||||
export(downloadHandler)
|
||||
export(downloadLink)
|
||||
export(em)
|
||||
export(eventReactive)
|
||||
export(exprToFunction)
|
||||
export(fileInput)
|
||||
export(fixedPage)
|
||||
export(fixedPanel)
|
||||
export(fixedRow)
|
||||
export(flowLayout)
|
||||
export(fluidPage)
|
||||
export(fluidRow)
|
||||
export(getDefaultReactiveDomain)
|
||||
export(h1)
|
||||
export(h2)
|
||||
export(h3)
|
||||
export(h4)
|
||||
export(h5)
|
||||
export(h6)
|
||||
export(headerPanel)
|
||||
export(helpText)
|
||||
export(hoverOpts)
|
||||
export(hr)
|
||||
export(htmlOutput)
|
||||
export(icon)
|
||||
export(imageOutput)
|
||||
export(img)
|
||||
export(incProgress)
|
||||
export(includeCSS)
|
||||
export(includeHTML)
|
||||
export(includeMarkdown)
|
||||
export(includeScript)
|
||||
export(includeText)
|
||||
export(inputPanel)
|
||||
export(installExprFunction)
|
||||
export(invalidateLater)
|
||||
export(is.reactive)
|
||||
export(is.reactivevalues)
|
||||
export(is.shiny.appobj)
|
||||
export(is.singleton)
|
||||
export(isolate)
|
||||
export(knit_print.html)
|
||||
export(knit_print.shiny.appobj)
|
||||
export(knit_print.shiny.render.function)
|
||||
export(knit_print.shiny.tag)
|
||||
export(knit_print.shiny.tag.list)
|
||||
export(mainPanel)
|
||||
export(makeReactiveBinding)
|
||||
export(markRenderFunction)
|
||||
export(maskReactiveContext)
|
||||
export(navbarMenu)
|
||||
export(navbarPage)
|
||||
export(navlistPanel)
|
||||
export(nearPoints)
|
||||
export(need)
|
||||
export(numericInput)
|
||||
export(observe)
|
||||
export(observeEvent)
|
||||
export(onReactiveDomainEnded)
|
||||
export(outputOptions)
|
||||
export(p)
|
||||
export(pageWithSidebar)
|
||||
export(parseQueryString)
|
||||
export(passwordInput)
|
||||
export(plotOutput)
|
||||
export(plotPNG)
|
||||
export(pre)
|
||||
export(radioButtons)
|
||||
export(reactive)
|
||||
export(reactiveFileReader)
|
||||
export(reactivePlot)
|
||||
export(reactivePoll)
|
||||
export(reactivePrint)
|
||||
export(reactiveTable)
|
||||
export(reactiveText)
|
||||
export(reactiveTimer)
|
||||
export(reactiveUI)
|
||||
export(reactiveValues)
|
||||
export(reactiveValuesToList)
|
||||
export(registerInputHandler)
|
||||
export(removeInputHandler)
|
||||
export(renderDataTable)
|
||||
export(renderImage)
|
||||
export(renderPlot)
|
||||
export(renderPrint)
|
||||
export(renderTable)
|
||||
export(renderText)
|
||||
export(renderUI)
|
||||
export(repeatable)
|
||||
export(runApp)
|
||||
export(runExample)
|
||||
export(runGist)
|
||||
export(runGitHub)
|
||||
export(runUrl)
|
||||
export(selectInput)
|
||||
export(selectizeInput)
|
||||
export(serverInfo)
|
||||
export(setProgress)
|
||||
export(shinyApp)
|
||||
export(shinyAppDir)
|
||||
export(shinyServer)
|
||||
export(shinyUI)
|
||||
export(showReactLog)
|
||||
export(sidebarLayout)
|
||||
export(sidebarPanel)
|
||||
export(singleton)
|
||||
export(sliderInput)
|
||||
export(span)
|
||||
export(splitLayout)
|
||||
export(stopApp)
|
||||
export(strong)
|
||||
export(submitButton)
|
||||
export(tabPanel)
|
||||
export(tableOutput)
|
||||
export(tabsetPanel)
|
||||
export(tag)
|
||||
export(tagAppendAttributes)
|
||||
export(tagAppendChild)
|
||||
export(tagAppendChildren)
|
||||
export(tagList)
|
||||
export(tagSetChildren)
|
||||
export(tags)
|
||||
export(textInput)
|
||||
export(textOutput)
|
||||
export(titlePanel)
|
||||
export(uiOutput)
|
||||
export(updateCheckboxGroupInput)
|
||||
export(updateCheckboxInput)
|
||||
export(updateDateInput)
|
||||
export(updateDateRangeInput)
|
||||
export(updateNumericInput)
|
||||
export(updateRadioButtons)
|
||||
export(updateSelectInput)
|
||||
export(updateSelectizeInput)
|
||||
export(updateSliderInput)
|
||||
export(updateTabsetPanel)
|
||||
export(updateTextInput)
|
||||
export(validate)
|
||||
export(validateCssUnit)
|
||||
export(verbatimTextOutput)
|
||||
export(verticalLayout)
|
||||
export(wellPanel)
|
||||
export(withMathJax)
|
||||
export(withProgress)
|
||||
export(withReactiveDomain)
|
||||
export(withTags)
|
||||
import(R6)
|
||||
import(digest)
|
||||
import(htmltools)
|
||||
import(httpuv)
|
||||
import(mime)
|
||||
import(xtable)
|
||||
839
NEWS
Normal file
839
NEWS
Normal file
@@ -0,0 +1,839 @@
|
||||
shiny 0.12.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fixed an issue where unbindAll() causes subsequent bindAll() to be ignored for
|
||||
previously bound outputs. (#856)
|
||||
|
||||
* Undeprecate `dataTableOutput` and `renderDataTable`, which had been deprecated
|
||||
in favor of the new DT package. The DT package is a bit too new and has a
|
||||
slightly different API, we were too hasty in deprecating the existing Shiny
|
||||
functions.
|
||||
|
||||
shiny 0.12.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Switched from RJSONIO to jsonlite. This improves consistency and speed when
|
||||
converting between R data structures and JSON. One notable change is that
|
||||
POSIXt objects are now serialized to JSON in UTC8601 format (like
|
||||
"2015-03-20T20:00:00Z"), instead of as seconds from the epoch).
|
||||
|
||||
* In addition to the existing support for clicking and hovering on plots
|
||||
created by base graphics, added support for double-clicking and brushing.
|
||||
(#769)
|
||||
|
||||
* Added support for clicking, hovering, double-clicking, and brushing for
|
||||
plots created by ggplot2, including support for facets. (#802)
|
||||
|
||||
* Added `nearPoints` and `brushedPoints` functions for easily selecting rows of
|
||||
data that are clicked/hovered, or brushed. (#802)
|
||||
|
||||
* Added `shiny.port` option. If this is option is set, `runApp()` will listen on
|
||||
this port by default. (#756)
|
||||
|
||||
* `runUrl`, `runGist`, and `runGitHub` now can save downloaded applications,
|
||||
with the `destdir` argument. (#688)
|
||||
|
||||
* Restored ability to set labels for `selectInput`. (#741)
|
||||
|
||||
* Travis continuous integration now uses Travis's native R support.
|
||||
|
||||
* Fixed encoding issue when the server receives data from the client browser.
|
||||
(#742)
|
||||
|
||||
* The `session` object now has class `ShinySession`, making it easier to test
|
||||
whether an object is indeed a session object. (#720, #746)
|
||||
|
||||
* Fix JavaScript error when an output appears in nested uiOutputs. (Thanks,
|
||||
Gregory Zhang. #749)
|
||||
|
||||
* Eliminate delay on receiving new value when `updateSliderInput(value=...)` is
|
||||
called.
|
||||
|
||||
* Updated to DataTables (Javascript library) 1.10.5.
|
||||
|
||||
* Fixed downloading of files that have no filename extension. (#575, #753)
|
||||
|
||||
* Fixed bug where nested UI outputs broke outputs. (#749, #750)
|
||||
|
||||
* Removed unneeded HTML ID attributes for `checkboxGroupInputs` and
|
||||
`radioButtons`. (#684)
|
||||
|
||||
* Fixed bug where checkboxes were still active even after `Shiny.unbindAll()`
|
||||
was called. (#206)
|
||||
|
||||
* The server side selectize input will load the first 1000 options by default
|
||||
before users start to type and search in the box. (#823)
|
||||
|
||||
* renderDataTable() and dataTableOutput() have been deprecated in shiny and will
|
||||
be removed in future versions of shiny. Please use the DT package instead:
|
||||
http://rstudio.github.io/DT/ (#807)
|
||||
|
||||
shiny 0.11.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Major client-side performance improvements for pages that have many
|
||||
conditionalPanels, tabPanels, and plotOutputs. (#693, #717, #723)
|
||||
|
||||
* `tabPanel`s now use the `title` for `value` by default. This fixes a bug
|
||||
in which an icon in the title caused problems with a conditionalPanel's test
|
||||
condition. (#725, #728)
|
||||
|
||||
* `selectInput` now has a `size` argument to control the height of the input
|
||||
box. (#729)
|
||||
|
||||
* `navbarPage` no longer includes a first row of extra whitespace when
|
||||
`header=NULL`. (#722)
|
||||
|
||||
* `selectInput`s now use Bootstrap styling when `selectize=FALSE`. (#724)
|
||||
|
||||
* Better vertical spacing of label for checkbox groups and radio buttons.
|
||||
|
||||
* `selectInput` correctly uses width for both selectize and non-selectize
|
||||
inputs. (#702)
|
||||
|
||||
* The wrapper tag generated by `htmlOutput` and `uiOutput` can now be any type
|
||||
of HTML tag, instead of just span and div. Also, custom classes are now
|
||||
allowed on the tag. (#704)
|
||||
|
||||
* Slider problems in IE 11 and Chrome on touchscreen-equipped Windows computers
|
||||
have been fixed. (#700)
|
||||
|
||||
* Sliders now work correctly with draggable panels. (#711)
|
||||
|
||||
* Fixed arguments in `fixedPanel`. (#709)
|
||||
|
||||
* downloadHandler content callback functions are now invoked with a temp file
|
||||
name that has the same extension as the final filename that will be used by
|
||||
the download. This is to deal with the fact that some file writing functions
|
||||
in R will auto-append the extension for their file type (pdf, zip).
|
||||
|
||||
shiny 0.11
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Changed sliders from jquery-slider to ion.rangeSlider. These sliders have
|
||||
an improved appearance, support updating more properties from the server,
|
||||
and can be controlled with keyboard input.
|
||||
|
||||
* Switched from Bootstrap 2 to Bootstrap 3. For most users, this will work
|
||||
seamlessly, but some users may need to use the shinybootstrap2 package for
|
||||
backward compatibility.
|
||||
|
||||
* The UI of a Shiny app can now have a body tag. This is useful for CSS
|
||||
templates that use classes on the body tag.
|
||||
|
||||
* `actionButton` and `actionLink` now pass their `...` arguments to the
|
||||
underlying tag function. (#607)
|
||||
|
||||
* Added `observeEvent` and `eventReactive` functions for clearer, more concise
|
||||
handling of `actionButton`, plot clicks, and other naturally-imperative
|
||||
inputs.
|
||||
|
||||
* Errors that happen in reactives no longer prevent any remaining pending
|
||||
observers from executing. It is also now possible for users to control how
|
||||
errors are handled, with the 'shiny.observer.error' global option. (#603,
|
||||
#604)
|
||||
|
||||
* Added an `escape` argument to `renderDataTable()` to escape the HTML entities
|
||||
in the data table for security reasons. This might break tables from previous
|
||||
versions of shiny that use raw HTML in the table content, and the old behavior
|
||||
can be brought back by `escape = FALSE` if you are aware of the security
|
||||
implications. (#627)
|
||||
|
||||
* Changed the URI encoding/decoding functions internally to use `encodeURI()`,
|
||||
`encodeURIComponent()`, and `decodeURIComponent()` from the httpuv package
|
||||
instead of `utils::URLencode()` and `utils::URLdecode()`. (#630)
|
||||
|
||||
* Shiny's web assets are now minified.
|
||||
|
||||
* The default reactive domain is now available in event handler functions. (#669)
|
||||
|
||||
* Password input fields can now be used, with `passwordInput()`. (#672)
|
||||
|
||||
shiny 0.10.2.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Remove use of `rstudio::viewer` in a code example, for R CMD check.
|
||||
|
||||
shiny 0.10.2.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Changed some examples to use \donttest instead of \dontrun.
|
||||
|
||||
shiny 0.10.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* The minimal version of R required for the shiny package is 3.0.0 now.
|
||||
|
||||
* Shiny apps can now consist of a single file, app.R, instead of ui.R and
|
||||
server.R.
|
||||
|
||||
* Upgraded DataTables from 1.9.4 to 1.10.2. This might be a breaking change if
|
||||
you have customized the DataTables options in your apps. (More info:
|
||||
https://github.com/rstudio/shiny/pull/558)
|
||||
|
||||
* File uploading via `fileInput()` works for Internet Explorer 8 and 9 now. Note
|
||||
IE8/9 do not support multiple files from a single file input. If you need to
|
||||
upload multiple files, you have to use one file input for each file.
|
||||
|
||||
* Switched away from reference classes to R6.
|
||||
|
||||
* Reactive log performance has been greatly improved.
|
||||
|
||||
* Added `Progress` and `withProgress`, to display the progress of computation
|
||||
on the client browser.
|
||||
|
||||
* Fixed #557: updateSelectizeInput(choices, server = TRUE) did not work when
|
||||
`choices` is a character vector.
|
||||
|
||||
* Searching in DataTables is case-insensitive and the search strings are not
|
||||
treated as regular expressions by default now. If you want case-sensitive
|
||||
searching or regular expressions, you can use the configuration options
|
||||
`search$caseInsensitive` and `search$regex`, e.g. `renderDataTable(...,
|
||||
options = list(search = list(caseInsensitve = FALSE, regex = TRUE)))`.
|
||||
|
||||
* Added support for `htmltools::htmlDependency`'s new `attachment` parameter to
|
||||
`renderUI`/`uiOutput`.
|
||||
|
||||
* Exported `createWebDependency`. It takes an `htmltools::htmlDependency` object
|
||||
and makes it available over Shiny's built-in web server.
|
||||
|
||||
* Custom output bindings can now render `htmltools::htmlDependency` objects at
|
||||
runtime using `Shiny.renderDependencies()`.
|
||||
|
||||
* Fixes to rounding behavior of sliderInput. (#301, #502)
|
||||
|
||||
* Updated selectize.js to version 0.11.2. (#596)
|
||||
* Added `position` parameter to `navbarPage`.
|
||||
|
||||
shiny 0.10.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Added Unicode support for Windows. Shiny apps running on Windows must use the
|
||||
UTF-8 encoding for ui.R and server.R (also the optional global.R) if they
|
||||
contain non-ASCII characters. See this article for details and examples:
|
||||
http://shiny.rstudio.com/gallery/unicode-characters.html (#516)
|
||||
|
||||
* `runGitHub()` also allows the 'username/repo' syntax now, which is equivalent
|
||||
to `runGitHub('repo', 'username')`. (#427)
|
||||
|
||||
* `navbarPage()` now accepts a `windowTitle` parameter to set the web browser
|
||||
page title to something other than the title displayed in the navbar.
|
||||
|
||||
* Added an `inline` argument to `textOutput()`, `imageOutput()`, `plotOutput()`,
|
||||
and `htmlOutput()`. When `inline = TRUE`, these outputs will be put in
|
||||
`span()` instead of the default `div()`. This occurs automatically when these
|
||||
outputs are created via the inline expressions (e.g. `r renderText(expr)`) in
|
||||
R Markdown documents. See an R Markdown example at
|
||||
http://shiny.rstudio.com/gallery/inline-output.html (#512)
|
||||
|
||||
* Added support for option groups in the select/selectize inputs. When the
|
||||
`choices` argument for `selectInput()`/`selectizeInput()` is a list of
|
||||
sub-lists and any sub-list is of length greater than 1, the HTML tag
|
||||
`<optgroup>` will be used. See an example at
|
||||
http://shiny.rstudio.com/gallery/option-groups-for-selectize-input.html (#542)
|
||||
|
||||
shiny 0.10.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* BREAKING CHANGE: By default, observers now terminate themselves if they were
|
||||
created during a session and that session ends. See ?domains for more details.
|
||||
|
||||
* Shiny can now be used in R Markdown v2 documents, to create "Shiny Docs":
|
||||
reports and presentations that combine narrative, statically computed output,
|
||||
and fully dynamic inputs and outputs. For more info, including examples, see
|
||||
http://rmarkdown.rstudio.com/authoring_shiny.html.
|
||||
|
||||
* The `session` object that can be passed into a server function (e.g.
|
||||
shinyServer(function(input, output, session) {...})) is now documented: see
|
||||
`?session`.
|
||||
|
||||
* Most inputs can now accept `NULL` label values to omit the label altogether.
|
||||
|
||||
* New `actionLink` input control; like `actionButton`, but with the appearance
|
||||
of a normal link.
|
||||
|
||||
* `renderPlot` now calls `print` on its result if it's visible (i.e. no more
|
||||
explicit `print()` required for ggplot2).
|
||||
|
||||
* Introduced Shiny app objects (see `?shinyApp`). These essentially replace the
|
||||
little-advertised ability for `runApp` to take a `list(ui=..., server=...)`
|
||||
as the first argument instead of a directory (though that ability remains for
|
||||
backward compatibility). Unlike those lists, Shiny app objects are tagged with
|
||||
class `shiny.appobj` so they can be run simply by printing them.
|
||||
|
||||
* Added `maskReactiveContext` function. It blocks the current reactive context,
|
||||
to evaluate expressions that shouldn't use reactive sources directly. (This
|
||||
should not be commonly needed.)
|
||||
|
||||
* Added `flowLayout`, `splitLayout`, and `inputPanel` functions for putting UI
|
||||
elements side by side. `flowLayout` lays out its children in a left-to-right,
|
||||
top-to-bottom arrangement. `splitLayout` evenly divides its horizontal space
|
||||
among its children (or unevenly divides if `cellWidths` argument is provided).
|
||||
`inputPanel` is like `flowPanel`, but with a light grey background, and is
|
||||
intended to be used to encapsulate small input controls wherever vertical
|
||||
space is at a premium.
|
||||
|
||||
* Added `serverInfo` to obtain info about the Shiny Server if the app is served
|
||||
through it.
|
||||
|
||||
* Added an `inline` argument (TRUE/FALSE) in `checkboxGroupInput()` and
|
||||
`radioButtons()` to allow the horizontal layout (inline = TRUE) of checkboxes
|
||||
or radio buttons. (Thanks, @saurfang, #481)
|
||||
|
||||
* `sliderInput` and `selectizeInput`/`selectInput` now use a standard horizontal
|
||||
size instead of filling up all available horizontal space. Pass `width="100%"`
|
||||
explicitly for the old behavior.
|
||||
|
||||
* Added the `updateSelectizeInput()` function to make it possible to process
|
||||
searching on the server side (i.e. using R), which can be much faster than the
|
||||
client side processing (i.e. using HTML and JavaScript). See the article at
|
||||
http://shiny.rstudio.com/articles/selectize.html for a detailed introduction.
|
||||
|
||||
* Fixed a bug of renderDataTable() when the data object only has 1 row and 1
|
||||
column. (Thanks, ZJ Dai, #429)
|
||||
|
||||
* `renderPrint` gained a new argument 'width' to control the width of the text
|
||||
output, e.g. renderPrint({mtcars}, width = 40).
|
||||
|
||||
* Fixed #220: the zip file for a directory created by some programs may not have
|
||||
the directory name as its first entry, in which case runUrl() can fail. (#220)
|
||||
|
||||
* `runGitHub()` can also take a value of the form "username/repo" in its first
|
||||
argument, e.g. both runGitHub("shiny_example", "rstudio") and
|
||||
runGitHub("rstudio/shiny_example") are valid ways to run the GitHub repo.
|
||||
|
||||
shiny 0.9.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fixed warning 'Error in Context$new : could not find function "loadMethod"'
|
||||
that was happening to dependent packages on "R CMD check".
|
||||
|
||||
shiny 0.9.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* BREAKING CHANGE: Added a `host` parameter to runApp() and runExample(),
|
||||
which defaults to the shiny.host option if it is non-NULL, or "127.0.0.1"
|
||||
otherwise. This means that by default, Shiny applications can only be
|
||||
accessed on the same machine from which they are served. To allow other
|
||||
clients to connect, as in previous versions of Shiny, use "0.0.0.0"
|
||||
(or the IP address of one of your network interfaces, if you care to be
|
||||
explicit about it).
|
||||
|
||||
* Added a new function `selectizeInput()` to use the JavaScript library
|
||||
selectize.js (https://github.com/brianreavis/selectize.js), which extends
|
||||
the basic select input in many aspects.
|
||||
|
||||
* The `selectInput()` function also gained a new argument `selectize = TRUE`
|
||||
to makes use of selectize.js by default. If you want to revert back to the
|
||||
original select input, you have to call selectInput(..., selectize = FALSE).
|
||||
|
||||
* Added Showcase mode, which displays the R code for an app right in the app
|
||||
itself. You can invoke Showcase mode by passing `display.mode="showcase"`
|
||||
to the `runApp()` function. Or, if an app is designed to run in Showcase
|
||||
mode by default, add a DESCRIPTION file in the app dir with Title, Author,
|
||||
and License fields; with "Type: Shiny"; and with "DisplayMode: Showcase".
|
||||
|
||||
* Upgraded to Bootstrap 2.3.2 and jQuery 1.11.0.
|
||||
|
||||
* Make `tags$head()` and `singleton()` behave correctly when used with
|
||||
`renderUI()` and `uiOutput()`. Previously, "hoisting content to the head"
|
||||
and "only rendering items a single time" were features that worked only
|
||||
when the page was initially loading, not in dynamic rendering.
|
||||
|
||||
* Files are now sourced with the `keep.source` option, to help with debugging
|
||||
and profiling.
|
||||
|
||||
* Support user-defined input parsers for data coming in from JavaScript using
|
||||
the parseShinyInput method.
|
||||
|
||||
* Fixed the bug #299: renderDataTable() can deal with 0-row data frames now.
|
||||
(reported by Harlan Harris)
|
||||
|
||||
* Added `navbarPage()` and `navbarMenu()` functions to create applications
|
||||
with multiple top level panels.
|
||||
|
||||
* Added `navlistPanel()` function to create layouts with a a bootstrap
|
||||
navlist on the left and tabPanels on the right
|
||||
|
||||
* Added `type` parameter to `tabsetPanel()` to enable the use of pill
|
||||
style tabs in addition to the standard ones.
|
||||
|
||||
* Added `position` paramter to `tabsetPanel()` to enable positioning of tabs
|
||||
above, below, left, or right of tab content.
|
||||
|
||||
* Added `fluidPage()` and `fixedPage()` functions as well as related row and
|
||||
column layout functions for creating arbitrary bootstrap grid layouts.
|
||||
|
||||
* Added `hr()` builder function for creating horizontal rules.
|
||||
|
||||
* Automatically concatenate duplicate attributes in tag definitions
|
||||
|
||||
* Added `responsive` parameter to page building functions for opting-out of
|
||||
bootstrap responsive css.
|
||||
|
||||
* Added `theme` parameter to page building functions for specifying alternate
|
||||
bootstrap css styles.
|
||||
|
||||
* Added `icon()` function for embedding icons from the
|
||||
[font awesome](http://fontawesome.io/) icon library
|
||||
|
||||
* Added `makeReactiveBinding` function to turn a "regular" variable into a
|
||||
reactive one (i.e. reading the variable makes the current reactive context
|
||||
dependent on it, and setting the variable is a source of reactivity).
|
||||
|
||||
* Added a function `withMathJax()` to include the MathJax library in an app.
|
||||
|
||||
* The argument `selected` in checkboxGroupInput(), selectInput(), and
|
||||
radioButtons() refers to the value(s) instead of the name(s) of the
|
||||
argument `choices` now. For example, the value of the `selected` argument
|
||||
in selectInput(..., choices = c('Label 1' = 'x1', 'Label 2' = 'x2'),
|
||||
selected = 'Label 2') must be updated to 'x2', although names/labels will
|
||||
be automatically converted to values internally for backward
|
||||
compatibility. The same change applies to updateCheckboxGroupInput(),
|
||||
updateSelectInput(), and updateRadioButtons() as well. (#340)
|
||||
|
||||
* Now it is possible to only update the value of a checkbox group, select input,
|
||||
or radio buttons using the `selected` argument without providing the
|
||||
`choices` argument in updateCheckboxGroupInput(), updateSelectInput(), and
|
||||
updateRadioButtons(), respectively. (#340)
|
||||
|
||||
* Added `absolutePanel` and `fixedPanel` functions for creating absolute-
|
||||
and fixed-position panels. They can be easily made user-draggable by
|
||||
specifying `draggable = TRUE`.
|
||||
|
||||
* For the `options` argument of the function `renderDataTable()`, we can
|
||||
pass literal JavaScript code to the DataTables library via `I()`. This
|
||||
makes it possible to use any JavaScript object in the options, e.g. a
|
||||
JavaScript function (which is not supported in JSON). See
|
||||
`?renderDataTable` for details and examples.
|
||||
|
||||
* DataTables also works under IE8 now.
|
||||
|
||||
* Fixed a bug in DataTables pagination when searching is turned on, which
|
||||
caused failures for matrices as well as empty rows when displaying data
|
||||
frames using renderDataTable().
|
||||
|
||||
* The `options` argument in `renderDataTable()` can also take a function
|
||||
that returns a list. This makes it possible to use reactive values in the
|
||||
options. (#392)
|
||||
|
||||
* `renderDataTable()` respects more DataTables options now: (1) either
|
||||
bPaginate = FALSE or iDisplayLength = -1 will disable pagination (i.e. all
|
||||
rows are returned from the data); besides, this means we can also use -1
|
||||
in the length menu, e.g. aLengthMenu = list(c(10, 30, -1), list(10, 30,
|
||||
'All')); (2) we can disable searching for individual columns through the
|
||||
bSearchable option, e.g. aoColumns = list(list(bSearchable = FALSE),
|
||||
list(bSearchable = TRUE),...) (the search box for the first column is
|
||||
hidden); (3) we can turn off searching entirely (for both global searching
|
||||
and individual columns) using the option bFilter = FALSE.
|
||||
|
||||
* Added an argument `callback` in `renderDataTable()` so that a custom
|
||||
JavaScript function can be applied to the DataTable object. This makes it
|
||||
much easier to use DataTables plug-ins.
|
||||
|
||||
* For numeric columns in a DataTable, the search boxes support lower and
|
||||
upper bounds now: a search query of the form "lower,upper" (without
|
||||
quotes) indicates the limits [lower, upper]. For a column X, this means
|
||||
the rows corresponding to X >= lower & X <= upper are returned. If we omit
|
||||
either the lower limit or the upper limit, only the other limit will be
|
||||
used, e.g. ",upper" means X <= upper.
|
||||
|
||||
* `updateNumericInput(value)` tries to preserve numeric precision by avoiding
|
||||
scientific notation when possible, e.g. 102145 is no longer rounded to
|
||||
1.0214e+05 = 102140. (Thanks, Martin Loos. #401)
|
||||
|
||||
* `sliderInput()` no longer treats a label wrapped in HTML() as plain text,
|
||||
e.g. the label in sliderInput(..., label = HTML('<em>A Label</em>')) will
|
||||
not be escaped any more. (#119)
|
||||
|
||||
* Fixed #306: the trailing slash in a path could fail `addResourcePath()`
|
||||
under Windows. (Thanks, ZJ Dai)
|
||||
|
||||
* Dots are now legal characters for inputId/outputId. (Thanks, Kevin
|
||||
Lindquist. #358)
|
||||
|
||||
shiny 0.8.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Debug hooks are registered on all user-provided functions and (reactive)
|
||||
expressions (e.g., in renderPlot()), which makes it possible to set
|
||||
breakpoints in these functions using the latest version of the RStudio
|
||||
IDE, and the RStudio visual debugging tools can be used to debug Shiny
|
||||
apps. Internally, the registration is done via installExprFunction(),
|
||||
which is a new function introduced in this version to replace
|
||||
exprToFunction() so that the registration can be automatically done.
|
||||
|
||||
* Added a new function renderDataTable() to display tables using the
|
||||
JavaScript library DataTables. It includes basic features like pagination,
|
||||
searching (global search or search by individual columns), sorting (by
|
||||
single or multiple columns). All these features are implemented on the R
|
||||
side; for example, we can use R regular expressions for searching.
|
||||
Besides, it also uses the Bootstrap CSS style. See the full
|
||||
documentation and examples in the tutorial:
|
||||
http://rstudio.github.io/shiny/tutorial/#datatables
|
||||
|
||||
* Added a new option `shiny.error` which can take a function as an error
|
||||
handler. It is called when an error occurs in an app (in user-provided
|
||||
code), e.g., after we set options(shiny.error = recover), we can enter a
|
||||
specified environment in the call stack to debug our code after an error
|
||||
occurs.
|
||||
|
||||
* The argument `launch.browser` in runApp() can also be a function,
|
||||
which takes the URL of the shiny app as its input value.
|
||||
|
||||
* runApp() uses a random port between 3000 and 8000 instead of 8100 now. It
|
||||
will try up to 20 ports in case certain ports are not available.
|
||||
|
||||
* Fixed a bug for conditional panels: the value `input.id` in the condition
|
||||
was not correctly retrieved when the input widget had a type, such as
|
||||
numericInput(). (reported by Jason Bryer)
|
||||
|
||||
* Fixed two bugs in plotOutput(); clickId and hoverId did not give correct
|
||||
coordinates in Firefox, or when the axis limits of the plot were changed.
|
||||
(reported by Chris Warth and Greg D)
|
||||
|
||||
* The minimal required version for the httpuv package was increased to 1.2
|
||||
(on CRAN now).
|
||||
|
||||
|
||||
shiny 0.7.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Stopped sending websocket subprotocol. This fixes a compatibility issue with
|
||||
Google Chrome 30.
|
||||
|
||||
* The `input` and `output` objects are now also accessible via `session$input`
|
||||
and `session$output`.
|
||||
|
||||
* Added click and hover events for static plots; see `?plotOutput` for details.
|
||||
|
||||
* Added optional logging of the execution states of a reactive program, and
|
||||
tools for visualizing the log data. To use, start a new R session and call
|
||||
`options(shiny.reactlog=TRUE)`. Then launch a Shiny app and interact with it.
|
||||
Press Ctrl+F3 (or for Mac, Cmd+F3) in the browser to launch an interactive
|
||||
visualization of the reactivity that has occurred. See `?showReactLog` for
|
||||
more information.
|
||||
|
||||
* Added `includeScript()` and `includeCSS()` functions.
|
||||
|
||||
* Reactive expressions now have class="reactive" attribute. Also added
|
||||
`is.reactive()` and `is.reactivevalues()` functions.
|
||||
|
||||
* New `stopApp()` function, which stops an app and returns a value to the caller
|
||||
of `runApp()`.
|
||||
|
||||
* Added the `shiny.usecairo` option, which can be used to tell Shiny not to use
|
||||
Cairo for PNG output even when it is installed. (Defaults to `TRUE`.)
|
||||
|
||||
* Speed increases for `selectInput()` and `radioButtons()`, and their
|
||||
corresponding updater functions, for when they have many options.
|
||||
|
||||
* Added `tagSetChildren()` and `tagAppendChildren()` functions.
|
||||
|
||||
* The HTTP request object that created the websocket is now accessible from the
|
||||
`session` object, as `session$request`. This is a Rook-like request
|
||||
environment that can be used to access HTTP headers, among other things.
|
||||
(Note: When running in a Shiny Server environment, the request will reflect
|
||||
the proxy HTTP request that was made from the Shiny Server process to the R
|
||||
process, not the request that was made from the web browser to Shiny Server.)
|
||||
|
||||
* Fix `getComputedStyle` issue, for IE8 browser compatibility (#196). Note:
|
||||
Shiny Server is still required for IE8/9 compatibility.
|
||||
|
||||
* Add shiny.sharedSecret option, to require the HTTP header Shiny-Shared-Secret
|
||||
to be set to the given value.
|
||||
|
||||
shiny 0.6.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* `tabsetPanel()` can be directed to start with a specific tab selected.
|
||||
|
||||
* Fix bug where multiple file uploads with 3 or more files result in incorrect
|
||||
data.
|
||||
|
||||
* Add `withTags()` function.
|
||||
|
||||
* Add dateInput and dateRangeInput.
|
||||
|
||||
* `shinyServer()` now takes an optional `session` argument, which is used for
|
||||
communication with the session object.
|
||||
|
||||
* Add functions to update values of existing inputs on a page, instead of
|
||||
replacing them entirely.
|
||||
|
||||
* Allow listening on domain sockets.
|
||||
|
||||
* Added `actionButton()` to Shiny.
|
||||
|
||||
* The server can now send custom JSON messages to the client. On the client
|
||||
side, functions can be registered to handle these messages.
|
||||
|
||||
* Callbacks can be registered to be called at the end of a client session.
|
||||
|
||||
* Add ability to set priority of observers and outputs. Each priority level
|
||||
gets its own queue.
|
||||
|
||||
* Fix bug where the presence of a submit button would prevent sending of
|
||||
metadata until the button was clicked.
|
||||
|
||||
* `reactiveTimer()` and `invalidateLater()` by default no longer invalidate
|
||||
reactive objects after the client session has closed.
|
||||
|
||||
* Shiny apps can be run without a server.r and ui.r file.
|
||||
|
||||
shiny 0.5.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Switch from websockets package for handling websocket connections to httpuv.
|
||||
|
||||
* New method for detecting hidden output objects. Instead of checking that
|
||||
height and width are 0, it checks that the object or any ancestor in the DOM
|
||||
has style display:none.
|
||||
|
||||
* Add `clientData` reactive values object, which carries information about the
|
||||
client. This includes the hidden status of output objects, height/width plot
|
||||
output objects, and the URL of the browser.
|
||||
|
||||
* Add `parseQueryString()` function.
|
||||
|
||||
* Add `renderImage()` function for sending arbitrary image files to the client,
|
||||
and its counterpart, `imageOutput()`.
|
||||
|
||||
* Add support for high-resolution (Retina) displays.
|
||||
|
||||
* Fix bug #55, where `renderTable()` would throw error with an empty data frame.
|
||||
|
||||
shiny 0.4.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix bug where width and height weren't passed along properly from
|
||||
`reactivePlot` to `renderPlot`.
|
||||
|
||||
* Fix bug where infinite recursion would happen when `reactivePlot` was passed
|
||||
a function for width or height.
|
||||
|
||||
shiny 0.4.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Added suspend/resume capability to observers.
|
||||
|
||||
* Output objects are automatically suspended when they are hidden on the user's
|
||||
web browser.
|
||||
|
||||
* `runGist()` accepts GitHub's new URL format, which includes the username.
|
||||
|
||||
* `reactive()` and `observe()` now take expressions instead of functions.
|
||||
|
||||
* `reactiveText()`, `reactivePlot()`, and so on, have been renamed to
|
||||
`renderText()`, `renderPlot()`, etc. They also now take expressions instead
|
||||
of functions.
|
||||
|
||||
* Fixed a bug where empty values in a numericInput were sent to the R process
|
||||
as 0. They are now sent as NA.
|
||||
|
||||
shiny 0.3.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix issue #91: bug where downloading files did not work.
|
||||
|
||||
* Add [[<- operator for shinyoutput object, making it possible to assign values
|
||||
with `output[['plot1']] <- ...`.
|
||||
|
||||
* Reactive functions now preserve the visible/invisible state of their returned
|
||||
values.
|
||||
|
||||
shiny 0.3.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Reactive functions are now evaluated lazily.
|
||||
|
||||
* Add `reactiveValues()`.
|
||||
|
||||
* Using `as.list()` to convert a reactivevalues object (like `input`) to a list
|
||||
is deprecated. The new function `reactiveValuesToList()` should be used
|
||||
instead.
|
||||
|
||||
* Add `isolate()`. This function is used for accessing reactive functions,
|
||||
without them invalidating their parent contexts.
|
||||
|
||||
* Fix issue #58: bug where reactive functions are not re-run when all items in
|
||||
a checkboxGroup are unchecked.
|
||||
|
||||
* Fix issue #71, where `reactiveTable()` would return blank if the first
|
||||
element of a data frame was NA.
|
||||
|
||||
* In `plotOutput`, better validation for CSS units when specifying width and
|
||||
height.
|
||||
|
||||
* `reactivePrint()` no longer displays invisible output.
|
||||
|
||||
* `reactiveText()` no longer displays printed output, only the return value
|
||||
from a function.
|
||||
|
||||
* The `runGitHub()` and `runUrl()` functions have been added, for running
|
||||
Shiny apps from GitHub repositories and zip/tar files at remote URLs.
|
||||
|
||||
* Fix issue #64, where pressing Enter in a textbox would cause a form to
|
||||
submit.
|
||||
|
||||
shiny 0.2.4
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* `runGist` has been updated to use the new download URLs from
|
||||
https://gist.github.com.
|
||||
|
||||
* Shiny now uses `CairoPNG()` for output, when the Cairo package is available.
|
||||
This provides better-looking output on Linux and Windows.
|
||||
|
||||
shiny 0.2.3
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Ignore request variables for routing purposes
|
||||
|
||||
shiny 0.2.2
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix CRAN warning (assigning to global environment)
|
||||
|
||||
|
||||
shiny 0.2.1
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* [BREAKING] Modify API of `downloadHandler`: The `content` function now takes
|
||||
a file path, not writable connection, as an argument. This makes it much
|
||||
easier to work with APIs that only write to file paths, not connections.
|
||||
|
||||
|
||||
shiny 0.2.0
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix subtle name resolution bug--the usual symptom being S4 methods not being
|
||||
invoked correctly when called from inside of ui.R or server.R
|
||||
|
||||
|
||||
shiny 0.1.14
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix slider animator, which broke in 0.1.10
|
||||
|
||||
|
||||
shiny 0.1.13
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix temp file leak in reactivePlot
|
||||
|
||||
|
||||
shiny 0.1.12
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix problems with runGist on Windows
|
||||
* Add feature for on-the-fly file downloads (e.g. CSV data, PDFs)
|
||||
* Add CSS hooks for app-wide busy indicators
|
||||
|
||||
|
||||
shiny 0.1.11
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Fix input binding with IE8 on Shiny Server
|
||||
* Fix issue #41: reactiveTable should allow print options too
|
||||
* Allow dynamic sizing of reactivePlot (i.e. using a function instead of a fixed
|
||||
value)
|
||||
|
||||
|
||||
shiny 0.1.10
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Support more MIME types when serving out of www
|
||||
* Fix issue #35: Allow modification of untar args
|
||||
* headerPanel can take an explicit window title parameter
|
||||
* checkboxInput uses correct attribute `checked` instead of `selected`
|
||||
* Fix plot rendering with IE8 on Shiny Server
|
||||
|
||||
|
||||
shiny 0.1.9
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
* Much less flicker when updating plots
|
||||
* More customizable error display
|
||||
* Add `includeText`, `includeHTML`, and `includeMarkdown` functions for putting
|
||||
text, HTML, and Markdown content from external files in the application's UI.
|
||||
|
||||
|
||||
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 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!
|
||||
380
R/app.R
Normal file
380
R/app.R
Normal file
@@ -0,0 +1,380 @@
|
||||
# TODO: Subapp global.R
|
||||
|
||||
#' Create a Shiny app object
|
||||
#'
|
||||
#' These functions create Shiny app objects from either an explicit UI/server
|
||||
#' pair (\code{shinyApp}), or by passing the path of a directory that contains a
|
||||
#' Shiny app (\code{shinyAppDir}). You generally shouldn't need to use these
|
||||
#' functions to create/run applications; they are intended for interoperability
|
||||
#' purposes, such as embedding Shiny apps inside a \pkg{knitr} document.
|
||||
#'
|
||||
#' Normally when this function is used at the R console, the Shiny app object is
|
||||
#' automatically passed to the \code{print()} function, which runs the app. If
|
||||
#' this is called in the middle of a function, the value will not be passed to
|
||||
#' \code{print()} and the app will not be run. To make the app run, pass the app
|
||||
#' object to \code{print()} or \code{\link{runApp}()}.
|
||||
#'
|
||||
#' @param ui The UI definition of the app (for example, a call to
|
||||
#' \code{fluidPage()} with nested controls)
|
||||
#' @param server A server function
|
||||
#' @param onStart A function that will be called before the app is actually run.
|
||||
#' This is only needed for \code{shinyAppObj}, since in the \code{shinyAppDir}
|
||||
#' case, a \code{global.R} file can be used for this purpose.
|
||||
#' @param options Named options that should be passed to the `runApp` call. You
|
||||
#' can also specify \code{width} and \code{height} parameters which provide a
|
||||
#' hint to the embedding environment about the ideal height/width for the app.
|
||||
#' @param uiPattern A regular expression that will be applied to each \code{GET}
|
||||
#' request to determine whether the \code{ui} should be used to handle the
|
||||
#' request. Note that the entire request path must match the regular
|
||||
#' expression in order for the match to be considered successful.
|
||||
#' @return An object that represents the app. Printing the object or passing it
|
||||
#' to \code{\link{runApp}} will run the app.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' shinyAppDir(system.file("examples/01_hello", package="shiny"))
|
||||
#'
|
||||
#'
|
||||
#' # The object can be passed to runApp()
|
||||
#' app <- shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' numericInput("n", "n", 1),
|
||||
#' plotOutput("plot")
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot( plot(head(cars, input$n)) )
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
shinyApp <- function(ui=NULL, server=NULL, onStart=NULL, options=list(),
|
||||
uiPattern="/") {
|
||||
if (is.null(server)) {
|
||||
stop("`server` missing from shinyApp")
|
||||
}
|
||||
|
||||
# Ensure that the entire path is a match
|
||||
uiPattern <- sprintf("^%s$", uiPattern)
|
||||
|
||||
httpHandler <- uiHttpHandler(ui, uiPattern)
|
||||
|
||||
serverFuncSource <- function() {
|
||||
server
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
httpHandler = httpHandler,
|
||||
serverFuncSource = serverFuncSource,
|
||||
onStart = onStart,
|
||||
options = options),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param appDir Path to directory that contains a Shiny app (i.e. a server.R
|
||||
#' file and either ui.R or www/index.html)
|
||||
#' @export
|
||||
shinyAppDir <- function(appDir, options=list()) {
|
||||
if (!file_test('-d', appDir)) {
|
||||
stop("No Shiny application exists at the path \"", appDir, "\"")
|
||||
}
|
||||
|
||||
# In case it's a relative path, convert to absolute (so we're not adversely
|
||||
# affected by future changes to the path)
|
||||
appDir <- normalizePath(appDir, mustWork = TRUE)
|
||||
|
||||
if (file.exists.ci(appDir, "server.R")) {
|
||||
shinyAppDir_serverR(appDir, options = options)
|
||||
} else if (file.exists.ci(appDir, "app.R")) {
|
||||
shinyAppDir_appR(appDir, options = options)
|
||||
} else {
|
||||
stop("App dir must contain either app.R or server.R.")
|
||||
}
|
||||
}
|
||||
|
||||
# This reads in an app dir in the case that there's a server.R (and ui.R/www)
|
||||
# present, and returns a shiny.appobj.
|
||||
shinyAppDir_serverR <- function(appDir, options=list()) {
|
||||
# Most of the complexity here comes from needing to hot-reload if the .R files
|
||||
# change on disk, or are created, or are removed.
|
||||
|
||||
# uiHandlerSource is a function that returns an HTTP handler for serving up
|
||||
# ui.R as a webpage. The "cachedFuncWithFile" call makes sure that the closure
|
||||
# we're creating here only gets executed when ui.R's contents change.
|
||||
uiHandlerSource <- cachedFuncWithFile(appDir, "ui.R", case.sensitive = FALSE,
|
||||
function(uiR) {
|
||||
if (file.exists(uiR)) {
|
||||
# If ui.R contains a call to shinyUI (which sets .globals$ui), use that.
|
||||
# If not, then take the last expression that's returned from ui.R.
|
||||
.globals$ui <- NULL
|
||||
on.exit(.globals$ui <- NULL, add = FALSE)
|
||||
ui <- sourceUTF8(uiR, local = new.env(parent = globalenv()))$value
|
||||
if (!is.null(.globals$ui)) {
|
||||
ui <- .globals$ui[[1]]
|
||||
}
|
||||
return(uiHttpHandler(ui))
|
||||
} else {
|
||||
return(function(req) NULL)
|
||||
}
|
||||
}
|
||||
)
|
||||
uiHandler <- function(req) {
|
||||
uiHandlerSource()(req)
|
||||
}
|
||||
|
||||
wwwDir <- file.path.ci(appDir, "www")
|
||||
fallbackWWWDir <- system.file("www-dir", package = "shiny")
|
||||
serverSource <- cachedFuncWithFile(appDir, "server.R", case.sensitive = FALSE,
|
||||
function(serverR) {
|
||||
# If server.R contains a call to shinyServer (which sets .globals$server),
|
||||
# use that. If not, then take the last expression that's returned from
|
||||
# server.R.
|
||||
.globals$server <- NULL
|
||||
on.exit(.globals$server <- NULL, add = TRUE)
|
||||
result <- sourceUTF8(serverR, local = new.env(parent = globalenv()))$value
|
||||
if (!is.null(.globals$server)) {
|
||||
result <- .globals$server[[1]]
|
||||
}
|
||||
return(result)
|
||||
}
|
||||
)
|
||||
|
||||
# This function stands in for the server function, and reloads the
|
||||
# real server function as necessary whenever server.R changes
|
||||
serverFuncSource <- function() {
|
||||
serverFunction <- serverSource()
|
||||
if (is.null(serverFunction)) {
|
||||
return(function(input, output) NULL)
|
||||
} else if (is.function(serverFunction)) {
|
||||
# This is what we normally expect; run the server function
|
||||
return(serverFunction)
|
||||
} else {
|
||||
stop("server.R returned an object of unexpected type: ",
|
||||
typeof(serverFunction))
|
||||
}
|
||||
}
|
||||
|
||||
oldwd <- NULL
|
||||
onStart <- function() {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
if (file.exists(file.path.ci(appDir, "global.R")))
|
||||
sourceUTF8(file.path.ci(appDir, "global.R"))
|
||||
}
|
||||
onEnd <- function() {
|
||||
setwd(oldwd)
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
httpHandler = joinHandlers(c(uiHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = serverFuncSource,
|
||||
onStart = onStart,
|
||||
onEnd = onEnd,
|
||||
options = options),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
# This reads in an app dir in the case that there's a app.R present, and returns
|
||||
# a shiny.appobj.
|
||||
shinyAppDir_appR <- function(appDir, options=list()) {
|
||||
fullpath <- file.path.ci(appDir, "app.R")
|
||||
|
||||
# This sources app.R and caches the content. When appObj() is called but
|
||||
# app.R hasn't changed, it won't re-source the file. But if called and
|
||||
# app.R has changed, it'll re-source the file and return the result.
|
||||
appObj <- cachedFuncWithFile(appDir, "app.R", case.sensitive = FALSE,
|
||||
function(appR) {
|
||||
result <- sourceUTF8(fullpath, local = new.env(parent = globalenv()))$value
|
||||
|
||||
if (!is.shiny.appobj(result))
|
||||
stop("app.R did not return a shiny.appobj object.")
|
||||
|
||||
return(result)
|
||||
}
|
||||
)
|
||||
|
||||
# A function that invokes the http handler from the appObj in app.R, but
|
||||
# since this uses appObj(), it only re-sources the file when it changes.
|
||||
dynHttpHandler <- function(...) {
|
||||
appObj()$httpHandler(...)
|
||||
}
|
||||
|
||||
dynServerFuncSource <- function(...) {
|
||||
appObj()$serverFuncSource(...)
|
||||
}
|
||||
|
||||
wwwDir <- file.path.ci(appDir, "www")
|
||||
fallbackWWWDir <- system.file("www-dir", package = "shiny")
|
||||
|
||||
oldwd <- NULL
|
||||
onStart <- function() {
|
||||
oldwd <<- getwd()
|
||||
setwd(appDir)
|
||||
}
|
||||
onEnd <- function() {
|
||||
setwd(oldwd)
|
||||
}
|
||||
|
||||
structure(
|
||||
list(
|
||||
httpHandler = joinHandlers(c(dynHttpHandler, wwwDir, fallbackWWWDir)),
|
||||
serverFuncSource = dynServerFuncSource,
|
||||
onStart = onStart,
|
||||
onEnd = onEnd,
|
||||
options = options
|
||||
),
|
||||
class = "shiny.appobj"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param x Object to convert to a Shiny app.
|
||||
#' @export
|
||||
as.shiny.appobj <- function(x) {
|
||||
UseMethod("as.shiny.appobj", x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.shiny.appobj <- function(x) {
|
||||
x
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.list <- function(x) {
|
||||
shinyApp(ui = x$ui, server = x$server)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
as.shiny.appobj.character <- function(x) {
|
||||
shinyAppDir(x)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @export
|
||||
is.shiny.appobj <- function(x) {
|
||||
inherits(x, "shiny.appobj")
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @param ... Additional parameters to be passed to print.
|
||||
#' @export
|
||||
print.shiny.appobj <- function(x, ...) {
|
||||
opts <- x$options %OR% list()
|
||||
opts <- opts[names(opts) %in%
|
||||
c("port", "launch.browser", "host", "quiet", "display.mode")]
|
||||
|
||||
args <- c(list(x), opts)
|
||||
|
||||
do.call(runApp, args)
|
||||
}
|
||||
|
||||
#' @rdname shinyApp
|
||||
#' @method as.tags shiny.appobj
|
||||
#' @export
|
||||
as.tags.shiny.appobj <- function(x, ...) {
|
||||
# jcheng 06/06/2014: Unfortunate copy/paste between this function and
|
||||
# knit_print.shiny.appobj, but I am trying to make the most conservative
|
||||
# change possible due to upcoming release.
|
||||
opts <- x$options %OR% list()
|
||||
width <- if (is.null(opts$width)) "100%" else opts$width
|
||||
height <- if (is.null(opts$height)) "400" else opts$height
|
||||
|
||||
path <- addSubApp(x)
|
||||
tags$iframe(src=path, width=width, height=height, class="shiny-frame")
|
||||
}
|
||||
|
||||
#' Knitr S3 methods
|
||||
#'
|
||||
#' These S3 methods are necessary to help Shiny applications and UI chunks embed
|
||||
#' themselves in knitr/rmarkdown documents.
|
||||
#'
|
||||
#' @name knitr_methods
|
||||
#' @param x Object to knit_print
|
||||
#' @param ... Additional knit_print arguments
|
||||
NULL
|
||||
|
||||
# If there's an R Markdown runtime option set but it isn't set to Shiny, then
|
||||
# return a warning indicating the runtime is inappropriate for this object.
|
||||
# Returns NULL in all other cases.
|
||||
shiny_rmd_warning <- function() {
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny")
|
||||
# note that the RStudio IDE checks for this specific string to detect Shiny
|
||||
# applications in static document
|
||||
list(structure(
|
||||
"Shiny application in a static R Markdown document",
|
||||
class = "rmd_warning"))
|
||||
else
|
||||
NULL
|
||||
}
|
||||
|
||||
#' @rdname knitr_methods
|
||||
#' @export
|
||||
knit_print.shiny.appobj <- function(x, ...) {
|
||||
opts <- x$options %OR% list()
|
||||
width <- if (is.null(opts$width)) "100%" else opts$width
|
||||
height <- if (is.null(opts$height)) "400" else opts$height
|
||||
|
||||
runtime <- knitr::opts_knit$get("rmarkdown.runtime")
|
||||
if (!is.null(runtime) && runtime != "shiny") {
|
||||
# If not rendering to a Shiny document, create a box exactly the same
|
||||
# dimensions as the Shiny app would have had (so the document continues to
|
||||
# flow as it would have with the app), and display a diagnostic message
|
||||
width <- validateCssUnit(width)
|
||||
height <- validateCssUnit(height)
|
||||
output <- tags$div(
|
||||
style=paste("width:", width, "; height:", height, "; text-align: center;",
|
||||
"box-sizing: border-box;", "-moz-box-sizing: border-box;",
|
||||
"-webkit-box-sizing: border-box;"),
|
||||
class="muted well",
|
||||
"Shiny applications not supported in static R Markdown documents")
|
||||
}
|
||||
else {
|
||||
path <- addSubApp(x)
|
||||
output <- tags$iframe(src=path, width=width, height=height,
|
||||
class="shiny-frame")
|
||||
}
|
||||
|
||||
# If embedded Shiny apps ever have JS/CSS dependencies (like pym.js) we'll
|
||||
# need to grab those and put them in meta, like in knit_print.shiny.tag. But
|
||||
# for now it's not an issue, so just return the HTML and warning.
|
||||
|
||||
knitr::asis_output(htmlPreserve(format(output, indent=FALSE)),
|
||||
meta = shiny_rmd_warning(), cacheable = FALSE)
|
||||
}
|
||||
|
||||
# Let us use a nicer syntax in knitr chunks than literally
|
||||
# calling output$value <- renderFoo(...) and fooOutput().
|
||||
#' @rdname knitr_methods
|
||||
#' @param inline Whether the object is printed inline.
|
||||
#' @export
|
||||
knit_print.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
x <- htmltools::as.tags(x, inline = inline)
|
||||
output <- knitr::knit_print(tagList(x))
|
||||
attr(output, "knit_cacheable") <- FALSE
|
||||
attr(output, "knit_meta") <- append(attr(output, "knit_meta"),
|
||||
shiny_rmd_warning())
|
||||
output
|
||||
}
|
||||
419
R/bootstrap-layout.R
Normal file
419
R/bootstrap-layout.R
Normal file
@@ -0,0 +1,419 @@
|
||||
|
||||
#' Create a page with fluid layout
|
||||
#'
|
||||
#' Functions for creating fluid page layouts. A fluid page layout consists of
|
||||
#' rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fluid pages scale their
|
||||
#' components in realtime to fill all available browser width.
|
||||
#'
|
||||
#' @param ... Elements to include within the page
|
||||
#' @param title The browser window title (defaults to the host URL of the page).
|
||||
#' Can also be set as a side effect of the \code{\link{titlePanel}} function.
|
||||
#' @param responsive This option is deprecated; it is no longer optional with
|
||||
#' Bootstrap 3.
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
#'
|
||||
#' @details To create a fluid page use the \code{fluidPage} function and include
|
||||
#' instances of \code{fluidRow} and \code{\link{column}} within it. As an
|
||||
#' alternative to low-level row and column functions you can also use
|
||||
#' higher-level layout functions like \code{\link{sidebarLayout}}.
|
||||
#'
|
||||
#' @note See the \href{http://shiny.rstudio.com/articles/layout-guide.html}{
|
||||
#' Shiny-Application-Layout-Guide} for additional details on laying out fluid
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}, \code{\link{sidebarLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fluidPage(
|
||||
#'
|
||||
#' # Application title
|
||||
#' titlePanel("Hello Shiny!"),
|
||||
#'
|
||||
#' sidebarLayout(
|
||||
#'
|
||||
#' # 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")
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' shinyUI(fluidPage(
|
||||
#' title = "Hello Shiny!",
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
|
||||
bootstrapPage(div(class = "container-fluid", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
theme = theme)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname fluidPage
|
||||
#' @export
|
||||
fluidRow <- function(...) {
|
||||
div(class = "row", ...)
|
||||
}
|
||||
|
||||
#' Create a page with a fixed layout
|
||||
#'
|
||||
#' Functions for creating fixed page layouts. A fixed page layout consists of
|
||||
#' rows which in turn include columns. Rows exist for the purpose of making sure
|
||||
#' their elements appear on the same line (if the browser has adequate width).
|
||||
#' Columns exist for the purpose of defining how much horizontal space within a
|
||||
#' 12-unit wide grid it's elements should occupy. Fixed pages limit their width
|
||||
#' to 940 pixels on a typical display, and 724px or 1170px on smaller and larger
|
||||
#' displays respectively.
|
||||
#'
|
||||
#' @param ... Elements to include within the container
|
||||
#' @param title The browser window title (defaults to the host URL of the page)
|
||||
#' @param responsive This option is deprecated; it is no longer optional with
|
||||
#' Bootstrap 3.
|
||||
#' @param theme Alternative Bootstrap stylesheet (normally a css file within the
|
||||
#' www directory). For example, to use the theme located at
|
||||
#' \code{www/bootstrap.css} you would use \code{theme = "bootstrap.css"}.
|
||||
#'
|
||||
#' @return A UI defintion that can be passed to the \link{shinyUI} function.
|
||||
#'
|
||||
#' @details To create a fixed page use the \code{fixedPage} function and include
|
||||
#' instances of \code{fixedRow} and \code{\link{column}} within it. Note that
|
||||
#' unlike \code{\link{fluidPage}}, fixed pages cannot make use of higher-level
|
||||
#' layout functions like \code{sidebarLayout}, rather, all layout must be done
|
||||
#' with \code{fixedRow} and \code{column}.
|
||||
#'
|
||||
#' @note See the \href{http://shiny.rstudio.com/articles/layout-guide.html}{
|
||||
#' Shiny Application Layout Guide} for additional details on laying out fixed
|
||||
#' pages.
|
||||
#'
|
||||
#' @seealso \code{\link{column}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fixedPage(
|
||||
#' title = "Hello, Shiny!",
|
||||
#' fixedRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' ))
|
||||
#'
|
||||
#' @rdname fixedPage
|
||||
#' @export
|
||||
fixedPage <- function(..., title = NULL, responsive = NULL, theme = NULL) {
|
||||
bootstrapPage(div(class = "container", ...),
|
||||
title = title,
|
||||
responsive = responsive,
|
||||
theme = theme)
|
||||
}
|
||||
|
||||
#' @rdname fixedPage
|
||||
#' @export
|
||||
fixedRow <- function(...) {
|
||||
div(class = "row", ...)
|
||||
}
|
||||
|
||||
|
||||
#' Create a column within a UI definition
|
||||
#'
|
||||
#' Create a column for use within a \code{\link{fluidRow}} or
|
||||
#' \code{\link{fixedRow}}
|
||||
#'
|
||||
#' @param width The grid width of the column (must be between 1 and 12)
|
||||
#' @param ... Elements to include within the column
|
||||
#' @param offset The number of columns to offset this column from the end of the
|
||||
#' previous column.
|
||||
#'
|
||||
#' @return A column that can be included within a
|
||||
#' \code{\link{fluidRow}} or \code{\link{fixedRow}}.
|
||||
#'
|
||||
#'
|
||||
#' @seealso \code{\link{fluidRow}}, \code{\link{fixedRow}}.
|
||||
#'
|
||||
#' @examples
|
||||
#' fluidRow(
|
||||
#' column(4,
|
||||
#' sliderInput("obs", "Number of observations:",
|
||||
#' min = 1, max = 1000, value = 500)
|
||||
#' ),
|
||||
#' column(8,
|
||||
#' plotOutput("distPlot")
|
||||
#' )
|
||||
#' )
|
||||
#'
|
||||
#' fluidRow(
|
||||
#' column(width = 4,
|
||||
#' "4"
|
||||
#' ),
|
||||
#' column(width = 3, offset = 2,
|
||||
#' "3 offset 2"
|
||||
#' )
|
||||
#' )
|
||||
#' @export
|
||||
column <- function(width, ..., offset = 0) {
|
||||
|
||||
if (!is.numeric(width) || (width < 1) || (width > 12))
|
||||
stop("column width must be between 1 and 12")
|
||||
|
||||
colClass <- paste0("col-sm-", width)
|
||||
if (offset > 0)
|
||||
colClass <- paste0(colClass, " col-sm-offset-", offset)
|
||||
div(class = colClass, ...)
|
||||
}
|
||||
|
||||
|
||||
#' Create a panel containing an application title.
|
||||
#'
|
||||
#' @param title An application title to display
|
||||
#' @param windowTitle The title that should be displayed by the browser window.
|
||||
#'
|
||||
#' @details Calling this function has the side effect of including a
|
||||
#' \code{title} tag within the head. You can also specify a page title
|
||||
#' explicitly using the `title` parameter of the top-level page function.
|
||||
#'
|
||||
#'
|
||||
#' @examples
|
||||
#' titlePanel("Hello Shiny!")
|
||||
#'
|
||||
#' @export
|
||||
titlePanel <- function(title, windowTitle=title) {
|
||||
tagList(
|
||||
tags$head(tags$title(windowTitle)),
|
||||
h2(title)
|
||||
)
|
||||
}
|
||||
|
||||
#' Layout a sidebar and main area
|
||||
#'
|
||||
#' Create a layout with a sidebar and main area. The sidebar is displayed with a
|
||||
#' distinct background color and typically contains input controls. The main
|
||||
#' area occupies 2/3 of the horizontal width and typically contains outputs.
|
||||
#'
|
||||
#' @param sidebarPanel The \link{sidebarPanel} containing input controls
|
||||
#' @param mainPanel The \link{mainPanel} containing outputs
|
||||
#' @param position The position of the sidebar relative to the main area ("left"
|
||||
#' or "right")
|
||||
#' @param fluid \code{TRUE} to use fluid layout; \code{FALSE} to use fixed
|
||||
#' layout.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Define UI
|
||||
#' shinyUI(fluidPage(
|
||||
#'
|
||||
#' # Application title
|
||||
#' titlePanel("Hello Shiny!"),
|
||||
#'
|
||||
#' sidebarLayout(
|
||||
#'
|
||||
#' # 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
|
||||
sidebarLayout <- function(sidebarPanel,
|
||||
mainPanel,
|
||||
position = c("left", "right"),
|
||||
fluid = TRUE) {
|
||||
|
||||
# determine the order
|
||||
position <- match.arg(position)
|
||||
if (position == "left") {
|
||||
firstPanel <- sidebarPanel
|
||||
secondPanel <- mainPanel
|
||||
}
|
||||
else if (position == "right") {
|
||||
firstPanel <- mainPanel
|
||||
secondPanel <- sidebarPanel
|
||||
}
|
||||
|
||||
# return as as row
|
||||
if (fluid)
|
||||
fluidRow(firstPanel, secondPanel)
|
||||
else
|
||||
fixedRow(firstPanel, secondPanel)
|
||||
}
|
||||
|
||||
#' Lay out UI elements vertically
|
||||
#'
|
||||
#' Create a container that includes one or more rows of content (each element
|
||||
#' passed to the container will appear on it's own line in the UI)
|
||||
#'
|
||||
#' @param ... Elements to include within the container
|
||||
#' @param fluid \code{TRUE} to use fluid layout; \code{FALSE} to use fixed
|
||||
#' layout.
|
||||
#'
|
||||
#' @seealso \code{\link{fluidPage}}, \code{\link{flowLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' shinyUI(fluidPage(
|
||||
#' verticalLayout(
|
||||
#' a(href="http://example.com/link1", "Link One"),
|
||||
#' a(href="http://example.com/link2", "Link Two"),
|
||||
#' a(href="http://example.com/link3", "Link Three")
|
||||
#' )
|
||||
#' ))
|
||||
#' @export
|
||||
verticalLayout <- function(..., fluid = TRUE) {
|
||||
lapply(list(...), function(row) {
|
||||
col <- column(12, row)
|
||||
if (fluid)
|
||||
fluidRow(col)
|
||||
else
|
||||
fixedRow(col)
|
||||
})
|
||||
}
|
||||
|
||||
#' Flow layout
|
||||
#'
|
||||
#' Lays out elements in a left-to-right, top-to-bottom arrangement. The elements
|
||||
#' on a given row will be top-aligned with each other. This layout will not work
|
||||
#' well with elements that have a percentage-based width (e.g.
|
||||
#' \code{\link{plotOutput}} at its default setting of \code{width = "100\%"}).
|
||||
#'
|
||||
#' @param ... Unnamed arguments will become child elements of the layout. Named
|
||||
#' arguments will become HTML attributes on the outermost tag.
|
||||
#' @param cellArgs Any additional attributes that should be used for each cell
|
||||
#' of the layout.
|
||||
#'
|
||||
#' @seealso \code{\link{verticalLayout}}
|
||||
#'
|
||||
#' @examples
|
||||
#' flowLayout(
|
||||
#' numericInput("rows", "How many rows?", 5),
|
||||
#' selectInput("letter", "Which letter?", LETTERS),
|
||||
#' sliderInput("value", "What value?", 0, 100, 50)
|
||||
#' )
|
||||
#' @export
|
||||
flowLayout <- function(..., cellArgs = list()) {
|
||||
|
||||
children <- list(...)
|
||||
childIdx <- !nzchar(names(children) %OR% character(length(children)))
|
||||
attribs <- children[!childIdx]
|
||||
children <- children[childIdx]
|
||||
|
||||
do.call(tags$div, c(list(class = "shiny-flow-layout"),
|
||||
attribs,
|
||||
lapply(children, function(x) {
|
||||
do.call(tags$div, c(cellArgs, list(x)))
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
#' Input panel
|
||||
#'
|
||||
#' A \code{\link{flowLayout}} with a grey border and light grey background,
|
||||
#' suitable for wrapping inputs.
|
||||
#'
|
||||
#' @param ... Input controls or other HTML elements.
|
||||
#'
|
||||
#' @export
|
||||
inputPanel <- function(...) {
|
||||
div(class = "shiny-input-panel",
|
||||
flowLayout(...)
|
||||
)
|
||||
}
|
||||
|
||||
#' Split layout
|
||||
#'
|
||||
#' Lays out elements horizontally, dividing the available horizontal space into
|
||||
#' equal parts (by default).
|
||||
#'
|
||||
#' @param ... Unnamed arguments will become child elements of the layout. Named
|
||||
#' arguments will become HTML attributes on the outermost tag.
|
||||
#' @param cellWidths Character or numeric vector indicating the widths of the
|
||||
#' individual cells. Recycling will be used if needed. Character values will
|
||||
#' be interpreted as CSS lengths (see \code{\link{validateCssUnit}}), numeric
|
||||
#' values as pixels.
|
||||
#' @param cellArgs Any additional attributes that should be used for each cell
|
||||
#' of the layout.
|
||||
#'
|
||||
#' @examples
|
||||
#' # Equal sizing
|
||||
#' splitLayout(
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2")
|
||||
#' )
|
||||
#'
|
||||
#' # Custom widths
|
||||
#' splitLayout(cellWidths = c("25%", "75%"),
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2")
|
||||
#' )
|
||||
#'
|
||||
#' # All cells at 300 pixels wide, with cell padding
|
||||
#' # and a border around everything
|
||||
#' splitLayout(
|
||||
#' style = "border: 1px solid silver;",
|
||||
#' cellWidths = 300,
|
||||
#' cellArgs = list(style = "padding: 6px"),
|
||||
#' plotOutput("plot1"),
|
||||
#' plotOutput("plot2"),
|
||||
#' plotOutput("plot3")
|
||||
#' )
|
||||
#' @export
|
||||
splitLayout <- function(..., cellWidths = NULL, cellArgs = list()) {
|
||||
|
||||
children <- list(...)
|
||||
childIdx <- !nzchar(names(children) %OR% character(length(children)))
|
||||
attribs <- children[!childIdx]
|
||||
children <- children[childIdx]
|
||||
count <- length(children)
|
||||
|
||||
if (length(cellWidths) == 0 || is.na(cellWidths)) {
|
||||
cellWidths <- sprintf("%.3f%%", 100 / count)
|
||||
}
|
||||
cellWidths <- rep(cellWidths, length.out = count)
|
||||
cellWidths <- sapply(cellWidths, validateCssUnit)
|
||||
|
||||
do.call(tags$div, c(list(class = "shiny-split-layout"),
|
||||
attribs,
|
||||
mapply(children, cellWidths, FUN = function(x, w) {
|
||||
do.call(tags$div, c(
|
||||
list(style = sprintf("width: %s;", w)),
|
||||
cellArgs,
|
||||
list(x)
|
||||
))
|
||||
}, SIMPLIFY = FALSE)
|
||||
))
|
||||
}
|
||||
2274
R/bootstrap.R
Normal file
2274
R/bootstrap.R
Normal file
File diff suppressed because it is too large
Load Diff
77
R/cache.R
Normal file
77
R/cache.R
Normal file
@@ -0,0 +1,77 @@
|
||||
# A context object for tracking a cache that needs to be dirtied when a set of
|
||||
# files changes on disk. Each time the cache is dirtied, the set of files is
|
||||
# cleared. Therefore, the set of files needs to be re-built each time the cached
|
||||
# code executes. This approach allows for dynamic dependency graphs.
|
||||
CacheContext <- R6Class(
|
||||
'CacheContext',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.dirty = TRUE,
|
||||
# List of functions that return TRUE if dirty
|
||||
.tests = list(),
|
||||
|
||||
addDependencyFile = function(file) {
|
||||
if (.dirty)
|
||||
return()
|
||||
|
||||
file <- normalizePath(file)
|
||||
|
||||
mtime <- file.info(file)$mtime
|
||||
.tests <<- c(.tests, function() {
|
||||
newMtime <- try(file.info(file)$mtime, silent=TRUE)
|
||||
if (inherits(newMtime, 'try-error'))
|
||||
return(TRUE)
|
||||
return(!identical(mtime, newMtime))
|
||||
})
|
||||
invisible()
|
||||
},
|
||||
forceDirty = function() {
|
||||
.dirty <<- TRUE
|
||||
.tests <<- list()
|
||||
invisible()
|
||||
},
|
||||
isDirty = function() {
|
||||
if (.dirty)
|
||||
return(TRUE)
|
||||
|
||||
for (test in .tests) {
|
||||
if (test()) {
|
||||
forceDirty()
|
||||
return(TRUE)
|
||||
}
|
||||
}
|
||||
|
||||
return(FALSE)
|
||||
},
|
||||
reset = function() {
|
||||
.dirty <<- FALSE
|
||||
.tests <<- list()
|
||||
},
|
||||
with = function(func) {
|
||||
oldCC <- .currentCacheContext$cc
|
||||
.currentCacheContext$cc <- self
|
||||
on.exit(.currentCacheContext$cc <- oldCC)
|
||||
|
||||
return(func())
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
.currentCacheContext <- new.env()
|
||||
|
||||
# Indicates to Shiny that the given file path is part of the dependency graph
|
||||
# for whatever is currently executing (so far, only ui.R). By default, ui.R only
|
||||
# gets re-executed when it is detected to have changed; this function allows the
|
||||
# caller to indicate that it should also re-execute if the given file changes.
|
||||
#
|
||||
# If NULL or NA is given as the argument, then ui.R will re-execute next time.
|
||||
dependsOnFile <- function(filepath) {
|
||||
if (is.null(.currentCacheContext$cc))
|
||||
return()
|
||||
|
||||
if (is.null(filepath) || is.na(filepath))
|
||||
.currentCacheContext$cc$forceDirty()
|
||||
else
|
||||
.currentCacheContext$cc$addDependencyFile(filepath)
|
||||
}
|
||||
113
R/fileupload.R
Normal file
113
R/fileupload.R
Normal file
@@ -0,0 +1,113 @@
|
||||
# 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 <- R6Class(
|
||||
'FileUploadOperation',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.parent = NULL,
|
||||
.id = character(0),
|
||||
.files = data.frame(),
|
||||
.dir = character(0),
|
||||
.currentFileInfo = list(),
|
||||
.currentFileData = NULL,
|
||||
.pendingFileInfos = list(),
|
||||
|
||||
initialize = function(parent, id, dir, fileInfos) {
|
||||
.parent <<- parent
|
||||
.id <<- id
|
||||
.files <<- data.frame(name=character(),
|
||||
size=numeric(),
|
||||
type=character(),
|
||||
datapath=character(),
|
||||
stringsAsFactors=FALSE)
|
||||
.dir <<- dir
|
||||
.pendingFileInfos <<- fileInfos
|
||||
},
|
||||
fileBegin = function() {
|
||||
if (length(.pendingFileInfos) < 1)
|
||||
stop("fileBegin called too many times")
|
||||
|
||||
file <- .pendingFileInfos[[1]]
|
||||
.currentFileInfo <<- file
|
||||
.pendingFileInfos <<- tail(.pendingFileInfos, -1)
|
||||
|
||||
filename <- file.path(.dir, as.character(length(.files$name)))
|
||||
row <- data.frame(name=file$name, size=file$size, type=file$type,
|
||||
datapath=filename, stringsAsFactors=FALSE)
|
||||
|
||||
if (length(.files$name) == 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() {
|
||||
if (length(.pendingFileInfos) > 0)
|
||||
stop("File upload job was stopped prematurely")
|
||||
.parent$onJobFinished(.id)
|
||||
return(.files)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#' @include map.R
|
||||
FileUploadContext <- R6Class(
|
||||
'FileUploadContext',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.basedir = character(0),
|
||||
.operations = 'Map',
|
||||
|
||||
initialize = function(dir=tempdir()) {
|
||||
.basedir <<- dir
|
||||
.operations <<- Map$new()
|
||||
},
|
||||
createUploadOperation = function(fileInfos) {
|
||||
while (TRUE) {
|
||||
id <- paste(as.raw(p_runif(12, min=0, max=0xFF)), collapse='')
|
||||
dir <- file.path(.basedir, id)
|
||||
if (!dir.create(dir))
|
||||
next
|
||||
|
||||
op <- FileUploadOperation$new(self, id, dir, fileInfos)
|
||||
.operations$set(id, op)
|
||||
return(id)
|
||||
}
|
||||
},
|
||||
getUploadOperation = function(jobId) {
|
||||
.operations$get(jobId)
|
||||
},
|
||||
onJobFinished = function(jobId) {
|
||||
.operations$remove(jobId)
|
||||
}
|
||||
)
|
||||
)
|
||||
23
R/globals.R
Normal file
23
R/globals.R
Normal file
@@ -0,0 +1,23 @@
|
||||
# A scope where we can put mutable global state
|
||||
.globals <- new.env(parent = emptyenv())
|
||||
|
||||
.onLoad <- function(libname, pkgname) {
|
||||
# R's lazy-loading package scheme causes the private seed to be cached in the
|
||||
# package itself, making our PRNG completely deterministic. This line resets
|
||||
# the private seed during load.
|
||||
withPrivateSeed(reinitializeSeed())
|
||||
}
|
||||
|
||||
.onAttach <- function(libname, pkgname) {
|
||||
# Check for htmlwidgets version, if installed. As of Shiny 0.12.0 and
|
||||
# htmlwidgets 0.4, both packages switched from RJSONIO to jsonlite. Because of
|
||||
# this change, Shiny 0.12.0 will work only with htmlwidgets >= 0.4, and vice
|
||||
# versa.
|
||||
if (system.file(package = "htmlwidgets") != "" &&
|
||||
utils::packageVersion("htmlwidgets") < "0.4") {
|
||||
packageStartupMessage(
|
||||
"This version of Shiny is designed to work with htmlwidgets >= 0.4. ",
|
||||
"Please upgrade your version of htmlwidgets."
|
||||
)
|
||||
}
|
||||
}
|
||||
103
R/graph.R
Normal file
103
R/graph.R
Normal file
@@ -0,0 +1,103 @@
|
||||
writeReactLog <- function(file=stdout()) {
|
||||
cat(toJSON(.graphStack$as_list(), pretty=TRUE), file=file)
|
||||
}
|
||||
|
||||
#' Reactive Log Visualizer
|
||||
#'
|
||||
#' Provides an interactive browser-based tool for visualizing reactive
|
||||
#' dependencies and execution in your application.
|
||||
#'
|
||||
#' To use the reactive log visualizer, start with a fresh R session and
|
||||
#' run the command \code{options(shiny.reactlog=TRUE)}; then launch your
|
||||
#' application in the usual way (e.g. using \code{\link{runApp}}). At
|
||||
#' any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your
|
||||
#' web browser to launch the reactive log visualization.
|
||||
#'
|
||||
#' The reactive log visualization only includes reactive activity up
|
||||
#' until the time the report was loaded. If you want to see more recent
|
||||
#' activity, refresh the browser.
|
||||
#'
|
||||
#' Note that Shiny does not distinguish between reactive dependencies
|
||||
#' that "belong" to one Shiny user session versus another, so the
|
||||
#' visualization will include all reactive activity that has taken place
|
||||
#' in the process, not just for a particular application or session.
|
||||
#'
|
||||
#' As an alternative to pressing Ctrl/Command+F3--for example, if you
|
||||
#' are using reactives outside of the context of a Shiny
|
||||
#' application--you can run the \code{showReactLog} function, which will
|
||||
#' generate the reactive log visualization as a static HTML file and
|
||||
#' launch it in your default browser. In this case, refreshing your
|
||||
#' browser will not load new activity into the report; you will need to
|
||||
#' call \code{showReactLog()} explicitly.
|
||||
#'
|
||||
#' For security and performance reasons, do not enable
|
||||
#' \code{shiny.reactlog} in production environments. When the option is
|
||||
#' enabled, it's possible for any user of your app to see at least some
|
||||
#' of the source code of your reactive expressions and observers.
|
||||
#'
|
||||
#' @export
|
||||
showReactLog <- function() {
|
||||
browseURL(renderReactLog())
|
||||
}
|
||||
|
||||
renderReactLog <- function() {
|
||||
templateFile <- system.file('www/reactive-graph.html', package='shiny')
|
||||
html <- paste(readLines(templateFile, warn=FALSE), collapse='\r\n')
|
||||
tc <- textConnection(NULL, 'w')
|
||||
on.exit(close(tc))
|
||||
writeReactLog(tc)
|
||||
cat('\n', file=tc)
|
||||
flush(tc)
|
||||
html <- sub('__DATA__', paste(textConnectionValue(tc), collapse='\r\n'), html, fixed=TRUE)
|
||||
file <- tempfile(fileext = '.html')
|
||||
writeLines(html, file)
|
||||
return(file)
|
||||
}
|
||||
|
||||
.graphAppend <- function(logEntry, domain = getDefaultReactiveDomain()) {
|
||||
if (isTRUE(getOption('shiny.reactlog')))
|
||||
.graphStack$push(logEntry)
|
||||
|
||||
if (!is.null(domain)) {
|
||||
domain$reactlog(logEntry)
|
||||
}
|
||||
}
|
||||
|
||||
.graphDependsOn <- function(id, label) {
|
||||
.graphAppend(list(action='dep', id=id, dependsOn=label))
|
||||
}
|
||||
|
||||
.graphDependsOnId <- function(id, dependee) {
|
||||
.graphAppend(list(action='depId', id=id, dependsOn=dependee))
|
||||
}
|
||||
|
||||
.graphCreateContext <- function(id, label, type, prevId, domain) {
|
||||
.graphAppend(list(
|
||||
action='ctx', id=id, label=paste(label, collapse='\n'),
|
||||
srcref=attr(label, "srcref"), srcfile=attr(label, "srcfile"),
|
||||
type=type, prevId=prevId
|
||||
), domain = domain)
|
||||
}
|
||||
|
||||
.graphEnterContext <- function(id) {
|
||||
.graphAppend(list(action='enter', id=id))
|
||||
}
|
||||
|
||||
.graphExitContext <- function(id) {
|
||||
.graphAppend(list(action='exit', id=id))
|
||||
}
|
||||
|
||||
.graphValueChange <- function(label, value) {
|
||||
.graphAppend(list(
|
||||
action = 'valueChange',
|
||||
id = label,
|
||||
value = paste(capture.output(str(value)), collapse='\n')
|
||||
))
|
||||
}
|
||||
|
||||
.graphInvalidate <- function(id, domain) {
|
||||
.graphAppend(list(action='invalidate', id=id), domain)
|
||||
}
|
||||
|
||||
#' @include stack.R
|
||||
.graphStack <- Stack$new()
|
||||
24
R/hooks.R
Normal file
24
R/hooks.R
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
# Call an application hook. Application hooks are provided so that front ends
|
||||
# can know when a Shiny application is running:
|
||||
#
|
||||
# shiny.onAppStart -- called when an application begins running
|
||||
# shiny.onAppStop -- called when an appliation stops
|
||||
#
|
||||
# Both hooks are passed the url where the application is accessible (appUrl).
|
||||
# Note that the appUrl can be NULL if the application was run on a UNIX domain
|
||||
# socket rather than a TCP/IP port/
|
||||
callAppHook <- function(name, appUrl) {
|
||||
for (hook in getHooksList(paste0("shiny.", name)))
|
||||
hook(appUrl)
|
||||
}
|
||||
|
||||
# The value for getHook can be a single function or a list of functions,
|
||||
# This function ensures that the result can always be processed as a list
|
||||
getHooksList <- function(name) {
|
||||
hooks <- getHook(name)
|
||||
if (!is.list(hooks))
|
||||
hooks <- list(hooks)
|
||||
hooks
|
||||
}
|
||||
29
R/html-deps.R
Normal file
29
R/html-deps.R
Normal file
@@ -0,0 +1,29 @@
|
||||
#' Create a web dependency
|
||||
#'
|
||||
#' Ensure that a file-based HTML dependency (from the htmltools package) can be
|
||||
#' served over Shiny's HTTP server. This function works by using
|
||||
#' \code{\link{addResourcePath}} to map the HTML dependency's directory to a
|
||||
#' URL.
|
||||
#'
|
||||
#' @param dependency A single HTML dependency object, created using
|
||||
#' \code{\link{htmlDependency}}. If the \code{src} value is named, then
|
||||
#' \code{href} and/or \code{file} names must be present.
|
||||
#'
|
||||
#' @return A single HTML dependency object that has an \code{href}-named element
|
||||
#' in its \code{src}.
|
||||
#' @export
|
||||
createWebDependency <- function(dependency) {
|
||||
if (is.null(dependency))
|
||||
return(NULL)
|
||||
|
||||
if (!inherits(dependency, "html_dependency"))
|
||||
stop("Unexpected non-html_dependency type")
|
||||
|
||||
if (is.null(dependency$src$href)) {
|
||||
prefix <- paste(dependency$name, "-", dependency$version, sep = "")
|
||||
addResourcePath(prefix, dependency$src$file)
|
||||
dependency$src$href <- prefix
|
||||
}
|
||||
|
||||
return(dependency)
|
||||
}
|
||||
7
R/htmltools.R
Normal file
7
R/htmltools.R
Normal file
@@ -0,0 +1,7 @@
|
||||
#' @export a br code div em h1 h2 h3 h4 h5 h6 hr HTML img p pre span strong
|
||||
#' @export includeCSS includeHTML includeMarkdown includeScript includeText
|
||||
#' @export is.singleton singleton
|
||||
#' @export tag tagAppendAttributes tagAppendChild tagAppendChildren tagList tags tagSetChildren withTags
|
||||
#' @export validateCssUnit
|
||||
#' @export knit_print.html knit_print.shiny.tag knit_print.shiny.tag.list
|
||||
NULL
|
||||
136
R/image-interact-opts.R
Normal file
136
R/image-interact-opts.R
Normal file
@@ -0,0 +1,136 @@
|
||||
#' Create an object representing click options
|
||||
#'
|
||||
#' This generates an object representing click options, to be passed as the
|
||||
#' \code{click} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_click"},
|
||||
#' then the click coordinates will be available as \code{input$plot_click}.
|
||||
#' @param clip Should the click area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive click events even when the mouse is outside
|
||||
#' the plotting area, as long as it is still inside the image.
|
||||
#' @export
|
||||
clickOpts <- function(id = NULL, clip = TRUE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
clip = clip
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#' Create an object representing double-click options
|
||||
#'
|
||||
#' This generates an object representing dobule-click options, to be passed as
|
||||
#' the \code{dblclick} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is
|
||||
#' \code{"plot_dblclick"}, then the click coordinates will be available as
|
||||
#' \code{input$plot_dblclick}.
|
||||
#' @param clip Should the click area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive double-click events even when the mouse is
|
||||
#' outside the plotting area, as long as it is still inside the image.
|
||||
#' @param delay Maximum delay (in ms) between a pair clicks for them to be
|
||||
#' counted as a double-click.
|
||||
#' @export
|
||||
dblclickOpts <- function(id = NULL, clip = TRUE, delay = 400) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
clip = clip,
|
||||
delay = delay
|
||||
)
|
||||
}
|
||||
|
||||
#' Create an object representing hover options
|
||||
#'
|
||||
#' This generates an object representing hovering options, to be passed as the
|
||||
#' \code{hover} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_hover"},
|
||||
#' then the hover coordinates will be available as \code{input$plot_hover}.
|
||||
#' @param delay How long to delay (in milliseconds) when debouncing or
|
||||
#' throttling, before sending the mouse location to the server.
|
||||
#' @param delayType The type of algorithm for limiting the number of hover
|
||||
#' events. Use \code{"throttle"} to limit the number of hover events to one
|
||||
#' every \code{delay} milliseconds. Use \code{"debounce"} to suspend events
|
||||
#' while the cursor is moving, and wait until the cursor has been at rest for
|
||||
#' \code{delay} milliseconds before sending an event.
|
||||
#' @param clip Should the hover area be clipped to the plotting area? If FALSE,
|
||||
#' then the server will receive hover events even when the mouse is outside
|
||||
#' the plotting area, as long as it is still inside the image.
|
||||
#' @param nullOutside If \code{TRUE} (the default), the value will be set to
|
||||
#' \code{NULL} when the mouse exits the plotting area. If \code{FALSE}, the
|
||||
#' value will stop changing when the cursor exits the plotting area.
|
||||
#' @export
|
||||
hoverOpts <- function(id = NULL, delay = 300,
|
||||
delayType = c("debounce", "throttle"), clip = TRUE,
|
||||
nullOutside = TRUE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
delay = delay,
|
||||
delayType = match.arg(delayType),
|
||||
clip = clip,
|
||||
nullOutside = nullOutside
|
||||
)
|
||||
}
|
||||
|
||||
#' Create an object representing brushing options
|
||||
#'
|
||||
#' This generates an object representing brushing options, to be passed as the
|
||||
#' \code{brush} argument of \code{\link{imageOutput}} or
|
||||
#' \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' @param id Input value name. For example, if the value is \code{"plot_brush"},
|
||||
#' then the coordinates will be available as \code{input$plot_brush}.
|
||||
#' @param fill Fill color of the brush.
|
||||
#' @param stroke Outline color of the brush.
|
||||
#' @param opacity Opacity of the brush
|
||||
#' @param delay How long to delay (in milliseconds) when debouncing or
|
||||
#' throttling, before sending the brush data to the server.
|
||||
#' @param delayType The type of algorithm for limiting the number of brush
|
||||
#' events. Use \code{"throttle"} to limit the number of brush events to one
|
||||
#' every \code{delay} milliseconds. Use \code{"debounce"} to suspend events
|
||||
#' while the cursor is moving, and wait until the cursor has been at rest for
|
||||
#' \code{delay} milliseconds before sending an event.
|
||||
#' @param clip Should the brush area be clipped to the plotting area? If FALSE,
|
||||
#' then the user will be able to brush outside the plotting area, as long as
|
||||
#' it is still inside the image.
|
||||
#' @param direction The direction for brushing. If \code{"xy"}, the brush can be
|
||||
#' drawn and moved in both x and y directions. If \code{"x"}, or \code{"y"},
|
||||
#' the brush wil work horizontally or vertically.
|
||||
#' @param resetOnNew When a new image is sent to the browser (via
|
||||
#' \code{\link{renderImage}}), should the brush be reset? The default,
|
||||
#' \code{FALSE}, is useful if you want to update the plot while keeping the
|
||||
#' brush. Using \code{TRUE} is useful if you want to clear the brush whenever
|
||||
#' the plot is updated.
|
||||
#' @export
|
||||
brushOpts <- function(id = NULL, fill = "#9cf", stroke = "#036",
|
||||
opacity = 0.25, delay = 300,
|
||||
delayType = c("debounce", "throttle"), clip = TRUE,
|
||||
direction = c("xy", "x", "y"),
|
||||
resetOnNew = FALSE) {
|
||||
if (is.null(id))
|
||||
stop("id must not be NULL")
|
||||
|
||||
list(
|
||||
id = id,
|
||||
fill = fill,
|
||||
stroke = stroke,
|
||||
opacity = opacity,
|
||||
delay = delay,
|
||||
delayType = match.arg(delayType),
|
||||
clip = clip,
|
||||
direction = match.arg(direction),
|
||||
resetOnNew = resetOnNew
|
||||
)
|
||||
}
|
||||
437
R/image-interact.R
Normal file
437
R/image-interact.R
Normal file
@@ -0,0 +1,437 @@
|
||||
#' Find rows of data that are selected by a brush
|
||||
#'
|
||||
#' This function returns rows from a data frame which are under a brush used
|
||||
#' with \code{\link{plotOutput}}.
|
||||
#'
|
||||
#' It is also possible for this function to return all rows from the input data
|
||||
#' frame, but with an additional column \code{selected_}, which indicates which
|
||||
#' rows of the input data frame are selected by the brush (\code{TRUE} for
|
||||
#' selected, \code{FALSE} for not-selected). This is enabled by setting
|
||||
#' \code{allRows=TRUE} option.
|
||||
#'
|
||||
#' The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2}
|
||||
#' arguments specify which columns in the data correspond to the x variable, y
|
||||
#' variable, and panel variables of the plot. For example, if your plot is
|
||||
#' \code{plot(x=cars$speed, y=cars$dist)}, and your brush is named
|
||||
#' \code{"cars_brush"}, then you would use \code{brushedPoints(cars,
|
||||
#' input$cars_brush, "speed", "dist")}.
|
||||
#'
|
||||
#' For plots created with ggplot2, it should not be necessary to specify the
|
||||
#' column names; that information will already be contained in the brush,
|
||||
#' provided that variables are in the original data, and not computed. For
|
||||
#' example, with \code{ggplot(cars, aes(x=speed, y=dist)) + geom_point()}, you
|
||||
#' could use \code{brushedPoints(cars, input$cars_brush)}. If, however, you use
|
||||
#' a computed column, like \code{ggplot(cars, aes(x=speed/2, y=dist)) +
|
||||
#' geom_point()}, then it will not be able to automatically extract column names
|
||||
#' and filter on them. If you want to use this function to filter data, it is
|
||||
#' recommended that you not use computed columns; instead, modify the data
|
||||
#' first, and then make the plot with "raw" columns in the modified data.
|
||||
#'
|
||||
#' If a specified x or y column is a factor, then it will be coerced to an
|
||||
#' integer vector. If it is a character vector, then it will be coerced to a
|
||||
#' factor and then integer vector. This means that the brush will be considered
|
||||
#' to cover a given character/factor value when it covers the center value.
|
||||
#'
|
||||
#' If the brush is operating in just the x or y directions (e.g., with
|
||||
#' \code{brushOpts(direction = "x")}, then this function will filter out points
|
||||
#' using just the x or y variable, whichever is appropriate.
|
||||
#'
|
||||
#' @param brush The data from a brush, such as \code{input$plot_brush}.
|
||||
#' @param df A data frame from which to select rows.
|
||||
#' @param xvar,yvar A string with the name of the variable on the x or y axis.
|
||||
#' This must also be the name of a column in \code{df}. If absent, then this
|
||||
#' function will try to infer the variable from the brush (only works for
|
||||
#' ggplot2).
|
||||
#' @param panelvar1,panelvar2 Each of these is a string with the name of a panel
|
||||
#' variable. For example, if with ggplot2, you facet on a variable called
|
||||
#' \code{cyl}, then you can use \code{"cyl"} here. However, specifying the
|
||||
#' panel variable should not be necessary with ggplot2; Shiny should be able
|
||||
#' to auto-detect the panel variable.
|
||||
#' @param allRows If \code{FALSE} (the default) return a data frame containing
|
||||
#' the selected rows. If \code{TRUE}, the input data frame will have a new
|
||||
#' column, \code{selected_}, which indicates whether the row was inside the
|
||||
#' brush (\code{TRUE}) or outside the brush (\code{FALSE}).
|
||||
#'
|
||||
#' @seealso \code{\link{plotOutput}} for example usage.
|
||||
#' @export
|
||||
brushedPoints <- function(df, brush, xvar = NULL, yvar = NULL,
|
||||
panelvar1 = NULL, panelvar2 = NULL,
|
||||
allRows = FALSE) {
|
||||
if (is.null(brush)) {
|
||||
if (allRows)
|
||||
df$selected_ <- FALSE
|
||||
else
|
||||
df <- df[0, , drop = FALSE]
|
||||
|
||||
return(df)
|
||||
}
|
||||
|
||||
if (is.null(brush$xmin)) {
|
||||
stop("brushedPoints requires a brush object with xmin, xmax, ymin, and ymax.")
|
||||
}
|
||||
|
||||
# Which direction(s) the brush is selecting over. Direction can be 'x', 'y',
|
||||
# or 'xy'.
|
||||
use_x <- grepl("x", brush$direction)
|
||||
use_y <- grepl("y", brush$direction)
|
||||
|
||||
# Try to extract vars from brush object
|
||||
xvar <- xvar %OR% brush$mapping$x
|
||||
yvar <- yvar %OR% brush$mapping$y
|
||||
panelvar1 <- panelvar1 %OR% brush$mapping$panelvar1
|
||||
panelvar2 <- panelvar2 %OR% brush$mapping$panelvar2
|
||||
|
||||
# Filter out x and y values
|
||||
keep_rows <- rep(TRUE, nrow(df))
|
||||
if (use_x) {
|
||||
if (is.null(xvar))
|
||||
stop("brushedPoints: not able to automatically infer `xvar` from brush")
|
||||
# Extract data values from the data frame
|
||||
x <- asNumber(df[[xvar]])
|
||||
keep_rows <- keep_rows & (x >= brush$xmin & x <= brush$xmax)
|
||||
}
|
||||
if (use_y) {
|
||||
if (is.null(yvar))
|
||||
stop("brushedPoints: not able to automatically infer `yvar` from brush")
|
||||
y <- asNumber(df[[yvar]])
|
||||
keep_rows <- keep_rows & (y >= brush$ymin & y <= brush$ymax)
|
||||
}
|
||||
|
||||
# Find which rows are matches for the panel vars (if present)
|
||||
if (!is.null(panelvar1))
|
||||
keep_rows <- keep_rows & panelMatch(brush$panelvar1, df[[panelvar1]])
|
||||
if (!is.null(panelvar2))
|
||||
keep_rows <- keep_rows & panelMatch(brush$panelvar2, df[[panelvar2]])
|
||||
|
||||
if (allRows) {
|
||||
df$selected_ <- keep_rows
|
||||
df
|
||||
} else {
|
||||
df[keep_rows, , drop = FALSE]
|
||||
}
|
||||
}
|
||||
|
||||
# The `brush` data structure will look something like the examples below.
|
||||
# For base graphics, `mapping` is empty, and there are no panelvars:
|
||||
# List of 8
|
||||
# $ xmin : num 3.73
|
||||
# $ xmax : num 4.22
|
||||
# $ ymin : num 13.9
|
||||
# $ ymax : num 19.8
|
||||
# $ mapping: Named list()
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.36
|
||||
# ..$ right : num 5.58
|
||||
# ..$ bottom: num 9.46
|
||||
# ..$ top : num 34.8
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 58
|
||||
# ..$ right : num 429
|
||||
# ..$ bottom: num 226
|
||||
# ..$ top : num 58
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ direction: chr "y"
|
||||
#
|
||||
# For ggplot2, the mapping vars usually will be included, and if faceting is
|
||||
# used, they will be listed as panelvars:
|
||||
# List of 10
|
||||
# $ xmin : num 3.18
|
||||
# $ xmax : num 3.78
|
||||
# $ ymin : num 17.1
|
||||
# $ ymax : num 20.4
|
||||
# $ panelvar1: int 6
|
||||
# $ panelvar2: int 0
|
||||
# $ mapping :List of 4
|
||||
# ..$ x : chr "wt"
|
||||
# ..$ y : chr "mpg"
|
||||
# ..$ panelvar1: chr "cyl"
|
||||
# ..$ panelvar2: chr "am"
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.32
|
||||
# ..$ right : num 5.62
|
||||
# ..$ bottom: num 9.22
|
||||
# ..$ top : num 35.1
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 172
|
||||
# ..$ right : num 300
|
||||
# ..$ bottom: num 144
|
||||
# ..$ top : num 28.5
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ direction: chr "y"
|
||||
|
||||
|
||||
#'Find rows of data that are near a click/hover/double-click
|
||||
#'
|
||||
#'This function returns rows from a data frame which are near a click, hover, or
|
||||
#'double-click, when used with \code{\link{plotOutput}}. The rows will be sorted
|
||||
#'by their distance to the mouse event.
|
||||
#'
|
||||
#'It is also possible for this function to return all rows from the input data
|
||||
#'frame, but with an additional column \code{selected_}, which indicates which
|
||||
#'rows of the input data frame are selected by the brush (\code{TRUE} for
|
||||
#'selected, \code{FALSE} for not-selected). This is enabled by setting
|
||||
#'\code{allRows=TRUE} option. If this is used, the resulting data frame will not
|
||||
#'be sorted by distance to the mouse event.
|
||||
#'
|
||||
#'The \code{xvar}, \code{yvar}, \code{panelvar1}, and \code{panelvar2} arguments
|
||||
#'specify which columns in the data correspond to the x variable, y variable,
|
||||
#'and panel variables of the plot. For example, if your plot is
|
||||
#'\code{plot(x=cars$speed, y=cars$dist)}, and your click variable is named
|
||||
#'\code{"cars_click"}, then you would use \code{nearPoints(cars,
|
||||
#'input$cars_brush, "speed", "dist")}.
|
||||
#'
|
||||
#'@inheritParams brushedPoints
|
||||
#'@param coordinfo The data from a mouse event, such as \code{input$plot_click}.
|
||||
#'@param threshold A maxmimum distance to the click point; rows in the data
|
||||
#' frame where the distance to the click is less than \code{threshold} will be
|
||||
#' returned.
|
||||
#'@param maxpoints Maximum number of rows to return. If NULL (the default),
|
||||
#' return all rows that are within the threshold distance.
|
||||
#'@param addDist If TRUE, add a column named \code{dist_} that contains the
|
||||
#' distance from the coordinate to the point, in pixels. When no mouse event
|
||||
#' has yet occured, the value of \code{dist_} will be \code{NA}.
|
||||
#'@param allRows If \code{FALSE} (the default) return a data frame containing
|
||||
#' the selected rows. If \code{TRUE}, the input data frame will have a new
|
||||
#' column, \code{selected_}, which indicates whether the row was inside the
|
||||
#' selected by the mouse event (\code{TRUE}) or not (\code{FALSE}).
|
||||
#'
|
||||
#'@seealso \code{\link{plotOutput}} for more examples.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Note that in practice, these examples would need to go in reactives
|
||||
#' # or observers.
|
||||
#'
|
||||
#' # This would select all points within 5 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click)
|
||||
#'
|
||||
#' # Select just the nearest point within 10 pixels of the click
|
||||
#' nearPoints(mtcars, input$plot_click, threshold = 10, maxpoints = 1)
|
||||
#'
|
||||
#' }
|
||||
#'@export
|
||||
nearPoints <- function(df, coordinfo, xvar = NULL, yvar = NULL,
|
||||
panelvar1 = NULL, panelvar2 = NULL,
|
||||
threshold = 5, maxpoints = NULL, addDist = FALSE,
|
||||
allRows = FALSE) {
|
||||
if (is.null(coordinfo)) {
|
||||
if (addDist)
|
||||
df$dist_ <- NA_real_
|
||||
|
||||
if (allRows)
|
||||
df$selected_ <- FALSE
|
||||
else
|
||||
df <- df[0, , drop = FALSE]
|
||||
|
||||
return(df)
|
||||
}
|
||||
|
||||
if (is.null(coordinfo$x)) {
|
||||
stop("nearPoints requires a click/hover/double-click object with x and y values.")
|
||||
}
|
||||
|
||||
# Try to extract vars from coordinfo object
|
||||
xvar <- xvar %OR% coordinfo$mapping$x
|
||||
yvar <- yvar %OR% coordinfo$mapping$y
|
||||
panelvar1 <- panelvar1 %OR% coordinfo$mapping$panelvar1
|
||||
panelvar2 <- panelvar2 %OR% coordinfo$mapping$panelvar2
|
||||
|
||||
if (is.null(xvar))
|
||||
stop("nearPoints: not able to automatically infer `xvar` from coordinfo")
|
||||
if (is.null(yvar))
|
||||
stop("nearPoints: not able to automatically infer `yvar` from coordinfo")
|
||||
|
||||
# Extract data values from the data frame
|
||||
x <- asNumber(df[[xvar]])
|
||||
y <- asNumber(df[[yvar]])
|
||||
|
||||
# Get the pixel coordinates of the point
|
||||
coordPx <- scaleCoords(coordinfo$x, coordinfo$y, coordinfo)
|
||||
|
||||
# Get pixel coordinates of data points
|
||||
dataPx <- scaleCoords(x, y, coordinfo)
|
||||
|
||||
# Distances of data points to coordPx
|
||||
dists <- sqrt((dataPx$x - coordPx$x) ^ 2 + (dataPx$y - coordPx$y) ^ 2)
|
||||
|
||||
if (addDist)
|
||||
df$dist_ <- dists
|
||||
|
||||
keep_rows <- (dists <= threshold)
|
||||
|
||||
# Find which rows are matches for the panel vars (if present)
|
||||
if (!is.null(panelvar1))
|
||||
keep_rows <- keep_rows & panelMatch(coordinfo$panelvar1, df[[panelvar1]])
|
||||
if (!is.null(panelvar2))
|
||||
keep_rows <- keep_rows & panelMatch(coordinfo$panelvar2, df[[panelvar2]])
|
||||
|
||||
# Track the indices to keep
|
||||
keep_idx <- which(keep_rows)
|
||||
|
||||
# Order by distance
|
||||
dists <- dists[keep_idx]
|
||||
keep_idx <- keep_idx[order(dists)]
|
||||
|
||||
# Keep max number of rows
|
||||
if (!is.null(maxpoints) && length(keep_idx) > maxpoints) {
|
||||
keep_idx <- keep_idx[seq_len(maxpoints)]
|
||||
}
|
||||
|
||||
if (allRows) {
|
||||
# Add selected_ column if needed
|
||||
df$selected_ <- FALSE
|
||||
df$selected_[keep_idx] <- TRUE
|
||||
|
||||
} else {
|
||||
# If we don't keep all rows, return just the selected rows, sorted by
|
||||
# distance.
|
||||
df <- df[keep_idx, , drop = FALSE]
|
||||
}
|
||||
|
||||
df
|
||||
}
|
||||
|
||||
# The coordinfo data structure will look something like the examples below.
|
||||
# For base graphics, `mapping` is empty, and there are no panelvars:
|
||||
# List of 7
|
||||
# $ x : num 4.37
|
||||
# $ y : num 12
|
||||
# $ mapping: Named list()
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.36
|
||||
# ..$ right : num 5.58
|
||||
# ..$ bottom: num 9.46
|
||||
# ..$ top : num 34.8
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 58
|
||||
# ..$ right : num 429
|
||||
# ..$ bottom: num 226
|
||||
# ..$ top : num 58
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ .nonce : num 0.343
|
||||
#
|
||||
# For ggplot2, the mapping vars usually will be included, and if faceting is
|
||||
# used, they will be listed as panelvars:
|
||||
# List of 9
|
||||
# $ x : num 3.78
|
||||
# $ y : num 17.1
|
||||
# $ panelvar1: int 6
|
||||
# $ panelvar2: int 0
|
||||
# $ mapping :List of 4
|
||||
# ..$ x : chr "wt"
|
||||
# ..$ y : chr "mpg"
|
||||
# ..$ panelvar1: chr "cyl"
|
||||
# ..$ panelvar2: chr "am"
|
||||
# $ domain :List of 4
|
||||
# ..$ left : num 1.32
|
||||
# ..$ right : num 5.62
|
||||
# ..$ bottom: num 9.22
|
||||
# ..$ top : num 35.1
|
||||
# $ range :List of 4
|
||||
# ..$ left : num 172
|
||||
# ..$ right : num 300
|
||||
# ..$ bottom: num 144
|
||||
# ..$ top : num 28.5
|
||||
# $ log :List of 2
|
||||
# ..$ x: NULL
|
||||
# ..$ y: NULL
|
||||
# $ .nonce : num 0.603
|
||||
|
||||
|
||||
|
||||
# Coerce various types of variables to numbers. This works for Date, POSIXt,
|
||||
# characters, and factors. Used because the mouse coords are numeric.
|
||||
asNumber <- function(x) {
|
||||
if (is.character(x)) x <- as.factor(x)
|
||||
if (is.factor(x)) x <- as.integer(x)
|
||||
as.numeric(x)
|
||||
}
|
||||
|
||||
# Given a panelvar value and a vector x, return logical vector indicating which
|
||||
# items match the panelvar value. Because the panelvar value is always a
|
||||
# string but the vector could be numeric, it might be necessary to coerce the
|
||||
# panelvar to a number before comparing to the vector.
|
||||
panelMatch <- function(search_value, x) {
|
||||
if (is.numeric(x)) search_value <- as.numeric(search_value)
|
||||
x == search_value
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Scaling functions
|
||||
# These functions have direct analogs in Javascript code, except these are
|
||||
# vectorized for x and y.
|
||||
|
||||
# Map a value x from a domain to a range. If clip is true, clip it to the
|
||||
# range.
|
||||
mapLinear <- function(x, domainMin, domainMax, rangeMin, rangeMax, clip = TRUE) {
|
||||
factor <- (rangeMax - rangeMin) / (domainMax - domainMin)
|
||||
val <- x - domainMin
|
||||
newval <- (val * factor) + rangeMin
|
||||
|
||||
if (clip) {
|
||||
maxval <- max(rangeMax, rangeMin)
|
||||
minval <- min(rangeMax, rangeMin)
|
||||
newval[newval > maxval] <- maxval
|
||||
newval[newval < minval] <- minval
|
||||
}
|
||||
newval
|
||||
}
|
||||
|
||||
# Scale val from domain to range. If logbase is present, use log scaling.
|
||||
scale1D <- function(val, domainMin, domainMax, rangeMin, rangeMax,
|
||||
logbase = NULL, clip = TRUE) {
|
||||
if (!is.null(logbase))
|
||||
val <- log(val, logbase)
|
||||
mapLinear(val, domainMin, domainMax, rangeMin, rangeMax, clip)
|
||||
}
|
||||
|
||||
# Inverse scale val, from range to domain. If logbase is present, use inverse
|
||||
# log (power) transformation.
|
||||
scaleInv1D <- function(val, domainMin, domainMax, rangeMin, rangeMax,
|
||||
logbase = NULL, clip = TRUE) {
|
||||
res <- mapLinear(val, rangeMin, rangeMax, domainMin, domainMax, clip)
|
||||
if (!is.null(logbase))
|
||||
res <- logbase ^ res
|
||||
res
|
||||
}
|
||||
|
||||
# Scale x and y coordinates from domain to range, using information in
|
||||
# scaleinfo. scaleinfo must contain items $domain, $range, and $log. The
|
||||
# scaleinfo object corresponds to one element from the coordmap object generated
|
||||
# by getPrevPlotCoordmap or getGgplotCoordmap; it is the scaling information for
|
||||
# one panel in a plot.
|
||||
scaleCoords <- function(x, y, scaleinfo) {
|
||||
if (is.null(scaleinfo))
|
||||
return(NULL)
|
||||
|
||||
domain <- scaleinfo$domain
|
||||
range <- scaleinfo$range
|
||||
log <- scaleinfo$log
|
||||
|
||||
list(
|
||||
x = scale1D(x, domain$left, domain$right, range$left, range$right, log$x),
|
||||
y = scale1D(y, domain$bottom, domain$top, range$bottom, range$top, log$y)
|
||||
)
|
||||
}
|
||||
|
||||
# Inverse scale x and y coordinates from range to domain, using information in
|
||||
# scaleinfo.
|
||||
scaleInvCoords <- function(x, y, scaleinfo) {
|
||||
if (is.null(scaleinfo))
|
||||
return(NULL)
|
||||
|
||||
domain <- scaleinfo$domain
|
||||
range <- scaleinfo$range
|
||||
log <- scaleinfo$log
|
||||
|
||||
list(
|
||||
x = scaleInv1D(x, domain$left, domain$right, range$left, range$right, log$x),
|
||||
y = scaleInv1D(y, domain$bottom, domain$top, range$bottom, range$top, log$y)
|
||||
)
|
||||
}
|
||||
58
R/imageutils.R
Normal file
58
R/imageutils.R
Normal file
@@ -0,0 +1,58 @@
|
||||
#' Run a plotting function and save the output as a PNG
|
||||
#'
|
||||
#' This function returns the name of the PNG file that it generates. In
|
||||
#' essence, it calls \code{png()}, then \code{func()}, then \code{dev.off()}.
|
||||
#' So \code{func} must be a function that will generate a plot when used this
|
||||
#' way.
|
||||
#'
|
||||
#' For output, it will try to use the following devices, in this order:
|
||||
#' quartz (via \code{\link[grDevices]{png}}), then \code{\link[Cairo]{CairoPNG}},
|
||||
#' and finally \code{\link[grDevices]{png}}. This is in order of quality of
|
||||
#' output. Notably, plain \code{png} output on Linux and Windows may not
|
||||
#' antialias some point shapes, resulting in poor quality output.
|
||||
#'
|
||||
#' In some cases, \code{Cairo()} provides output that looks worse than
|
||||
#' \code{png()}. To disable Cairo output for an app, use
|
||||
#' \code{options(shiny.usecairo=FALSE)}.
|
||||
#'
|
||||
#' @param func A function that generates a plot.
|
||||
#' @param filename The name of the output file. Defaults to a temp file with
|
||||
#' extension \code{.png}.
|
||||
#' @param width Width in pixels.
|
||||
#' @param height Height in pixels.
|
||||
#' @param res Resolution in pixels per inch. This value is passed to
|
||||
#' \code{\link{png}}. Note that this affects the resolution of PNG rendering in
|
||||
#' R; it won't change the actual ppi of the browser.
|
||||
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#'
|
||||
#' @export
|
||||
plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
width=400, height=400, res=72, ...) {
|
||||
# If quartz is available, use png() (which will default to quartz).
|
||||
# Otherwise, if the Cairo package is installed, use CairoPNG().
|
||||
# Finally, if neither quartz nor Cairo, use png().
|
||||
if (capabilities("aqua")) {
|
||||
pngfun <- png
|
||||
} else if ((getOption('shiny.usecairo') %OR% TRUE) &&
|
||||
nchar(system.file(package = "Cairo"))) {
|
||||
pngfun <- Cairo::CairoPNG
|
||||
} else {
|
||||
pngfun <- png
|
||||
}
|
||||
|
||||
pngfun(filename=filename, width=width, height=height, res=res, ...)
|
||||
# Call plot.new() so that even if no plotting operations are performed at
|
||||
# least we have a blank background. N.B. we need to set the margin to 0
|
||||
# temporarily before plot.new() because when the plot size is small (e.g.
|
||||
# 200x50), we will get an error "figure margin too large", which is triggered
|
||||
# by plot.new() with the default (large) margin. However, this does not
|
||||
# guarantee user's code in func() will not trigger the error -- they may have
|
||||
# to set par(mar = smaller_value) before they draw base graphics.
|
||||
op <- par(mar = rep(0, 4))
|
||||
tryCatch(plot.new(), finally = par(op))
|
||||
dv <- dev.cur()
|
||||
tryCatch(shinyCallingHandlers(func()), finally = dev.off(dv))
|
||||
|
||||
filename
|
||||
}
|
||||
104
R/jqueryui.R
Normal file
104
R/jqueryui.R
Normal file
@@ -0,0 +1,104 @@
|
||||
#' Panel with absolute positioning
|
||||
#'
|
||||
#' Creates a panel whose contents are absolutely positioned.
|
||||
#'
|
||||
#' The \code{absolutePanel} function creates a \code{<div>} tag whose CSS
|
||||
#' position is set to \code{absolute} (or fixed if \code{fixed = TRUE}). The way
|
||||
#' absolute positioning works in HTML is that absolute coordinates are specified
|
||||
#' relative to its nearest parent element whose position is not set to
|
||||
#' \code{static} (which is the default), and if no such parent is found, then
|
||||
#' relative to the page borders. If you're not sure what that means, just keep
|
||||
#' in mind that you may get strange results if you use \code{absolutePanel} from
|
||||
#' inside of certain types of panels.
|
||||
#'
|
||||
#' The \code{fixedPanel} function is the same as \code{absolutePanel} with
|
||||
#' \code{fixed = TRUE}.
|
||||
#'
|
||||
#' The position (\code{top}, \code{left}, \code{right}, \code{bottom}) and size
|
||||
#' (\code{width}, \code{height}) parameters are all optional, but you should
|
||||
#' specify exactly two of \code{top}, \code{bottom}, and \code{height} and
|
||||
#' exactly two of \code{left}, \code{right}, and \code{width} for predictable
|
||||
#' results.
|
||||
#'
|
||||
#' Like most other distance parameters in Shiny, the position and size
|
||||
#' parameters take a number (interpreted as pixels) or a valid CSS size string,
|
||||
#' such as \code{"100px"} (100 pixels) or \code{"25\%"}.
|
||||
#'
|
||||
#' For arcane HTML reasons, to have the panel fill the page or parent you should
|
||||
#' specify \code{0} for \code{top}, \code{left}, \code{right}, and \code{bottom}
|
||||
#' rather than the more obvious \code{width = "100\%"} and \code{height =
|
||||
#' "100\%"}.
|
||||
#'
|
||||
#' @param ... Attributes (named arguments) or children (unnamed arguments) that
|
||||
#' should be included in the panel.
|
||||
#'
|
||||
#' @param top Distance between the top of the panel, and the top of the page or
|
||||
#' parent container.
|
||||
#' @param left Distance between the left side of the panel, and the left of the
|
||||
#' page or parent container.
|
||||
#' @param right Distance between the right side of the panel, and the right of
|
||||
#' the page or parent container.
|
||||
#' @param bottom Distance between the bottom of the panel, and the bottom of the
|
||||
#' page or parent container.
|
||||
#' @param width Width of the panel.
|
||||
#' @param height Height of the panel.
|
||||
#' @param draggable If \code{TRUE}, allows the user to move the panel by
|
||||
#' clicking and dragging.
|
||||
#' @param fixed Positions the panel relative to the browser window and prevents
|
||||
#' it from being scrolled with the rest of the page.
|
||||
#' @param cursor The type of cursor that should appear when the user mouses over
|
||||
#' the panel. Use \code{"move"} for a north-east-south-west icon,
|
||||
#' \code{"default"} for the usual cursor arrow, or \code{"inherit"} for the
|
||||
#' usual cursor behavior (including changing to an I-beam when the cursor is
|
||||
#' over text). The default is \code{"auto"}, which is equivalent to
|
||||
#' \code{ifelse(draggable, "move", "inherit")}.
|
||||
#' @return An HTML element or list of elements.
|
||||
#'
|
||||
#' @export
|
||||
absolutePanel <- function(...,
|
||||
top = NULL, left = NULL, right = NULL, bottom = NULL,
|
||||
width = NULL, height = NULL,
|
||||
draggable = FALSE, fixed = FALSE,
|
||||
cursor = c('auto', 'move', 'default', 'inherit')) {
|
||||
cssProps <- list(
|
||||
top = top,
|
||||
left = left,
|
||||
right = right,
|
||||
bottom = bottom,
|
||||
width = width,
|
||||
height = height
|
||||
)
|
||||
cssProps <- cssProps[!sapply(cssProps, is.null)]
|
||||
cssProps <- sapply(cssProps, validateCssUnit)
|
||||
cssProps[['position']] <- ifelse(fixed, 'fixed', 'absolute')
|
||||
cssProps[['cursor']] <- match.arg(cursor)
|
||||
if (identical(cssProps[['cursor']], 'auto'))
|
||||
cssProps[['cursor']] <- ifelse(draggable, 'move', 'inherit')
|
||||
|
||||
style <- paste(paste(names(cssProps), cssProps, sep = ':', collapse = ';'), ';', sep='')
|
||||
divTag <- tags$div(style=style, ...)
|
||||
if (isTRUE(draggable)) {
|
||||
divTag <- tagAppendAttributes(divTag, class='draggable')
|
||||
return(tagList(
|
||||
# IMPORTANT NOTE: If you update jqueryui, make sure you DON'T include the datepicker,
|
||||
# as it collides with our bootstrap datepicker!
|
||||
singleton(tags$head(tags$script(src='shared/jqueryui/1.10.4/jquery-ui.min.js'))),
|
||||
divTag,
|
||||
tags$script('$(".draggable").draggable();')
|
||||
))
|
||||
} else {
|
||||
return(divTag)
|
||||
}
|
||||
}
|
||||
|
||||
#' @rdname absolutePanel
|
||||
#' @export
|
||||
fixedPanel <- function(...,
|
||||
top = NULL, left = NULL, right = NULL, bottom = NULL,
|
||||
width = NULL, height = NULL,
|
||||
draggable = FALSE,
|
||||
cursor = c('auto', 'move', 'default', 'inherit')) {
|
||||
absolutePanel(..., top=top, left=left, right=right, bottom=bottom,
|
||||
width=width, height=height, draggable=draggable, cursor=match.arg(cursor),
|
||||
fixed=TRUE)
|
||||
}
|
||||
74
R/map.R
Normal file
74
R/map.R
Normal 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 <- R6Class(
|
||||
'Map',
|
||||
portable = FALSE,
|
||||
public = list(
|
||||
initialize = function() {
|
||||
private$env <- new.env(parent=emptyenv())
|
||||
},
|
||||
get = function(key) {
|
||||
env[[key]]
|
||||
},
|
||||
set = function(key, value) {
|
||||
env[[key]] <- value
|
||||
value
|
||||
},
|
||||
mset = function(...) {
|
||||
args <- list(...)
|
||||
if (length(args) == 0)
|
||||
return()
|
||||
|
||||
arg_names <- names(args)
|
||||
if (is.null(arg_names) || any(!nzchar(arg_names)))
|
||||
stop("All elements must be named")
|
||||
|
||||
list2env(args, envir = env)
|
||||
},
|
||||
remove = function(key) {
|
||||
if (!self$containsKey(key))
|
||||
return(NULL)
|
||||
|
||||
result <- env[[key]]
|
||||
rm(list=key, envir=env, inherits=FALSE)
|
||||
result
|
||||
},
|
||||
containsKey = function(key) {
|
||||
exists(key, envir=env, inherits=FALSE)
|
||||
},
|
||||
keys = function() {
|
||||
# Sadly, this is much faster than ls(), because it doesn't sort the keys.
|
||||
names(as.list(env, all.names=TRUE))
|
||||
},
|
||||
values = function() {
|
||||
as.list(env, all.names=TRUE)
|
||||
},
|
||||
clear = function() {
|
||||
private$env <- new.env(parent=emptyenv())
|
||||
invisible(NULL)
|
||||
},
|
||||
size = function() {
|
||||
length(env)
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
env = 'environment'
|
||||
)
|
||||
)
|
||||
|
||||
as.list.Map <- function(map) {
|
||||
map$values()
|
||||
}
|
||||
length.Map <- function(map) {
|
||||
map$size()
|
||||
}
|
||||
73
R/middleware-shiny.R
Normal file
73
R/middleware-shiny.R
Normal file
@@ -0,0 +1,73 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
reactLogHandler <- function(req) {
|
||||
if (!identical(req$PATH_INFO, '/reactlog'))
|
||||
return(NULL)
|
||||
|
||||
if (!isTRUE(getOption('shiny.reactlog'))) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
return(httpResponse(
|
||||
status=200,
|
||||
content=list(file=renderReactLog(), owned=TRUE)
|
||||
))
|
||||
}
|
||||
|
||||
sessionHandler <- function(req) {
|
||||
path <- req$PATH_INFO
|
||||
if (is.null(path))
|
||||
return(NULL)
|
||||
|
||||
matches <- regmatches(path, regexec('^(/session/([0-9a-f]+))(/.*)$', path))
|
||||
if (length(matches[[1]]) == 0)
|
||||
return(NULL)
|
||||
|
||||
session <- matches[[1]][3]
|
||||
subpath <- matches[[1]][4]
|
||||
|
||||
shinysession <- appsByToken$get(session)
|
||||
if (is.null(shinysession))
|
||||
return(NULL)
|
||||
|
||||
subreq <- as.environment(as.list(req, all.names=TRUE))
|
||||
subreq$PATH_INFO <- subpath
|
||||
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, matches[[1]][2], sep='')
|
||||
|
||||
withReactiveDomain(shinysession, {
|
||||
shinysession$handleRequest(subreq)
|
||||
})
|
||||
}
|
||||
|
||||
dynamicHandler <- function(filePath, dependencyFiles=filePath) {
|
||||
lastKnownTimestamps <- NA
|
||||
metaHandler <- function(req) NULL
|
||||
|
||||
if (!file.exists(filePath))
|
||||
return(metaHandler)
|
||||
|
||||
cacheContext <- CacheContext$new()
|
||||
|
||||
return (function(req) {
|
||||
# Check if we need to rebuild
|
||||
if (cacheContext$isDirty()) {
|
||||
cacheContext$reset()
|
||||
for (dep in dependencyFiles)
|
||||
cacheContext$addDependencyFile(dep)
|
||||
|
||||
clearClients()
|
||||
if (file.exists(filePath)) {
|
||||
local({
|
||||
cacheContext$with(function() {
|
||||
sys.source(filePath, envir=new.env(parent=globalenv()), keep.source=TRUE)
|
||||
})
|
||||
})
|
||||
}
|
||||
metaHandler <<- joinHandlers(.globals$clients)
|
||||
clearClients()
|
||||
}
|
||||
|
||||
return(metaHandler(req))
|
||||
})
|
||||
}
|
||||
360
R/middleware.R
Normal file
360
R/middleware.R
Normal file
@@ -0,0 +1,360 @@
|
||||
# This file contains a general toolkit for routing and combining bits of
|
||||
# HTTP-handling logic. It is similar in spirit to Rook (and Rack, and WSGI, and
|
||||
# Connect, and...) but adds cascading and routing.
|
||||
#
|
||||
# This file is called "middleware" because that's the term used for these bits
|
||||
# of logic in these other frameworks. However, our code uses the word "handler"
|
||||
# so we'll stick to that for the rest of this document; just know that they're
|
||||
# basically the same concept.
|
||||
#
|
||||
# ## Intro to handlers
|
||||
#
|
||||
# A **handler** (or sometimes, **httpHandler**) is a function that takes a
|
||||
# `req` parameter--a request object as described in the Rook specification--and
|
||||
# returns `NULL`, or an `httpResponse`.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
httpResponse <- function(status = 200,
|
||||
content_type = "text/html; charset=UTF-8",
|
||||
content = "",
|
||||
headers = list()) {
|
||||
# Make sure it's a list, not a vector
|
||||
headers <- as.list(headers)
|
||||
if (is.null(headers$`X-UA-Compatible`))
|
||||
headers$`X-UA-Compatible` <- "IE=edge,chrome=1"
|
||||
resp <- list(status = status, content_type = content_type, content = content,
|
||||
headers = headers)
|
||||
class(resp) <- 'httpResponse'
|
||||
return(resp)
|
||||
}
|
||||
|
||||
#
|
||||
# You can think of a web application as being simply an aggregation of these
|
||||
# functions, each of which performs one kind of duty. Each handler in turn gets
|
||||
# a look at the request and can decide whether it knows how to handle it. If
|
||||
# so, it returns an `httpResponse` and processing terminates; if not, it
|
||||
# returns `NULL` and the next handler gets to execute. If the final handler
|
||||
# returns `NULL`, a 404 response should be returned.
|
||||
#
|
||||
# We have a similar construct for websockets: **websocket handlers** or
|
||||
# **wsHandlers**. These take a single `ws` argument which is the websocket
|
||||
# connection that was just opened, and they can either return `TRUE` if they
|
||||
# are handling the connection, and `NULL` to pass responsibility on to the next
|
||||
# wsHandler.
|
||||
#
|
||||
# ### Combining handlers
|
||||
#
|
||||
# Since it's so common for httpHandlers to be invoked in this "cascading"
|
||||
# fashion, we'll introduce a function that takes zero or more handlers and
|
||||
# returns a single handler. And while we're at it, making a directory of static
|
||||
# content available is such a common thing to do, we'll allow strings
|
||||
# representing paths to be used instead of handlers; any such strings we
|
||||
# encounter will be converted into `staticHandler` objects.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
joinHandlers <- function(handlers) {
|
||||
# Zero handlers; return a null handler
|
||||
if (length(handlers) == 0)
|
||||
return(function(req) NULL)
|
||||
|
||||
# Just one handler (function)? Return it.
|
||||
if (is.function(handlers))
|
||||
return(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(req) NULL)
|
||||
if (length(handlers) == 1)
|
||||
return(handlers[[1]])
|
||||
|
||||
function(req) {
|
||||
for (handler in handlers) {
|
||||
response <- handler(req)
|
||||
if (!is.null(response))
|
||||
return(response)
|
||||
}
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Note that we don't have an equivalent of `joinHandlers` for wsHandlers. It's
|
||||
# easy to imagine it, we just haven't needed one.
|
||||
#
|
||||
# ### Handler routing
|
||||
#
|
||||
# Handlers do not have a built-in notion of routing. Conceptually, given a list
|
||||
# of handlers, all the handlers are peers and they all get to see every request
|
||||
# (well, up until the point that a handler returns a response).
|
||||
#
|
||||
# You could implement routing in each handler by checking the request's
|
||||
# `PATH_INFO` field, but since it's such a common need, let's make it simple by
|
||||
# introducing a `routeHandler` function. This is a handler
|
||||
# [decorator](http://en.wikipedia.org/wiki/Decorator_pattern) and it's
|
||||
# responsible for 1) filtering out requests that don't match the given route,
|
||||
# and 2) temporarily modifying the request object to take the matched part of
|
||||
# the route off of the `PATH_INFO` (and add it to the end of `SCRIPT_NAME`).
|
||||
# This way, the handler doesn't need to figure out about what part of its URL
|
||||
# path has already been matched via routing.
|
||||
#
|
||||
# (BTW, it's safe for `routeHandler` calls to nest.)
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
routeHandler <- function(prefix, handler) {
|
||||
force(prefix)
|
||||
force(handler)
|
||||
|
||||
if (identical("", prefix))
|
||||
return(handler)
|
||||
|
||||
if (length(prefix) != 1 || !isTRUE(grepl("^/[^\\]+$", prefix))) {
|
||||
stop("Invalid URL prefix \"", prefix, "\"")
|
||||
}
|
||||
|
||||
pathPattern <- paste("^\\Q", prefix, "\\E/", sep = "")
|
||||
function(req) {
|
||||
if (isTRUE(grepl(pathPattern, req$PATH_INFO))) {
|
||||
origScript <- req$SCRIPT_NAME
|
||||
origPath <- req$PATH_INFO
|
||||
on.exit({
|
||||
req$SCRIPT_NAME <- origScript
|
||||
req$PATH_INFO <- origPath
|
||||
}, add = TRUE)
|
||||
pathInfo <- substr(req$PATH_INFO, nchar(prefix)+1, nchar(req$PATH_INFO))
|
||||
req$SCRIPT_NAME <- paste(req$SCRIPT_NAME, prefix, sep = "")
|
||||
req$PATH_INFO <- pathInfo
|
||||
return(handler(req))
|
||||
} else {
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# We have a version for websocket handlers as well. Pity about the copy/paste
|
||||
# job.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
routeWSHandler <- function(prefix, wshandler) {
|
||||
force(prefix)
|
||||
force(wshandler)
|
||||
|
||||
if (identical("", prefix))
|
||||
return(wshandler)
|
||||
|
||||
if (length(prefix) != 1 || !isTRUE(grepl("^/[^\\]+$", prefix))) {
|
||||
stop("Invalid URL prefix \"", prefix, "\"")
|
||||
}
|
||||
|
||||
pathPattern <- paste("^\\Q", prefix, "\\E/", sep = "")
|
||||
function(ws) {
|
||||
req <- ws$request
|
||||
if (isTRUE(grepl(pathPattern, req$PATH_INFO))) {
|
||||
origScript <- req$SCRIPT_NAME
|
||||
origPath <- req$PATH_INFO
|
||||
on.exit({
|
||||
req$SCRIPT_NAME <- origScript
|
||||
req$PATH_INFO <- origPath
|
||||
}, add = TRUE)
|
||||
pathInfo <- substr(req$PATH_INFO, nchar(prefix)+1, nchar(req$PATH_INFO))
|
||||
req$SCRIPT_NAME <- paste(req$SCRIPT_NAME, prefix, sep = "")
|
||||
req$PATH_INFO <- pathInfo
|
||||
return(wshandler(ws))
|
||||
} else {
|
||||
return(NULL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# ### Handler implementations
|
||||
#
|
||||
# Now let's actually write some handlers. Note that these functions aren't
|
||||
# *themselves* handlers, you call them and they *return* a handler. Handler
|
||||
# factory functions, if you will.
|
||||
#
|
||||
# Here's one that serves up static assets from a directory.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
staticHandler <- function(root) {
|
||||
force(root)
|
||||
return(function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
path <- req$PATH_INFO
|
||||
|
||||
if (is.null(path))
|
||||
return(httpResponse(400, content="<h1>Bad Request</h1>"))
|
||||
|
||||
if (path == '/')
|
||||
path <- '/index.html'
|
||||
|
||||
abs.path <- resolve(root, path)
|
||||
if (is.null(abs.path))
|
||||
return(NULL)
|
||||
|
||||
content.type <- getContentType(abs.path)
|
||||
response.content <- readBin(abs.path, 'raw', n=file.info(abs.path)$size)
|
||||
return(httpResponse(200, content.type, response.content))
|
||||
})
|
||||
}
|
||||
|
||||
#
|
||||
# ## Handler manager
|
||||
#
|
||||
# The handler manager gives you a place to register handlers (of both http and
|
||||
# websocket varieties) and provides an httpuv-compatible set of callbacks for
|
||||
# invoking them.
|
||||
#
|
||||
# Create one of these, make zero or more calls to `addHandler` and
|
||||
# `addWSHandler` methods (order matters--first one wins!), and then pass the
|
||||
# return value of `createHttpuvApp` to httpuv's `startServer` function.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
HandlerList <- R6Class("HandlerList",
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
handlers = list(),
|
||||
|
||||
add = function(handler, key, tail = FALSE) {
|
||||
if (!is.null(handlers[[key]]))
|
||||
stop("Key ", key, " already in use")
|
||||
newList <- structure(names=key, list(handler))
|
||||
|
||||
if (length(handlers) == 0)
|
||||
handlers <<- newList
|
||||
else if (tail)
|
||||
handlers <<- c(handlers, newList)
|
||||
else
|
||||
handlers <<- c(newList, handlers)
|
||||
},
|
||||
remove = function(key) {
|
||||
handlers[key] <<- NULL
|
||||
},
|
||||
clear = function() {
|
||||
handlers <<- list()
|
||||
},
|
||||
invoke = function(...) {
|
||||
for (handler in handlers) {
|
||||
result <- handler(...)
|
||||
if (!is.null(result))
|
||||
return(result)
|
||||
}
|
||||
return(NULL)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
HandlerManager <- R6Class("HandlerManager",
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
handlers = "HandlerList",
|
||||
wsHandlers = "HandlerList",
|
||||
|
||||
initialize = function() {
|
||||
handlers <<- HandlerList$new()
|
||||
wsHandlers <<- HandlerList$new()
|
||||
},
|
||||
|
||||
addHandler = function(handler, key, tail = FALSE) {
|
||||
handlers$add(handler, key, tail)
|
||||
},
|
||||
removeHandler = function(key) {
|
||||
handlers$remove(key)
|
||||
},
|
||||
addWSHandler = function(wsHandler, key, tail = FALSE) {
|
||||
wsHandlers$add(wsHandler, key, tail)
|
||||
},
|
||||
removeWSHandler = function(key) {
|
||||
wsHandlers$remove(key)
|
||||
},
|
||||
clear = function() {
|
||||
handlers$clear()
|
||||
wsHandlers$clear()
|
||||
},
|
||||
createHttpuvApp = function() {
|
||||
list(
|
||||
onHeaders = function(req) {
|
||||
maxSize <- getOption('shiny.maxRequestSize') %OR% (5 * 1024 * 1024)
|
||||
if (maxSize <= 0)
|
||||
return(NULL)
|
||||
|
||||
reqSize <- 0
|
||||
if (length(req$CONTENT_LENGTH) > 0)
|
||||
reqSize <- as.numeric(req$CONTENT_LENGTH)
|
||||
else if (length(req$HTTP_TRANSFER_ENCODING) > 0)
|
||||
reqSize <- Inf
|
||||
|
||||
if (reqSize > maxSize) {
|
||||
return(list(status = 413L,
|
||||
headers = list(
|
||||
'Content-Type' = 'text/plain'
|
||||
),
|
||||
body = 'Maximum upload size exceeded'))
|
||||
}
|
||||
else {
|
||||
return(NULL)
|
||||
}
|
||||
},
|
||||
call = .httpServer(
|
||||
function (req) {
|
||||
return(handlers$invoke(req))
|
||||
},
|
||||
getOption('shiny.sharedSecret')
|
||||
),
|
||||
onWSOpen = function(ws) {
|
||||
return(wsHandlers$invoke(ws))
|
||||
}
|
||||
)
|
||||
},
|
||||
.httpServer = function(handler, sharedSecret) {
|
||||
filter <- getOption('shiny.http.response.filter')
|
||||
if (is.null(filter))
|
||||
filter <- function(req, response) response
|
||||
|
||||
function(req) {
|
||||
if (!is.null(sharedSecret)
|
||||
&& !identical(sharedSecret, req$HTTP_SHINY_SHARED_SECRET)) {
|
||||
return(list(status=403,
|
||||
body='<h1>403 Forbidden</h1><p>Shared secret mismatch</p>',
|
||||
headers=list('Content-Type' = 'text/html')))
|
||||
}
|
||||
|
||||
response <- handler(req)
|
||||
if (is.null(response))
|
||||
response <- httpResponse(404, content="<h1>Not Found</h1>")
|
||||
|
||||
if (inherits(response, "httpResponse")) {
|
||||
headers <- as.list(response$headers)
|
||||
headers$'Content-Type' <- response$content_type
|
||||
|
||||
response <- filter(req, response)
|
||||
return(list(status=response$status,
|
||||
body=response$content,
|
||||
headers=headers))
|
||||
} else {
|
||||
# Assume it's a Rook-compatible response
|
||||
return(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
#
|
||||
# ## Next steps
|
||||
#
|
||||
# See server.R and middleware-shiny.R to see actual implementation and usage of
|
||||
# handlers in the context of Shiny.
|
||||
114
R/priorityqueue.R
Normal file
114
R/priorityqueue.R
Normal file
@@ -0,0 +1,114 @@
|
||||
# "...like a regular queue or stack data structure, but where additionally each
|
||||
# element has a "priority" associated with it. In a priority queue, an element
|
||||
# with high priority is served before an element with low priority. If two
|
||||
# elements have the same priority, they are served according to their order in
|
||||
# the queue." (http://en.wikipedia.org/wiki/Priority_queue)
|
||||
|
||||
PriorityQueue <- R6Class(
|
||||
'PriorityQueue',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
# Keys are priorities, values are subqueues (implemented as list)
|
||||
.itemsByPriority = 'Map',
|
||||
# Sorted vector (largest first)
|
||||
.priorities = numeric(0),
|
||||
|
||||
initialize = function() {
|
||||
.itemsByPriority <<- Map$new()
|
||||
},
|
||||
# Enqueue an item, with the given priority level (must be integer). Higher
|
||||
# priority numbers are dequeued earlier than lower.
|
||||
enqueue = function(item, priority) {
|
||||
priority <- normalizePriority(priority)
|
||||
|
||||
if (!(priority %in% .priorities)) {
|
||||
.priorities <<- c(.priorities, priority)
|
||||
.priorities <<- sort(.priorities, decreasing=TRUE)
|
||||
.itemsByPriority$set(.key(priority), list(item))
|
||||
} else {
|
||||
.itemsByPriority$set(
|
||||
.key(priority),
|
||||
c(.itemsByPriority$get(.key(priority)), item)
|
||||
)
|
||||
}
|
||||
return(invisible())
|
||||
},
|
||||
# Retrieve a single item by 1) priority number (highest first) and then 2)
|
||||
# insertion order (first in, first out). If there are no items to be
|
||||
# dequeued, then NULL is returned. If it is necessary to distinguish between
|
||||
# a NULL value and the empty case, call isEmpty() before dequeue().
|
||||
dequeue = function() {
|
||||
if (length(.priorities) == 0)
|
||||
return(NULL)
|
||||
|
||||
maxPriority <- .priorities[[1]]
|
||||
items <- .itemsByPriority$get(.key(maxPriority))
|
||||
firstItem <- items[[1]]
|
||||
if (length(items) == 1) {
|
||||
# This is the last item at this priority. Remove both the list and the
|
||||
# priority level.
|
||||
.itemsByPriority$remove(.key(maxPriority))
|
||||
.priorities <<- .priorities[-1]
|
||||
} else {
|
||||
# There are still items at this priority. Remove the current item from
|
||||
# the list, and save it.
|
||||
items <- items[-1]
|
||||
.itemsByPriority$set(.key(maxPriority), items)
|
||||
}
|
||||
return(firstItem)
|
||||
},
|
||||
# Returns TRUE if no items are in the queue, otherwise FALSE.
|
||||
isEmpty = function() {
|
||||
length(.priorities) == 0
|
||||
},
|
||||
# Translates a priority integer to a character that is suitable for using as
|
||||
# a key.
|
||||
.key = function(priority) {
|
||||
sprintf('%a', priority)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
normalizePriority <- function(priority) {
|
||||
|
||||
if (is.null(priority))
|
||||
priority <- 0
|
||||
|
||||
# Cast integers to numeric to prevent any inconsistencies
|
||||
if (is.integer(priority))
|
||||
priority <- as.numeric(priority)
|
||||
|
||||
if (!is.numeric(priority))
|
||||
stop('priority must be an integer or numeric')
|
||||
|
||||
# Check length
|
||||
if (length(priority) == 0) {
|
||||
warning('Zero-length priority vector was passed; using 0')
|
||||
priority <- 0
|
||||
} else if (length(priority) > 1) {
|
||||
warning('Priority has length > 1 and only the first element will be used')
|
||||
priority <- priority[1]
|
||||
}
|
||||
|
||||
# NA == 0
|
||||
if (is.na(priority))
|
||||
priority <- 0
|
||||
|
||||
return(priority)
|
||||
}
|
||||
|
||||
# pq <- PriorityQueue$new()
|
||||
# pq$enqueue('a', 1)
|
||||
# pq$enqueue('b', 1L)
|
||||
# pq$enqueue('c', 1)
|
||||
# pq$enqueue('A', 2)
|
||||
# pq$enqueue('B', 2L)
|
||||
# pq$enqueue('C', 2)
|
||||
# pq$enqueue('d', 1)
|
||||
# pq$enqueue('e', 1L)
|
||||
# pq$enqueue('f', 1)
|
||||
# pq$enqueue('D', 2)
|
||||
# pq$enqueue('E', 2L)
|
||||
# pq$enqueue('F', 2)
|
||||
# # Expect ABCDEFabcdef
|
||||
283
R/progress.R
Normal file
283
R/progress.R
Normal file
@@ -0,0 +1,283 @@
|
||||
#' Reporting progress (object-oriented API)
|
||||
#'
|
||||
#' Reports progress to the user during long-running operations.
|
||||
#'
|
||||
#' This package exposes two distinct programming APIs for working with
|
||||
#' progress. \code{\link{withProgress}} and \code{\link{setProgress}}
|
||||
#' together provide a simple function-based interface, while the
|
||||
#' \code{Progress} reference class provides an object-oriented API.
|
||||
#'
|
||||
#' Instantiating a \code{Progress} object causes a progress panel to be
|
||||
#' created, and it will be displayed the first time the \code{set}
|
||||
#' method is called. Calling \code{close} will cause the progress panel
|
||||
#' to be removed.
|
||||
#'
|
||||
#' \strong{Methods}
|
||||
#' \describe{
|
||||
#' \item{\code{initialize(session, min = 0, max = 1)}}{
|
||||
#' Creates a new progress panel (but does not display it).
|
||||
#' }
|
||||
#' \item{\code{set(value = NULL, message = NULL, detail = NULL)}}{
|
||||
#' Updates the progress panel. When called the first time, the
|
||||
#' progress panel is displayed.
|
||||
#' }
|
||||
#' \item{\code{inc(amount = 0.1, message = NULL, detail = NULL)}}{
|
||||
#' Like \code{set}, this updates the progress panel. The difference is
|
||||
#' that \code{inc} increases the progress bar by \code{amount}, instead
|
||||
#' of setting it to a specific value.
|
||||
#' }
|
||||
#' \item{\code{close()}}{
|
||||
#' Removes the progress panel. Future calls to \code{set} and
|
||||
#' \code{close} will be ignored.
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#' @param session The Shiny session object, as provided by
|
||||
#' \code{shinyServer} to the server function.
|
||||
#' @param min The value that represents the starting point of the
|
||||
#' progress bar. Must be less tham \code{max}.
|
||||
#' @param max The value that represents the end of the progress bar.
|
||||
#' Must be greater than \code{min}.
|
||||
#' @param message A single-element character vector; the message to be
|
||||
#' displayed to the user, or \code{NULL} to hide the current message
|
||||
#' (if any).
|
||||
#' @param detail A single-element character vector; the detail message
|
||||
#' to be displayed to the user, or \code{NULL} to hide the current
|
||||
#' detail message (if any). The detail message will be shown with a
|
||||
#' de-emphasized appearance relative to \code{message}.
|
||||
#' @param value A numeric value at which to set
|
||||
#' the progress bar, relative to \code{min} and \code{max}.
|
||||
#' \code{NULL} hides the progress bar, if it is currently visible.
|
||||
#' @param amount Single-element numeric vector; the value at which to set
|
||||
#' the progress bar, relative to \code{min} and \code{max}.
|
||||
#' \code{NULL} hides the progress bar, if it is currently visible.
|
||||
#' @param amount For the \code{inc()} method, a numeric value to increment the
|
||||
#' progress bar.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # server.R
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' progress <- shiny::Progress$new(session, min=1, max=15)
|
||||
#' on.exit(progress$close())
|
||||
#'
|
||||
#' progress$set(message = 'Calculation in progress',
|
||||
#' detail = 'This may take a while...')
|
||||
#'
|
||||
#' for (i in 1:15) {
|
||||
#' progress$set(value = i)
|
||||
#' Sys.sleep(0.5)
|
||||
#' }
|
||||
#' plot(cars)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @seealso \code{\link{withProgress}}
|
||||
#' @format NULL
|
||||
#' @usage NULL
|
||||
#' @export
|
||||
Progress <- R6Class(
|
||||
'Progress',
|
||||
portable = TRUE,
|
||||
public = list(
|
||||
|
||||
initialize = function(session = getDefaultReactiveDomain(), min = 0, max = 1) {
|
||||
if (!inherits(session, "ShinySession"))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
private$session <- session
|
||||
private$id <- paste(as.character(as.raw(runif(8, min=0, max=255))), collapse='')
|
||||
private$min <- min
|
||||
private$max <- max
|
||||
private$value <- NULL
|
||||
private$closed <- FALSE
|
||||
|
||||
session$sendProgress('open', list(id = private$id))
|
||||
},
|
||||
|
||||
set = function(value = NULL, message = NULL, detail = NULL) {
|
||||
if (private$closed) {
|
||||
warning("Attempting to set progress, but progress already closed.")
|
||||
return()
|
||||
}
|
||||
|
||||
if (is.null(value) || is.na(value)) {
|
||||
value <- NULL
|
||||
} else {
|
||||
# Normalize value to number between 0 and 1
|
||||
value <- min(1, max(0, (value - private$min) / (private$max - private$min)))
|
||||
}
|
||||
|
||||
private$value <- value
|
||||
|
||||
data <- dropNulls(list(
|
||||
id = private$id,
|
||||
message = message,
|
||||
detail = detail,
|
||||
value = value
|
||||
))
|
||||
|
||||
private$session$sendProgress('update', data)
|
||||
},
|
||||
|
||||
inc = function(amount = 0.1, message = NULL, detail = NULL) {
|
||||
value <- min(self$getValue() + amount, private$max)
|
||||
self$set(value, message, detail)
|
||||
},
|
||||
|
||||
getMin = function() private$min,
|
||||
|
||||
getMax = function() private$max,
|
||||
|
||||
# Return value (not the normalized 0-1 value, but in the original range)
|
||||
getValue = function() {
|
||||
private$value * (private$max - private$min) + private$min
|
||||
},
|
||||
|
||||
close = function() {
|
||||
if (private$closed) {
|
||||
warning("Attempting to close progress, but progress already closed.")
|
||||
return()
|
||||
}
|
||||
|
||||
private$session$sendProgress('close', list(id = private$id))
|
||||
private$closed <- TRUE
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
session = 'environment',
|
||||
id = character(0),
|
||||
min = numeric(0),
|
||||
max = numeric(0),
|
||||
value = NULL,
|
||||
closed = logical(0)
|
||||
)
|
||||
)
|
||||
|
||||
#' Reporting progress (functional API)
|
||||
#'
|
||||
#' Reports progress to the user during long-running operations.
|
||||
#'
|
||||
#' This package exposes two distinct programming APIs for working with progress.
|
||||
#' Using \code{withProgress} with \code{incProgress} or \code{setProgress}
|
||||
#' provide a simple function-based interface, while the \code{\link{Progress}}
|
||||
#' reference class provides an object-oriented API.
|
||||
#'
|
||||
#' Use \code{withProgress} to wrap the scope of your work; doing so will cause a
|
||||
#' new progress panel to be created, and it will be displayed the first time
|
||||
#' \code{incProgress} or \code{setProgress} are called. When \code{withProgress}
|
||||
#' exits, the corresponding progress panel will be removed.
|
||||
#'
|
||||
#' The \code{incProgress} function increments the status bar by a specified
|
||||
#' amount, whereas the \code{setProgress} function sets it to a specific value,
|
||||
#' and can also set the text displayed.
|
||||
#'
|
||||
#' Generally, \code{withProgress}/\code{incProgress}/\code{setProgress} should
|
||||
#' be sufficient; the exception is if the work to be done is asynchronous (this
|
||||
#' is not common) or otherwise cannot be encapsulated by a single scope. In that
|
||||
#' case, you can use the \code{Progress} reference class.
|
||||
#'
|
||||
#' @param session The Shiny session object, as provided by \code{shinyServer} to
|
||||
#' the server function. The default is to automatically find the session by
|
||||
#' using the current reactive domain.
|
||||
#' @param expr The work to be done. This expression should contain calls to
|
||||
#' \code{setProgress}.
|
||||
#' @param min The value that represents the starting point of the progress bar.
|
||||
#' Must be less tham \code{max}. Default is 0.
|
||||
#' @param max The value that represents the end of the progress bar. Must be
|
||||
#' greater than \code{min}. Default is 1.
|
||||
#' @param amount For \code{incProgress}, the amount to increment the status bar.
|
||||
#' Default is 0.1.
|
||||
#' @param env The environment in which \code{expr} should be evaluated.
|
||||
#' @param quoted Whether \code{expr} is a quoted expression (this is not
|
||||
#' common).
|
||||
#' @param message A single-element character vector; the message to be displayed
|
||||
#' to the user, or \code{NULL} to hide the current message (if any).
|
||||
#' @param detail A single-element character vector; the detail message to be
|
||||
#' displayed to the user, or \code{NULL} to hide the current detail message
|
||||
#' (if any). The detail message will be shown with a de-emphasized appearance
|
||||
#' relative to \code{message}.
|
||||
#' @param value Single-element numeric vector; the value at which to set the
|
||||
#' progress bar, relative to \code{min} and \code{max}. \code{NULL} hides the
|
||||
#' progress bar, if it is currently visible.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # server.R
|
||||
#' shinyServer(function(input, output) {
|
||||
#' output$plot <- renderPlot({
|
||||
#' withProgress(message = 'Calculation in progress',
|
||||
#' detail = 'This may take a while...', value = 0, {
|
||||
#' for (i in 1:15) {
|
||||
#' incProgress(1/15)
|
||||
#' Sys.sleep(0.25)
|
||||
#' }
|
||||
#' })
|
||||
#' plot(cars)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @seealso \code{\link{Progress}}
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
withProgress <- function(expr, min = 0, max = 1,
|
||||
value = min + (max - min) * 0.1,
|
||||
message = NULL, detail = NULL,
|
||||
session = getDefaultReactiveDomain(),
|
||||
env = parent.frame(), quoted = FALSE) {
|
||||
|
||||
if (!quoted)
|
||||
expr <- substitute(expr)
|
||||
|
||||
if (!inherits(session, "ShinySession"))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
p <- Progress$new(session, min = min, max = max)
|
||||
|
||||
session$progressStack$push(p)
|
||||
on.exit({
|
||||
session$progressStack$pop()
|
||||
p$close()
|
||||
})
|
||||
|
||||
p$set(value, message, detail)
|
||||
|
||||
eval(expr, env)
|
||||
}
|
||||
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
setProgress <- function(value = NULL, message = NULL, detail = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
if (!inherits(session, "ShinySession"))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
if (session$progressStack$size() == 0) {
|
||||
warning('setProgress was called outside of withProgress; ignoring')
|
||||
return()
|
||||
}
|
||||
|
||||
session$progressStack$peek()$set(value, message, detail)
|
||||
invisible()
|
||||
}
|
||||
|
||||
#' @rdname withProgress
|
||||
#' @export
|
||||
incProgress <- function(amount = 0.1, message = NULL, detail = NULL,
|
||||
session = getDefaultReactiveDomain()) {
|
||||
|
||||
if (!inherits(session, "ShinySession"))
|
||||
stop("'session' is not a ShinySession object.")
|
||||
|
||||
if (session$progressStack$size() == 0) {
|
||||
warning('incProgress was called outside of withProgress; ignoring')
|
||||
return()
|
||||
}
|
||||
|
||||
p <- session$progressStack$peek()
|
||||
p$inc(amount, message, detail)
|
||||
invisible()
|
||||
}
|
||||
381
R/react.R
381
R/react.R
@@ -1,303 +1,154 @@
|
||||
# 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 <- R6Class(
|
||||
'Context',
|
||||
fields = list(
|
||||
id = 'character',
|
||||
.invalidated = 'logical',
|
||||
.callbacks = 'list'
|
||||
),
|
||||
methods = list(
|
||||
initialize = function() {
|
||||
id <<- .get.reactive.environment()$next.id()
|
||||
.invalidated <<- F
|
||||
.callbacks <<- list()
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
id = character(0),
|
||||
.label = character(0), # For debug purposes
|
||||
.invalidated = FALSE,
|
||||
.invalidateCallbacks = list(),
|
||||
.flushCallbacks = list(),
|
||||
.domain = NULL,
|
||||
|
||||
initialize = function(domain, label='', type='other', prevId='') {
|
||||
id <<- .getReactiveEnvironment()$nextId()
|
||||
.label <<- label
|
||||
.domain <<- domain
|
||||
.graphCreateContext(id, label, type, prevId, domain)
|
||||
},
|
||||
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."
|
||||
withReactiveDomain(.domain, {
|
||||
env <- .getReactiveEnvironment()
|
||||
.graphEnterContext(id)
|
||||
tryCatch(
|
||||
env$runWith(self, func),
|
||||
finally = .graphExitContext(id)
|
||||
)
|
||||
})
|
||||
},
|
||||
invalidate = function() {
|
||||
"Invalidate this context. It will immediately call the callbacks
|
||||
that have been registered with onInvalidate()."
|
||||
if (.invalidated)
|
||||
return()
|
||||
.invalidated <<- T
|
||||
.get.reactive.environment()$add.pending.invalidate(.self)
|
||||
.invalidated <<- TRUE
|
||||
|
||||
.graphInvalidate(id, .domain)
|
||||
lapply(.invalidateCallbacks, function(func) {
|
||||
func()
|
||||
})
|
||||
.invalidateCallbacks <<- list()
|
||||
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)
|
||||
.invalidateCallbacks <<- c(.invalidateCallbacks, func)
|
||||
NULL
|
||||
},
|
||||
execute.callbacks = function() {
|
||||
lapply(.callbacks, function(func) {
|
||||
addPendingFlush = function(priority) {
|
||||
"Tell the reactive environment that this context should be flushed the
|
||||
next time flushReact() called."
|
||||
.getReactiveEnvironment()$addPendingFlush(self, priority)
|
||||
},
|
||||
onFlush = function(func) {
|
||||
"Register a function to be called when this context is flushed."
|
||||
.flushCallbacks <<- c(.flushCallbacks, func)
|
||||
},
|
||||
executeFlushCallbacks = function() {
|
||||
"For internal use only."
|
||||
lapply(.flushCallbacks, function(func) {
|
||||
func()
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
ReactiveEnvironment <- setRefClass(
|
||||
ReactiveEnvironment <- R6Class(
|
||||
'ReactiveEnvironment',
|
||||
fields = c('.current.context', '.next.id', '.pending.invalidate'),
|
||||
methods = list(
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.currentContext = NULL,
|
||||
.nextId = 0L,
|
||||
.pendingFlush = 'PriorityQueue',
|
||||
.inFlush = FALSE,
|
||||
|
||||
initialize = function() {
|
||||
.current.context <<- NULL
|
||||
.next.id <<- 0L
|
||||
.pending.invalidate <<- list()
|
||||
.pendingFlush <<- PriorityQueue$new()
|
||||
},
|
||||
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)) {
|
||||
if (isTRUE(getOption('shiny.suppressMissingContextError'))) {
|
||||
return(getDummyContext())
|
||||
} else {
|
||||
stop('Operation not allowed without an active reactive context. ',
|
||||
'(You tried to do something that can only be done from inside a ',
|
||||
'reactive expression or observer.)')
|
||||
}
|
||||
}
|
||||
return(.currentContext)
|
||||
},
|
||||
set.current.context = function(ctx) {
|
||||
.current.context <<- ctx
|
||||
runWith = function(ctx, func) {
|
||||
old.ctx <- .currentContext
|
||||
.currentContext <<- ctx
|
||||
on.exit(.currentContext <<- old.ctx)
|
||||
shinyCallingHandlers(func())
|
||||
},
|
||||
add.pending.invalidate = function(ctx) {
|
||||
.pending.invalidate <<- c(.pending.invalidate, ctx)
|
||||
addPendingFlush = function(ctx, priority) {
|
||||
.pendingFlush$enqueue(ctx, priority)
|
||||
},
|
||||
flush = function() {
|
||||
while (length(.pending.invalidate) > 0) {
|
||||
contexts <- .pending.invalidate
|
||||
.pending.invalidate <<- list()
|
||||
lapply(contexts, function(ctx) {
|
||||
ctx$execute.callbacks()
|
||||
NULL
|
||||
})
|
||||
# If already in a flush, don't start another one
|
||||
if (.inFlush) return()
|
||||
.inFlush <<- TRUE
|
||||
on.exit(.inFlush <<- FALSE)
|
||||
|
||||
while (!.pendingFlush$isEmpty()) {
|
||||
ctx <- .pendingFlush$dequeue()
|
||||
ctx$executeFlushCallbacks()
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
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() {
|
||||
if (!exists('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)) {
|
||||
assign('.ReactiveEnvironment', ReactiveEnvironment$new(), envir=.GlobalEnv)
|
||||
.getReactiveEnvironment <- local({
|
||||
reactiveEnvironment <- NULL
|
||||
function() {
|
||||
if (is.null(reactiveEnvironment))
|
||||
reactiveEnvironment <<- ReactiveEnvironment$new()
|
||||
return(reactiveEnvironment)
|
||||
}
|
||||
get('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)
|
||||
})
|
||||
|
||||
# Causes any pending invalidations to run.
|
||||
flushReact <- function() {
|
||||
.getReactiveEnvironment()$flush()
|
||||
}
|
||||
|
||||
flush.react <- function() {
|
||||
.get.reactive.environment()$flush()
|
||||
# Retrieves the current reactive context, or errors if there is no reactive
|
||||
# context active at the moment.
|
||||
getCurrentContext <- function() {
|
||||
.getReactiveEnvironment()$currentContext()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
getDummyContext <- function() {}
|
||||
local({
|
||||
dummyContext <- NULL
|
||||
getDummyContext <<- function() {
|
||||
if (is.null(dummyContext)) {
|
||||
dummyContext <<- Context$new(getDefaultReactiveDomain(), '[none]',
|
||||
type='isolate')
|
||||
}
|
||||
return(dummyContext)
|
||||
}
|
||||
})
|
||||
|
||||
253
R/reactive-domains.R
Normal file
253
R/reactive-domains.R
Normal file
@@ -0,0 +1,253 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
#
|
||||
# Over the last few months we've seen a number of cases where it'd be helpful
|
||||
# for objects that are instantiated within a Shiny app to know what Shiny
|
||||
# session they are "owned" by. I put "owned" in quotes because there isn't a
|
||||
# built-in notion of object ownership in Shiny today, any more than there is a
|
||||
# notion of one object owning another in R.
|
||||
#
|
||||
# But it's intuitive to everyone, I think, that the outputs for a session are
|
||||
# owned by that session, and any logic that is executed as part of the output
|
||||
# is done on behalf of that session. And it seems like in the vast majority of
|
||||
# cases, observers that are created inside a shinyServer function (i.e. one per
|
||||
# session) are also intuitively owned by the session that's starting up.
|
||||
#
|
||||
# This notion of ownership is important/helpful for a few scenarios that have
|
||||
# come up in recent months:
|
||||
#
|
||||
# 1. The showcase mode that Jonathan implemented recently highlights
|
||||
# observers/reactives as they execute. In order for sessions to only receive
|
||||
# highlights for their own code execution, we need to know which sessions own
|
||||
# which observers. 2. We've seen a number of apps crash out when observers
|
||||
# outlive their sessions and then try to do things with their sessions (the
|
||||
# most common error message was something like "Can't write to a closed
|
||||
# websocket", but we now silently ignore writes to closed websockets). It'd be
|
||||
# convenient for the default behavior of observers to be that they don't
|
||||
# outlive their parent sessions. 3. The reactive log visualizer currently
|
||||
# visualizes all reactivity in the process; it would be great if by default it
|
||||
# only visualized the current session. 4. When an observer has an error, it
|
||||
# would be great to be able to send the error to the session so it can do its
|
||||
# own handling (such as sending the error info to the client so the user can be
|
||||
# notified). 5. Shiny Server Pro wants to show the admin how much time is being
|
||||
# spent servicing each session.
|
||||
#
|
||||
# So what are the rules for establishing ownership?
|
||||
#
|
||||
# 1. Define the "current domain" as a global variable whose value will own any
|
||||
# newly created observer (by default). A domain is a reference class or
|
||||
# environment that contains the functions `onEnded(callback)`, `isEnded()`, and
|
||||
# `reactlog(logEntry)`.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
createMockDomain <- function() {
|
||||
callbacks <- list()
|
||||
ended <- FALSE
|
||||
domain <- new.env(parent = emptyenv())
|
||||
domain$onEnded <- function(callback) {
|
||||
callbacks <<- c(callbacks, callback)
|
||||
}
|
||||
domain$isEnded <- function() {
|
||||
ended
|
||||
}
|
||||
domain$reactlog <- function(logEntry) NULL
|
||||
domain$end <- function() {
|
||||
if (!ended) {
|
||||
ended <<- TRUE
|
||||
lapply(callbacks, do.call, list())
|
||||
}
|
||||
invisible()
|
||||
}
|
||||
return(domain)
|
||||
}
|
||||
|
||||
#
|
||||
# 2. The initial value of "current domain" is null.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
.globals$domain <- NULL
|
||||
|
||||
#
|
||||
# 3. Objects that can be owned include observers, reactive expressions,
|
||||
# invalidateLater instances, reactiveTimer instances. Whenever one of these is
|
||||
# created, by default its owner will be the current domain.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @name domains
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
getDefaultReactiveDomain <- function() {
|
||||
.globals$domain
|
||||
}
|
||||
|
||||
#
|
||||
# 4. While a session is being created and the shinyServer function is executed,
|
||||
# the current domain is set to the new session. When the shinyServer function
|
||||
# is done executing, the previous value of the current domain is restored. This
|
||||
# is made foolproof using a `withReactiveDomain` function.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
withReactiveDomain <- function(domain, expr) {
|
||||
oldValue <- .globals$domain
|
||||
.globals$domain <- domain
|
||||
on.exit(.globals$domain <- oldValue)
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
#
|
||||
# 5. While an observer or reactive expression is executing, the current domain
|
||||
# is set to the owner of the observer. When the observer completes, the
|
||||
# previous value of the current domain is restored.
|
||||
#
|
||||
# 6. Note that once created, an observer/reactive expression belongs to the
|
||||
# same domain forever, regardless of how many times it is invalidated and
|
||||
# re-executed, and regardless of what caused the invalidation to happen.
|
||||
#
|
||||
# 7. When a session ends, any observers that it owns are suspended, any
|
||||
# invalidateLater/reactiveTimers are stopped.
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
onReactiveDomainEnded <- function(domain, callback, failIfNull = FALSE) {
|
||||
if (is.null(domain)) {
|
||||
if (isTRUE(failIfNull))
|
||||
stop("onReactiveDomainEnded called with null domain and failIfNull=TRUE")
|
||||
else
|
||||
return()
|
||||
}
|
||||
domain$onEnded(callback)
|
||||
}
|
||||
|
||||
#
|
||||
# 8. If an uncaught error occurs while executing an observer, the session gets
|
||||
# a chance to handle it. I suppose the default behavior would be to send the
|
||||
# message to the client if possible, and then perhaps end the session (or not,
|
||||
# I could argue either way).
|
||||
#
|
||||
# The basic idea here is inspired by Node.js domains, which you can think of as
|
||||
# a way to track execution contexts across callback- or listener-oriented
|
||||
# asynchronous code. They use it to unify error handling code across a graph of
|
||||
# related objects. Our domains will be to unify both lifetime and error
|
||||
# handling across a graph of related reactive primitives.
|
||||
#
|
||||
# (You could imagine that as a client update is being processed, the session
|
||||
# associated with that client would become the current domain. IIRC this is how
|
||||
# showcase mode is implemented today. I don't think this would cover any cases
|
||||
# not covered by rule 5 above, and the absence of rule 5 would leave cases that
|
||||
# this rule would not cover.)
|
||||
#
|
||||
# Pitfalls/open issues:
|
||||
#
|
||||
# 1. Our current approach has the issue of observers staying alive longer than
|
||||
# they ought to. This proposal introduces the opposite risk: that
|
||||
# observers/invalidateLater/reactiveTimer instances, having implicitly been
|
||||
# assigned a parent, are suspended/disposed earlier than they ought to have
|
||||
# been. I find this especially worrisome for invalidateLater/reactiveTimer,
|
||||
# which will often be called in a reactive expression, and thus execute under
|
||||
# unpredictable circumstances. Perhaps those should continue to accept an
|
||||
# explicit "session=" parameter that the user is warned about if they don't
|
||||
# provide a value.
|
||||
#
|
||||
# 2. Are there situations where it is ambiguous what the right thing to do is,
|
||||
# and we should warn/error to ask the user to provide a domain explicitly?
|
||||
#
|
||||
## ------------------------------------------------------------------------
|
||||
|
||||
#' Reactive domains
|
||||
#'
|
||||
#' Reactive domains are a mechanism for establishing ownership over reactive
|
||||
#' primitives (like reactive expressions and observers), even if the set of
|
||||
#' reactive primitives is dynamically created. This is useful for lifetime
|
||||
#' management (i.e. destroying observers when the Shiny session that created
|
||||
#' them ends) and error handling.
|
||||
#'
|
||||
#' At any given time, there can be either a single "default" reactive domain
|
||||
#' object, or none (i.e. the reactive domain object is \code{NULL}). You can
|
||||
#' access the current default reactive domain by calling
|
||||
#' \code{getDefaultReactiveDomain}.
|
||||
#'
|
||||
#' Unless you specify otherwise, newly created observers and reactive
|
||||
#' expressions will be assigned to the current default domain (if any). You can
|
||||
#' override this assignment by providing an explicit \code{domain} argument to
|
||||
#' \code{\link{reactive}} or \code{\link{observe}}.
|
||||
#'
|
||||
#' For advanced usage, it's possible to override the default domain using
|
||||
#' \code{withReactiveDomain}. The \code{domain} argument will be made the
|
||||
#' default domain while \code{expr} is evaluated.
|
||||
#'
|
||||
#' Implementers of new reactive primitives can use \code{onReactiveDomainEnded}
|
||||
#' as a convenience function for registering callbacks. If the reactive domain
|
||||
#' is \code{NULL} and \code{failIfNull} is \code{FALSE}, then the callback will
|
||||
#' never be invoked.
|
||||
#'
|
||||
#' @name domains
|
||||
#' @param domain A valid domain object (for example, a Shiny session), or
|
||||
#' \code{NULL}
|
||||
#' @param expr An expression to evaluate under \code{domain}
|
||||
#' @param callback A callback function to be invoked
|
||||
#' @param failIfNull If \code{TRUE} then an error is given if the \code{domain}
|
||||
#' is \code{NULL}
|
||||
NULL
|
||||
|
||||
#
|
||||
# Example 1
|
||||
# ---
|
||||
# ```
|
||||
# obs1 <- observe({
|
||||
# })
|
||||
# shinyServer(function(input, output) {
|
||||
# obs2 <- observe({
|
||||
# obs3 <- observe({
|
||||
# })
|
||||
# })
|
||||
# })
|
||||
# # obs1 would have no domain, obs2 and obs3 would be owned by the session
|
||||
# ```
|
||||
#
|
||||
# Example 2
|
||||
# ---
|
||||
# ```
|
||||
# globalValues <- reactiveValues(broadcast="")
|
||||
# shinyServer(function(input, output) {
|
||||
# sessionValues <- reactiveValues()
|
||||
# output$messageOutput <- renderText({
|
||||
# globalValues$broadcast
|
||||
# obs1 <- observe({...})
|
||||
# })
|
||||
# observe({
|
||||
# if (input$goButton == 0) return()
|
||||
# isolate( globalValues$broadcast <- input$messageInput )
|
||||
# })
|
||||
# })
|
||||
# # The observer behind messageOutput would be owned by the session,
|
||||
# # as would all the many instances of obs1 that were created.
|
||||
# ```
|
||||
# ---
|
||||
#
|
||||
# Example 3
|
||||
# ---
|
||||
# ```
|
||||
# rexpr1 <- reactive({
|
||||
# invalidateLater(1000)
|
||||
# obs1 <- observe({...})
|
||||
# })
|
||||
# observeSomething <- function() {
|
||||
# obs2 <- observe({...})
|
||||
# })
|
||||
# shinyServer(function(input, output) {
|
||||
# obs3 <- observe({
|
||||
# observeSomething()
|
||||
# rexpr1()
|
||||
# })
|
||||
# })
|
||||
# # rexpr1, the invalidateLater call, and obs1 would all have no owner;
|
||||
# # obs2 and obs3 would be owned by the session.
|
||||
# ```
|
||||
1394
R/reactives.R
Normal file
1394
R/reactives.R
Normal file
File diff suppressed because it is too large
Load Diff
562
R/render-plot.R
Normal file
562
R/render-plot.R
Normal file
@@ -0,0 +1,562 @@
|
||||
#' Plot Output
|
||||
#'
|
||||
#' Renders 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}.
|
||||
#'
|
||||
#' @seealso For the corresponding client-side output function, and example
|
||||
#' usage, see \code{\link{plotOutput}}. For more details on how the plots are
|
||||
#' generated, and how to control the output, see \code{\link{plotPNG}}.
|
||||
#'
|
||||
#' @param expr An expression that generates a plot.
|
||||
#' @param width,height The width/height of the rendered plot, in pixels; or
|
||||
#' \code{'auto'} to use the \code{offsetWidth}/\code{offsetHeight} of the HTML
|
||||
#' element that is bound to this plot. You can also pass in a function that
|
||||
#' returns the width/height in pixels or \code{'auto'}; in the body of the
|
||||
#' function you may reference reactive values and functions. When rendering an
|
||||
#' inline plot, you must provide numeric values (in pixels) to both
|
||||
#' \code{width} and \code{height}.
|
||||
#' @param res Resolution of resulting plot, in pixels per inch. This value is
|
||||
#' passed to \code{\link{png}}. Note that this affects the resolution of PNG
|
||||
#' rendering in R; it won't change the actual ppi of the browser.
|
||||
#' @param ... Arguments to be passed through to \code{\link[grDevices]{png}}.
|
||||
#' These can be used to set the width, height, background color, etc.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that generates a plot (deprecated; use \code{expr}
|
||||
#' instead).
|
||||
#'
|
||||
#' @export
|
||||
renderPlot <- function(expr, width='auto', height='auto', res=72, ...,
|
||||
env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPlot: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
args <- list(...)
|
||||
|
||||
if (is.function(width))
|
||||
widthWrapper <- reactive({ width() })
|
||||
else
|
||||
widthWrapper <- NULL
|
||||
|
||||
if (is.function(height))
|
||||
heightWrapper <- reactive({ height() })
|
||||
else
|
||||
heightWrapper <- NULL
|
||||
|
||||
# If renderPlot isn't going to adapt to the height of the div, then the
|
||||
# div needs to adapt to the height of renderPlot. By default, plotOutput
|
||||
# sets the height to 400px, so to make it adapt we need to override it
|
||||
# with NULL.
|
||||
outputFunc <- plotOutput
|
||||
if (!identical(height, 'auto')) formals(outputFunc)['height'] <- list(NULL)
|
||||
|
||||
return(markRenderFunction(outputFunc, function(shinysession, name, ...) {
|
||||
if (!is.null(widthWrapper))
|
||||
width <- widthWrapper()
|
||||
if (!is.null(heightWrapper))
|
||||
height <- heightWrapper()
|
||||
|
||||
# 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 <- 'output_'
|
||||
if (width == 'auto')
|
||||
width <- shinysession$clientData[[paste(prefix, name, '_width', sep='')]];
|
||||
if (height == 'auto')
|
||||
height <- shinysession$clientData[[paste(prefix, name, '_height', sep='')]];
|
||||
|
||||
if (is.null(width) || is.null(height) || width <= 0 || height <= 0)
|
||||
return(NULL)
|
||||
|
||||
# Resolution multiplier
|
||||
pixelratio <- shinysession$clientData$pixelratio
|
||||
if (is.null(pixelratio))
|
||||
pixelratio <- 1
|
||||
|
||||
coordmap <- NULL
|
||||
plotFunc <- function() {
|
||||
# Actually perform the plotting
|
||||
result <- withVisible(func())
|
||||
|
||||
coordmap <<- NULL
|
||||
|
||||
if (result$visible) {
|
||||
# Use capture.output to squelch printing to the actual console; we
|
||||
# are only interested in plot output
|
||||
|
||||
# Special case for ggplot objects - need to capture coordmap
|
||||
if (inherits(result$value, "ggplot")) {
|
||||
capture.output(coordmap <<- getGgplotCoordmap(result$value, pixelratio))
|
||||
} else {
|
||||
capture.output(print(result$value))
|
||||
}
|
||||
}
|
||||
|
||||
if (is.null(coordmap)) {
|
||||
coordmap <<- getPrevPlotCoordmap(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
outfile <- do.call(plotPNG, c(plotFunc, width=width*pixelratio,
|
||||
height=height*pixelratio, res=res*pixelratio, args))
|
||||
on.exit(unlink(outfile))
|
||||
|
||||
# A list of attributes for the img
|
||||
res <- list(
|
||||
src=shinysession$fileUrl(name, outfile, contentType='image/png'),
|
||||
width=width, height=height, coordmap=coordmap
|
||||
)
|
||||
|
||||
# Get error message if present (from attribute on the coordmap)
|
||||
error <- attr(coordmap, "error", exact = TRUE)
|
||||
if (!is.null(error)) {
|
||||
res$error <- error
|
||||
}
|
||||
|
||||
res
|
||||
}))
|
||||
}
|
||||
|
||||
# The coordmap extraction functions below return something like the examples
|
||||
# below. For base graphics:
|
||||
# plot(mtcars$wt, mtcars$mpg)
|
||||
# str(getPrevPlotCoordmap(400, 300))
|
||||
# List of 1
|
||||
# $ :List of 4
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.36
|
||||
# .. ..$ right : num 5.58
|
||||
# .. ..$ bottom: num 9.46
|
||||
# .. ..$ top : num 34.8
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 50.4
|
||||
# .. ..$ right : num 373
|
||||
# .. ..$ bottom: num 199
|
||||
# .. ..$ top : num 79.6
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ mapping: Named list()
|
||||
#
|
||||
# For ggplot2, it might be something like:
|
||||
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
|
||||
# str(getGgplotCoordmap(p, 1))
|
||||
# List of 1
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars: Named list()
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 2
|
||||
# .. ..$ x: chr "wt"
|
||||
# .. ..$ y: chr "mpg"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 40.8
|
||||
# .. ..$ right : num 446
|
||||
# .. ..$ bottom: num 263
|
||||
# .. ..$ top : num 14.4
|
||||
#
|
||||
# With a faceted ggplot2 plot, the outer list contains two objects, each of
|
||||
# which represents one panel. In this example, there is one panelvar, but there
|
||||
# can be up to two of them.
|
||||
# mtc <- mtcars
|
||||
# mtc$am <- factor(mtc$am)
|
||||
# p <- ggplot(mtcars, aes(wt, mpg)) + geom_point() + facet_wrap(~ am)
|
||||
# str(getGgplotCoordmap(p, 1))
|
||||
# List of 2
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 1
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 1
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 1
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 3
|
||||
# .. ..$ x : chr "wt"
|
||||
# .. ..$ y : chr "mpg"
|
||||
# .. ..$ panelvar1: chr "am"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 45.6
|
||||
# .. ..$ right : num 317
|
||||
# .. ..$ bottom: num 251
|
||||
# .. ..$ top : num 35.7
|
||||
# $ :List of 10
|
||||
# ..$ panel : int 2
|
||||
# ..$ row : int 1
|
||||
# ..$ col : int 2
|
||||
# ..$ panel_vars:List of 1
|
||||
# .. ..$ panelvar1: Factor w/ 2 levels "0","1": 2
|
||||
# ..$ scale_x : int 1
|
||||
# ..$ scale_y : int 1
|
||||
# ..$ log :List of 2
|
||||
# .. ..$ x: NULL
|
||||
# .. ..$ y: NULL
|
||||
# ..$ domain :List of 4
|
||||
# .. ..$ left : num 1.32
|
||||
# .. ..$ right : num 5.62
|
||||
# .. ..$ bottom: num 9.22
|
||||
# .. ..$ top : num 35.1
|
||||
# ..$ mapping :List of 3
|
||||
# .. ..$ x : chr "wt"
|
||||
# .. ..$ y : chr "mpg"
|
||||
# .. ..$ panelvar1: chr "am"
|
||||
# ..$ range :List of 4
|
||||
# .. ..$ left : num 322
|
||||
# .. ..$ right : num 594
|
||||
# .. ..$ bottom: num 251
|
||||
# .. ..$ top : num 35.7
|
||||
|
||||
|
||||
# Get a coordmap for the previous plot made with base graphics.
|
||||
# Requires width and height of output image, in pixels.
|
||||
# Must be called before the graphics device is closed.
|
||||
getPrevPlotCoordmap <- function(width, height) {
|
||||
usrCoords <- par('usr')
|
||||
usrBounds <- usrCoords
|
||||
if (par('xlog')) {
|
||||
usrBounds[c(1,2)] <- 10 ^ usrBounds[c(1,2)]
|
||||
}
|
||||
if (par('ylog')) {
|
||||
usrBounds[c(3,4)] <- 10 ^ usrBounds[c(3,4)]
|
||||
}
|
||||
|
||||
# Wrapped in double list because other types of plots can have multiple panels.
|
||||
list(list(
|
||||
# Bounds of the plot area, in data space
|
||||
domain = list(
|
||||
left = usrCoords[1],
|
||||
right = usrCoords[2],
|
||||
bottom = usrCoords[3],
|
||||
top = usrCoords[4]
|
||||
),
|
||||
# The bounds of the plot area, in DOM pixels
|
||||
range = list(
|
||||
left = grconvertX(usrBounds[1], 'user', 'nfc') * width,
|
||||
right = grconvertX(usrBounds[2], 'user', 'nfc') * width,
|
||||
bottom = (1-grconvertY(usrBounds[3], 'user', 'nfc')) * height - 1,
|
||||
top = (1-grconvertY(usrBounds[4], 'user', 'nfc')) * height - 1
|
||||
),
|
||||
log = list(
|
||||
x = if (par('xlog')) 10 else NULL,
|
||||
y = if (par('ylog')) 10 else NULL
|
||||
),
|
||||
# We can't extract the original variable names from a base graphic.
|
||||
# `mapping` is an empty _named_ list, so that it is converted to an object
|
||||
# (not an array) in JSON.
|
||||
mapping = list(x = NULL)[0]
|
||||
))
|
||||
}
|
||||
|
||||
# Print a ggplot object and return a coordmap for it.
|
||||
getGgplotCoordmap <- function(p, pixelratio) {
|
||||
if (!inherits(p, "ggplot"))
|
||||
return(NULL)
|
||||
|
||||
# A modified version of print.ggplot which returns the built ggplot object
|
||||
# as well as the gtable grob.
|
||||
print_ggplot <- function(x) {
|
||||
grid::grid.newpage()
|
||||
|
||||
build <- ggplot2::ggplot_build(x)
|
||||
|
||||
gtable <- ggplot2::ggplot_gtable(build)
|
||||
grid::grid.draw(gtable)
|
||||
|
||||
list(
|
||||
build = build,
|
||||
gtable = gtable
|
||||
)
|
||||
}
|
||||
|
||||
# Given a built ggplot object, return x and y domains (data space coords) for
|
||||
# each panel.
|
||||
find_panel_info <- function(b) {
|
||||
layout <- b$panel$layout
|
||||
# Convert factor to numbers
|
||||
layout$PANEL <- as.integer(as.character(layout$PANEL))
|
||||
|
||||
# Names of facets
|
||||
facet <- b$plot$facet
|
||||
facet_vars <- NULL
|
||||
if (inherits(facet, "grid")) {
|
||||
facet_vars <- vapply(c(facet$cols, facet$rows), as.character, character(1))
|
||||
} else if (inherits(facet, "wrap")) {
|
||||
facet_vars <- vapply(facet$facets, as.character, character(1))
|
||||
}
|
||||
|
||||
# Iterate over each row in the layout data frame
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
# Slice out one row
|
||||
l <- layout[i, ]
|
||||
|
||||
scale_x <- l$SCALE_X
|
||||
scale_y <- l$SCALE_Y
|
||||
|
||||
mapping <- find_plot_mappings(b)
|
||||
|
||||
# For each of the faceting variables, get the value of that variable in
|
||||
# the current panel. Default to empty _named_ list so that it's sent as a
|
||||
# JSON object, not array.
|
||||
panel_vars <- list(a = NULL)[0]
|
||||
for (i in seq_along(facet_vars)) {
|
||||
var_name <- facet_vars[[i]]
|
||||
vname <- paste0("panelvar", i)
|
||||
|
||||
mapping[[vname]] <- var_name
|
||||
panel_vars[[vname]] <- l[[var_name]]
|
||||
}
|
||||
|
||||
list(
|
||||
panel = l$PANEL,
|
||||
row = l$ROW,
|
||||
col = l$COL,
|
||||
panel_vars = panel_vars,
|
||||
scale_x = scale_x,
|
||||
scale_y = scale_x,
|
||||
log = check_log_scales(b, scale_x, scale_y),
|
||||
domain = find_panel_domain(b, l$PANEL, scale_x, scale_y),
|
||||
mapping = mapping
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# Given a single range object (representing the data domain) from a built
|
||||
# ggplot object, return the domain.
|
||||
find_panel_domain <- function(b, panel_num, scalex_num = 1, scaley_num = 1) {
|
||||
range <- b$panel$ranges[[panel_num]]
|
||||
res <- list(
|
||||
left = range$x.range[1],
|
||||
right = range$x.range[2],
|
||||
bottom = range$y.range[1],
|
||||
top = range$y.range[2]
|
||||
)
|
||||
|
||||
# Check for reversed scales
|
||||
xscale <- b$panel$x_scales[[scalex_num]]
|
||||
yscale <- b$panel$y_scales[[scaley_num]]
|
||||
|
||||
if (!is.null(xscale$trans) && xscale$trans$name == "reverse") {
|
||||
res$left <- -res$left
|
||||
res$right <- -res$right
|
||||
}
|
||||
if (!is.null(yscale$trans) && yscale$trans$name == "reverse") {
|
||||
res$top <- -res$top
|
||||
res$bottom <- -res$bottom
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
# Given built ggplot object, return object with the log base for x and y if
|
||||
# there are log scales or coord transforms.
|
||||
check_log_scales <- function(b, scalex_num = 1, scaley_num = 1) {
|
||||
|
||||
# Given a vector of transformation names like c("log-10", "identity"),
|
||||
# return the first log base, like 10. If none are present, return NULL.
|
||||
extract_log_base <- function(names) {
|
||||
names <- names[grepl("^log-", names)]
|
||||
|
||||
if (length(names) == 0)
|
||||
return(NULL)
|
||||
|
||||
names <- names[1]
|
||||
|
||||
as.numeric(sub("^log-", "", names))
|
||||
}
|
||||
|
||||
# Look for log scales and log coord transforms. People shouldn't use both.
|
||||
x_names <- character(0)
|
||||
y_names <- character(0)
|
||||
|
||||
# Continuous scales have a trans; discrete ones don't
|
||||
if (!is.null(b$panel$x_scales[[scalex_num]]$trans))
|
||||
x_names <- b$panel$x_scales[[scalex_num]]$trans$name
|
||||
if (!is.null(b$panel$y_scales[[scaley_num]]$trans))
|
||||
y_names <- b$panel$y_scales[[scaley_num]]$trans$name
|
||||
|
||||
coords <- b$plot$coordinates
|
||||
if (!is.null(coords$trans)) {
|
||||
if (!is.null(coords$trans$x))
|
||||
x_names <- c(x_names, coords$trans$x$name)
|
||||
if (!is.null(coords$trans$y))
|
||||
y_names <- c(y_names, coords$trans$y$name)
|
||||
}
|
||||
|
||||
# Keep only scale/trans names that start with "log-"
|
||||
x_names <- x_names[grepl("^log-", x_names)]
|
||||
y_names <- y_names[grepl("^log-", y_names)]
|
||||
|
||||
# Extract the log base from the trans name -- a string like "log-10".
|
||||
list(
|
||||
x = extract_log_base(x_names),
|
||||
y = extract_log_base(y_names)
|
||||
)
|
||||
}
|
||||
|
||||
# Given a built ggplot object, return a named list of variables mapped to x
|
||||
# and y. This function will be called for each panel, but in practice the
|
||||
# result is always the same across panels, so we'll cache the result.
|
||||
mappings_cache <- NULL
|
||||
find_plot_mappings <- function(b) {
|
||||
if (!is.null(mappings_cache))
|
||||
return(mappings_cache)
|
||||
|
||||
# lapply'ing as.character results in unexpected behavior for expressions
|
||||
# like `wt/2`. This works better.
|
||||
mappings <- as.list(as.character(b$plot$mapping))
|
||||
|
||||
# If x or y mapping is missing, look in each layer for mappings and return
|
||||
# the first one.
|
||||
missing_mappings <- setdiff(c("x", "y"), names(mappings))
|
||||
if (length(missing_mappings) != 0) {
|
||||
# Grab mappings for each layer
|
||||
layer_mappings <- lapply(b$plot$layers, function(layer) {
|
||||
lapply(layer$mapping, as.character)
|
||||
})
|
||||
|
||||
# Get just the first x or y value in the combined list of plot and layer
|
||||
# mappings.
|
||||
mappings <- c(list(mappings), layer_mappings)
|
||||
mappings <- Reduce(x = mappings, init = list(x = NULL, y = NULL),
|
||||
function(init, m) {
|
||||
if (is.null(init$x) && !is.null(m$x)) init$x <- m$x
|
||||
if (is.null(init$y) && !is.null(m$y)) init$y <- m$y
|
||||
init
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
mappings_cache <<- mappings
|
||||
mappings
|
||||
}
|
||||
|
||||
# Given a gtable object, return the x and y ranges (in pixel dimensions)
|
||||
find_panel_ranges <- function(g, pixelratio) {
|
||||
# Given a vector of unit objects, return logical vector indicating which ones
|
||||
# are "null" units. These units use the remaining available width/height --
|
||||
# that is, the space not occupied by elements that have an absolute size.
|
||||
is_null_unit <- function(x) {
|
||||
vapply(x, FUN.VALUE = logical(1), function(u) {
|
||||
isTRUE(attr(u, "unit", exact = TRUE) == "null")
|
||||
})
|
||||
}
|
||||
|
||||
# Convert a unit (or vector of units) to a numeric vector of pixel sizes
|
||||
h_px <- function(x) as.numeric(grid::convertHeight(x, "native"))
|
||||
w_px <- function(x) as.numeric(grid::convertWidth(x, "native"))
|
||||
|
||||
# Given a vector of relative sizes (in grid units), and a function for
|
||||
# converting grid units to numeric pixels, return a numeric vector of
|
||||
# pixel sizes.
|
||||
find_px_sizes <- function(rel_sizes, unit_to_px) {
|
||||
# Total pixels (in height or width)
|
||||
total_px <- unit_to_px(grid::unit(1, "npc"))
|
||||
# Calculate size of all panel(s) together. Panels (and only panels) have
|
||||
# null size.
|
||||
null_idx <- is_null_unit(rel_sizes)
|
||||
# All the absolute heights. At this point, null heights are 0. We need to
|
||||
# calculate them separately and add them in later.
|
||||
px_sizes <- unit_to_px(rel_sizes)
|
||||
# Total size for panels is image size minus absolute (non-panel) elements
|
||||
panel_px_total <- total_px - sum(px_sizes)
|
||||
# Divide up the total panel size up into the panels (scaled by size)
|
||||
panel_sizes_rel <- as.numeric(rel_sizes[null_idx])
|
||||
panel_sizes_rel <- panel_sizes_rel / sum(panel_sizes_rel)
|
||||
px_sizes[null_idx] <- panel_px_total * panel_sizes_rel
|
||||
abs(px_sizes)
|
||||
}
|
||||
|
||||
px_heights <- find_px_sizes(g$heights, h_px)
|
||||
px_widths <- find_px_sizes(g$widths, w_px)
|
||||
|
||||
# Convert to absolute pixel positions
|
||||
x_pos <- cumsum(px_widths)
|
||||
y_pos <- cumsum(px_heights)
|
||||
|
||||
# Match up the pixel dimensions to panels
|
||||
layout <- g$layout
|
||||
# For panels:
|
||||
# * For facet_wrap, they'll be named "panel-1", "panel-2", etc.
|
||||
# * For no facet or facet_grid, they'll just be named "panel". For
|
||||
# facet_grid, we need to re-order the layout table. Assume that panel
|
||||
# numbers go from left to right, then next row.
|
||||
# Assign a number to each panel, corresponding to PANEl in the built ggplot
|
||||
# object.
|
||||
layout <- layout[grepl("^panel", layout$name), ]
|
||||
layout <- layout[order(layout$t, layout$l), ]
|
||||
layout$panel <- seq_len(nrow(layout))
|
||||
|
||||
# When using a HiDPI client on a Linux server, the pixel
|
||||
# dimensions are doubled, so we have to divide the dimensions by
|
||||
# `pixelratio`. When a HiDPI client is used on a Mac server (with
|
||||
# the quartz device), the pixel dimensions _aren't_ doubled, even though
|
||||
# the image has double size. In the latter case we don't have to scale the
|
||||
# numbers down.
|
||||
pix_ratio <- 1
|
||||
if (!grepl("^quartz", names(dev.cur()))) {
|
||||
pix_ratio <- pixelratio
|
||||
}
|
||||
|
||||
# Return list of lists, where each inner list has left, right, top, bottom
|
||||
# values for a panel
|
||||
lapply(seq_len(nrow(layout)), function(i) {
|
||||
p <- layout[i, , drop = FALSE]
|
||||
list(
|
||||
left = x_pos[p$l - 1] / pix_ratio,
|
||||
right = x_pos[p$r] / pix_ratio,
|
||||
bottom = y_pos[p$b] / pix_ratio,
|
||||
top = y_pos[p$t - 1] / pix_ratio
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
res <- print_ggplot(p)
|
||||
|
||||
tryCatch({
|
||||
# Get info from built ggplot object
|
||||
info <- find_panel_info(res$build)
|
||||
|
||||
# Get ranges from gtable - it's possible for this to return more elements than
|
||||
# info, because it calculates positions even for panels that aren't present.
|
||||
# This can happen with facet_wrap.
|
||||
ranges <- find_panel_ranges(res$gtable, pixelratio)
|
||||
|
||||
for (i in seq_along(info)) {
|
||||
info[[i]]$range <- ranges[[i]]
|
||||
}
|
||||
|
||||
return(info)
|
||||
|
||||
}, error = function(e) {
|
||||
# If there was an error extracting info from the ggplot object, just return
|
||||
# a list with the error message.
|
||||
return(structure(list(), error = e$message))
|
||||
})
|
||||
}
|
||||
149
R/run-url.R
Normal file
149
R/run-url.R
Normal file
@@ -0,0 +1,149 @@
|
||||
#' Run a Shiny application from a URL
|
||||
#'
|
||||
#' \code{runUrl()} downloads and launches a Shiny application that is hosted at
|
||||
#' a downloadable URL. The Shiny application must be saved in a .zip, .tar, or
|
||||
#' .tar.gz file. The Shiny application files must be contained in the root
|
||||
#' directory or a subdirectory in the archive. For example, the files might be
|
||||
#' \code{myapp/server.r} and \code{myapp/ui.r}. The functions \code{runGitHub()}
|
||||
#' and \code{runGist()} are based on \code{runUrl()}, using URL's from GitHub
|
||||
#' (\url{https://github.com}) and GitHub gists (\url{https://gist.github.com}),
|
||||
#' respectively.
|
||||
#' @param url URL of the application.
|
||||
#' @param filetype The file type (\code{".zip"}, \code{".tar"}, or
|
||||
#' \code{".tar.gz"}. Defaults to the file extension taken from the url.
|
||||
#' @param subdir A subdirectory in the repository that contains the app. By
|
||||
#' default, this function will run an app from the top level of the repo, but
|
||||
#' you can use a path such as `\code{"inst/shinyapp"}.
|
||||
#' @param destdir Directory to store the downloaded application files. If \code{NULL}
|
||||
#' (the default), the application files will be stored in a temporary directory
|
||||
#' and removed when the app exits
|
||||
#' @param ... Other arguments to be passed to \code{\link{runApp}()}, such as
|
||||
#' \code{port} and \code{launch.browser}.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runUrl('https://github.com/rstudio/shiny_example/archive/master.tar.gz')
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the archive
|
||||
#' runUrl("https://github.com/rstudio/shiny_example/archive/master.zip",
|
||||
#' subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
runUrl <- function(url, filetype = NULL, subdir = NULL, destdir = NULL, ...) {
|
||||
|
||||
if (!is.null(subdir) && ".." %in% strsplit(subdir, '/')[[1]])
|
||||
stop("'..' not allowed in subdir")
|
||||
|
||||
if (is.null(filetype))
|
||||
filetype <- basename(url)
|
||||
|
||||
if (grepl("\\.tar\\.gz$", filetype))
|
||||
fileext <- ".tar.gz"
|
||||
else if (grepl("\\.tar$", filetype))
|
||||
fileext <- ".tar"
|
||||
else if (grepl("\\.zip$", filetype))
|
||||
fileext <- ".zip"
|
||||
else
|
||||
stop("Unknown file extension.")
|
||||
|
||||
message("Downloading ", url)
|
||||
if (is.null(destdir)) {
|
||||
filePath <- tempfile('shinyapp', fileext = fileext)
|
||||
fileDir <- tempfile('shinyapp')
|
||||
} else {
|
||||
fileDir <- destdir
|
||||
filePath <- paste(destdir, fileext)
|
||||
}
|
||||
|
||||
dir.create(fileDir, showWarnings = FALSE)
|
||||
if (download(url, filePath, mode = "wb", quiet = TRUE) != 0)
|
||||
stop("Failed to download URL ", url)
|
||||
on.exit(unlink(filePath))
|
||||
|
||||
if (fileext %in% c(".tar", ".tar.gz")) {
|
||||
# Regular untar commonly causes two problems on Windows with github tarballs:
|
||||
# 1) If RTools' tar.exe is in the path, you get cygwin path warnings which
|
||||
# throw list=TRUE off;
|
||||
# 2) If the internal untar implementation is used, it chokes on the 'g'
|
||||
# type flag that github uses (to stash their commit hash info).
|
||||
# By using our own forked/modified untar2 we sidestep both issues.
|
||||
first <- untar2(filePath, list=TRUE)[1]
|
||||
untar2(filePath, exdir = fileDir)
|
||||
|
||||
} else if (fileext == ".zip") {
|
||||
first <- as.character(unzip(filePath, list=TRUE)$Name)[1]
|
||||
unzip(filePath, exdir = fileDir)
|
||||
}
|
||||
|
||||
if(is.null(destdir)){
|
||||
on.exit(unlink(fileDir, recursive = TRUE), add = TRUE)
|
||||
}
|
||||
|
||||
appdir <- file.path(fileDir, first)
|
||||
if (!file_test('-d', appdir)) appdir <- dirname(appdir)
|
||||
|
||||
if (!is.null(subdir)) appdir <- file.path(appdir, subdir)
|
||||
runApp(appdir, ...)
|
||||
}
|
||||
|
||||
#' @rdname runUrl
|
||||
#' @param gist The identifier of the gist. For example, if the gist is
|
||||
#' https://gist.github.com/jcheng5/3239667, then \code{3239667},
|
||||
#' \code{'3239667'}, and \code{'https://gist.github.com/jcheng5/3239667'} are
|
||||
#' all valid values.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runGist(3239667)
|
||||
#' runGist("https://gist.github.com/jcheng5/3239667")
|
||||
#'
|
||||
#' # Old URL format without username
|
||||
#' runGist("https://gist.github.com/3239667")
|
||||
#' }
|
||||
#'
|
||||
runGist <- function(gist, destdir = NULL, ...) {
|
||||
|
||||
gistUrl <- if (is.numeric(gist) || grepl('^[0-9a-f]+$', gist)) {
|
||||
sprintf('https://gist.github.com/%s/download', gist)
|
||||
} else if(grepl('^https://gist.github.com/([^/]+/)?([0-9a-f]+)$', gist)) {
|
||||
paste(gist, '/download', sep='')
|
||||
} else {
|
||||
stop('Unrecognized gist identifier format')
|
||||
}
|
||||
|
||||
runUrl(gistUrl, filetype = ".tar.gz", destdir = destdir, ...)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname runUrl
|
||||
#' @param repo Name of the repository.
|
||||
#' @param username GitHub username. If \code{repo} is of the form
|
||||
#' \code{"username/repo"}, \code{username} will be taken from \code{repo}.
|
||||
#' @param ref Desired git reference. Could be a commit, tag, or branch name.
|
||||
#' Defaults to \code{"master"}.
|
||||
#' @export
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' runGitHub("shiny_example", "rstudio")
|
||||
#' # or runGitHub("rstudio/shiny_example")
|
||||
#'
|
||||
#' # Can run an app from a subdirectory in the repo
|
||||
#' runGitHub("shiny_example", "rstudio", subdir = "inst/shinyapp/")
|
||||
#' }
|
||||
runGitHub <- function(repo, username = getOption("github.user"),
|
||||
ref = "master", subdir = NULL, destdir = NULL, ...) {
|
||||
|
||||
if (grepl('/', repo)) {
|
||||
res <- strsplit(repo, '/')[[1]]
|
||||
if (length(res) != 2) stop("'repo' must be of the form 'username/repo'")
|
||||
username <- res[1]
|
||||
repo <- res[2]
|
||||
}
|
||||
|
||||
url <- paste("https://github.com/", username, "/", repo, "/archive/",
|
||||
ref, ".tar.gz", sep = "")
|
||||
|
||||
runUrl(url, subdir = subdir, destdir = destdir, ...)
|
||||
}
|
||||
838
R/server.R
Normal file
838
R/server.R
Normal file
@@ -0,0 +1,838 @@
|
||||
#' @include globals.R
|
||||
|
||||
appsByToken <- Map$new()
|
||||
|
||||
# Create a map for input handlers and register the defaults.
|
||||
inputHandlers <- Map$new()
|
||||
|
||||
#' Register an Input Handler
|
||||
#'
|
||||
#' Adds an input handler for data of this type. When called, Shiny will use the
|
||||
#' function provided to refine the data passed back from the client (after being
|
||||
#' deserialized by jsonlite) before making it available in the \code{input}
|
||||
#' variable of the \code{server.R} file.
|
||||
#'
|
||||
#' This function will register the handler for the duration of the R process
|
||||
#' (unless Shiny is explicitly reloaded). For that reason, the \code{type} used
|
||||
#' should be very specific to this package to minimize the risk of colliding
|
||||
#' with another Shiny package which might use this data type name. We recommend
|
||||
#' the format of "packageName.widgetName".
|
||||
#'
|
||||
#' Currently Shiny registers the following handlers: \code{shiny.matrix},
|
||||
#' \code{shiny.number}, and \code{shiny.date}.
|
||||
#'
|
||||
#' The \code{type} of a custom Shiny Input widget will be deduced using the
|
||||
#' \code{getType()} JavaScript function on the registered Shiny inputBinding.
|
||||
#' @param type The type for which the handler should be added -- should be a
|
||||
#' single-element character vector.
|
||||
#' @param fun The handler function. This is the function that will be used to
|
||||
#' parse the data delivered from the client before it is available in the
|
||||
#' \code{input} variable. The function will be called with the following three
|
||||
#' parameters:
|
||||
#' \enumerate{
|
||||
#' \item{The value of this input as provided by the client, deserialized
|
||||
#' using jsonlite.}
|
||||
#' \item{The \code{shinysession} in which the input exists.}
|
||||
#' \item{The name of the input.}
|
||||
#' }
|
||||
#' @param force If \code{TRUE}, will overwrite any existing handler without
|
||||
#' warning. If \code{FALSE}, will throw an error if this class already has
|
||||
#' a handler defined.
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Register an input handler which rounds a input number to the nearest integer
|
||||
#' registerInputHandler("mypackage.validint", function(x, shinysession, name) {
|
||||
#' if (is.null(x)) return(NA)
|
||||
#' round(x)
|
||||
#' })
|
||||
#'
|
||||
#' ## On the Javascript side, the associated input binding must have a corresponding getType method:
|
||||
#' getType: function(el) {
|
||||
#' return "mypackage.validint";
|
||||
#' }
|
||||
#'
|
||||
#' }
|
||||
#' @seealso \code{\link{removeInputHandler}}
|
||||
#' @export
|
||||
registerInputHandler <- function(type, fun, force=FALSE){
|
||||
if (inputHandlers$containsKey(type) && !force){
|
||||
stop("There is already an input handler for type: ", type)
|
||||
}
|
||||
inputHandlers$set(type, fun)
|
||||
}
|
||||
|
||||
#' Deregister an Input Handler
|
||||
#'
|
||||
#' Removes an Input Handler. Rather than using the previously specified handler
|
||||
#' for data of this type, the default jsonlite serialization will be used.
|
||||
#'
|
||||
#' @param type The type for which handlers should be removed.
|
||||
#' @return The handler previously associated with this \code{type}, if one
|
||||
#' existed. Otherwise, \code{NULL}.
|
||||
#' @seealso \code{\link{registerInputHandler}}
|
||||
#' @export
|
||||
removeInputHandler <- function(type){
|
||||
inputHandlers$remove(type)
|
||||
}
|
||||
|
||||
# Takes a list-of-lists and returns a matrix. The lists
|
||||
# must all be the same length. NULL is replaced by NA.
|
||||
registerInputHandler("shiny.matrix", 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)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.number", function(val, ...){
|
||||
ifelse(is.null(val), NA, val)
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.date", function(val, ...){
|
||||
# First replace NULLs with NA, then convert to Date vector
|
||||
datelist <- ifelse(lapply(val, is.null), NA, val)
|
||||
as.Date(unlist(datelist))
|
||||
})
|
||||
|
||||
registerInputHandler("shiny.action", function(val, ...) {
|
||||
# mark up the action button value with a special class so we can recognize it later
|
||||
class(val) <- c(class(val), "shinyActionButtonValue")
|
||||
val
|
||||
})
|
||||
|
||||
# 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$clients <- function(req) NULL
|
||||
|
||||
|
||||
clearClients <- function() {
|
||||
.globals$clients <- function(req) NULL
|
||||
}
|
||||
|
||||
|
||||
registerClient <- function(client) {
|
||||
.globals$clients <- append(.globals$clients, client)
|
||||
}
|
||||
|
||||
|
||||
.globals$resources <- list()
|
||||
|
||||
.globals$showcaseDefault <- 0
|
||||
|
||||
.globals$showcaseOverride <- FALSE
|
||||
|
||||
#' 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, period, 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=TRUE, perl=TRUE)) {
|
||||
stop("addResourcePath called with invalid prefix; please see documentation")
|
||||
}
|
||||
|
||||
if (prefix %in% c('shared')) {
|
||||
stop("addResourcePath called with the reserved prefix '", prefix, "'; ",
|
||||
"please use a different prefix")
|
||||
}
|
||||
|
||||
directoryPath <- normalizePath(directoryPath, mustWork=TRUE)
|
||||
|
||||
existing <- .globals$resources[[prefix]]
|
||||
|
||||
.globals$resources[[prefix]] <- list(directoryPath=directoryPath,
|
||||
func=staticHandler(directoryPath))
|
||||
}
|
||||
|
||||
resourcePathHandler <- function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
path <- req$PATH_INFO
|
||||
|
||||
match <- regexpr('^/([^/]+)/', path, perl=TRUE)
|
||||
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))
|
||||
|
||||
subreq <- as.environment(as.list(req, all.names=TRUE))
|
||||
subreq$PATH_INFO <- suffix
|
||||
subreq$SCRIPT_NAME <- paste(subreq$SCRIPT_NAME, substr(path, 1, 2 + len), sep='')
|
||||
|
||||
return(resInfo$func(subreq))
|
||||
}
|
||||
|
||||
#' 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.
|
||||
#' In older versions of Shiny, it was necessary to call \code{shinyServer()} in
|
||||
#' the \code{server.R} file, but this is no longer required as of Shiny 0.10.
|
||||
#' Now the \code{server.R} file may simply return the appropriate server
|
||||
#' function (as the last expression in the code), without calling
|
||||
#' \code{shinyServer()}.
|
||||
#'
|
||||
#' 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. It also takes an
|
||||
#' optional \code{session} parameter, which is used when greater control is
|
||||
#' needed.
|
||||
#'
|
||||
#' See the \href{http://rstudio.github.com/shiny/tutorial/}{tutorial} for more
|
||||
#' on how to write a server function.
|
||||
#'
|
||||
#' @param func The server function for this application. See the details section
|
||||
#' for more information.
|
||||
#'
|
||||
#' @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, session) {
|
||||
#' output$uppercase <- renderText({
|
||||
#' toupper(input$message)
|
||||
#' })
|
||||
#' })
|
||||
#'
|
||||
#'
|
||||
#' # It is also possible for a server.R file to simply return the function,
|
||||
#' # without calling shinyServer().
|
||||
#' # For example, the server.R file could contain just the following:
|
||||
#' function(input, output, session) {
|
||||
#' output$uppercase <- renderText({
|
||||
#' toupper(input$message)
|
||||
#' })
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
shinyServer <- function(func) {
|
||||
.globals$server <- list(func)
|
||||
invisible(func)
|
||||
}
|
||||
|
||||
decodeMessage <- function(data) {
|
||||
readInt <- function(pos) {
|
||||
packBits(rawToBits(data[pos:(pos+3)]), type='integer')
|
||||
}
|
||||
|
||||
if (readInt(1) != 0x01020202L) {
|
||||
# Treat message as UTF-8
|
||||
charData <- rawToChar(data)
|
||||
Encoding(charData) <- 'UTF-8'
|
||||
return(jsonlite::fromJSON(charData, simplifyVector=FALSE))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
appvars <- new.env()
|
||||
appvars$server <- NULL
|
||||
|
||||
sys.www.root <- system.file('www', package='shiny')
|
||||
|
||||
# This value, if non-NULL, must be present on all HTTP and WebSocket
|
||||
# requests as the Shiny-Shared-Secret header or else access will be
|
||||
# denied (403 response for HTTP, and instant close for websocket).
|
||||
sharedSecret <- getOption('shiny.sharedSecret')
|
||||
|
||||
appHandlers <- list(
|
||||
http = joinHandlers(c(
|
||||
sessionHandler,
|
||||
httpHandlers,
|
||||
sys.www.root,
|
||||
resourcePathHandler,
|
||||
reactLogHandler)),
|
||||
ws = function(ws) {
|
||||
if (!is.null(sharedSecret)
|
||||
&& !identical(sharedSecret, ws$request$HTTP_SHINY_SHARED_SECRET)) {
|
||||
ws$close()
|
||||
return(TRUE)
|
||||
}
|
||||
|
||||
shinysession <- ShinySession$new(ws)
|
||||
appsByToken$set(shinysession$token, shinysession)
|
||||
shinysession$setShowcase(.globals$showcaseDefault)
|
||||
|
||||
ws$onMessage(function(binary, msg) {
|
||||
# To ease transition from websockets-based code. Should remove once we're stable.
|
||||
if (is.character(msg))
|
||||
msg <- charToRaw(msg)
|
||||
|
||||
if (isTRUE(getOption('shiny.trace'))) {
|
||||
if (binary)
|
||||
message("RECV ", '$$binary data$$')
|
||||
else
|
||||
message("RECV ", rawToChar(msg))
|
||||
}
|
||||
|
||||
if (identical(charToRaw("\003\xe9"), msg))
|
||||
return()
|
||||
|
||||
msg <- decodeMessage(msg)
|
||||
|
||||
# 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
|
||||
|
||||
if (!inputHandlers$containsKey(splitName[[2]])){
|
||||
# No input handler registered for this type
|
||||
stop("No handler registered for for type ", name)
|
||||
}
|
||||
|
||||
msg$data[[ splitName[[1]] ]] <-
|
||||
inputHandlers$get(splitName[[2]])(
|
||||
val,
|
||||
shinysession,
|
||||
splitName[[1]] )
|
||||
}
|
||||
else if (is.list(val) && is.null(names(val))) {
|
||||
val_flat <- unlist(val, recursive = TRUE)
|
||||
|
||||
if (is.null(val_flat)) {
|
||||
# This is to assign NULL instead of deleting the item
|
||||
msg$data[name] <- list(NULL)
|
||||
} else {
|
||||
msg$data[[name]] <- val_flat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(
|
||||
msg$method,
|
||||
init = {
|
||||
|
||||
serverFunc <- serverFuncSource()
|
||||
if (!identicalFunctionBodies(serverFunc, appvars$server)) {
|
||||
appvars$server <- serverFunc
|
||||
if (!is.null(appvars$server))
|
||||
{
|
||||
# Tag this function as the Shiny server function. A debugger may use this
|
||||
# tag to give this function special treatment.
|
||||
# It's very important that it's appvars$server itself and NOT a copy that
|
||||
# is invoked, otherwise new breakpoints won't be picked up.
|
||||
attr(appvars$server, "shinyServerFunction") <- TRUE
|
||||
registerDebugHook("server", appvars, "Server Function")
|
||||
}
|
||||
}
|
||||
|
||||
# Check for switching into/out of showcase mode
|
||||
if (.globals$showcaseOverride &&
|
||||
exists(".clientdata_url_search", where = msg$data)) {
|
||||
mode <- showcaseModeOfQuerystring(msg$data$.clientdata_url_search)
|
||||
if (!is.null(mode))
|
||||
shinysession$setShowcase(mode)
|
||||
}
|
||||
|
||||
shinysession$manageInputs(msg$data)
|
||||
|
||||
# The client tells us what singletons were rendered into
|
||||
# the initial page
|
||||
if (!is.null(msg$data$.clientdata_singletons)) {
|
||||
shinysession$singletons <<- strsplit(
|
||||
msg$data$.clientdata_singletons, ',')[[1]]
|
||||
}
|
||||
|
||||
local({
|
||||
args <- list(
|
||||
input=shinysession$input,
|
||||
output=.createOutputWriter(shinysession))
|
||||
|
||||
# The clientData and session arguments are optional; check if
|
||||
# each exists
|
||||
if ('clientData' %in% names(formals(serverFunc)))
|
||||
args$clientData <- shinysession$clientData
|
||||
|
||||
if ('session' %in% names(formals(serverFunc)))
|
||||
args$session <- shinysession
|
||||
|
||||
withReactiveDomain(shinysession, {
|
||||
do.call(appvars$server, args)
|
||||
})
|
||||
})
|
||||
},
|
||||
update = {
|
||||
shinysession$manageInputs(msg$data)
|
||||
},
|
||||
shinysession$dispatch(msg)
|
||||
)
|
||||
shinysession$manageHiddenOutputs()
|
||||
|
||||
if (exists(".shiny__stdout", globalenv()) &&
|
||||
exists("HTTP_GUID", ws$request)) {
|
||||
# safe to assume we're in shiny-server
|
||||
shiny_stdout <- get(".shiny__stdout", globalenv())
|
||||
|
||||
# eNter a flushReact
|
||||
writeLines(paste("_n_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
|
||||
flushReact()
|
||||
|
||||
# eXit a flushReact
|
||||
writeLines(paste("_x_flushReact ", get("HTTP_GUID", ws$request),
|
||||
" @ ", sprintf("%.3f", as.numeric(Sys.time())),
|
||||
sep=""), con=shiny_stdout)
|
||||
flush(shiny_stdout)
|
||||
} else {
|
||||
flushReact()
|
||||
}
|
||||
lapply(appsByToken$values(), function(shinysession) {
|
||||
shinysession$flushOutput()
|
||||
NULL
|
||||
})
|
||||
})
|
||||
|
||||
ws$onClose(function() {
|
||||
shinysession$wsClosed()
|
||||
appsByToken$remove(shinysession$token)
|
||||
})
|
||||
|
||||
return(TRUE)
|
||||
}
|
||||
)
|
||||
return(appHandlers)
|
||||
}
|
||||
|
||||
getEffectiveBody <- function(func) {
|
||||
# Note: NULL values are OK. isS4(NULL) returns FALSE, body(NULL)
|
||||
# returns NULL.
|
||||
if (isS4(func) && class(func) == "functionWithTrace")
|
||||
body(func@original)
|
||||
else
|
||||
body(func)
|
||||
}
|
||||
|
||||
identicalFunctionBodies <- function(a, b) {
|
||||
identical(getEffectiveBody(a), getEffectiveBody(b))
|
||||
}
|
||||
|
||||
handlerManager <- HandlerManager$new()
|
||||
|
||||
addSubApp <- function(appObj, autoRemove = TRUE) {
|
||||
path <- createUniqueId(16, "/app")
|
||||
appHandlers <- createAppHandlers(appObj$httpHandler, appObj$serverFuncSource)
|
||||
|
||||
# remove the leading / from the path so a relative path is returned
|
||||
# (needed for the case where the root URL for the Shiny app isn't /, such
|
||||
# as portmapped URLs)
|
||||
finalPath <- paste(
|
||||
substr(path, 2, nchar(path)),
|
||||
"/?w=", workerId(),
|
||||
"&__subapp__=1",
|
||||
sep="")
|
||||
handlerManager$addHandler(routeHandler(path, appHandlers$http), finalPath)
|
||||
handlerManager$addWSHandler(routeWSHandler(path, appHandlers$ws), finalPath)
|
||||
|
||||
if (autoRemove) {
|
||||
# If a session is currently active, remove this subapp automatically when
|
||||
# the current session ends
|
||||
onReactiveDomainEnded(getDefaultReactiveDomain(), function() {
|
||||
removeSubApp(finalPath)
|
||||
})
|
||||
}
|
||||
|
||||
return(finalPath)
|
||||
}
|
||||
|
||||
removeSubApp <- function(path) {
|
||||
handlerManager$removeHandler(path)
|
||||
handlerManager$removeWSHandler(path)
|
||||
}
|
||||
|
||||
startApp <- function(appObj, port, host, quiet) {
|
||||
appHandlers <- createAppHandlers(appObj$httpHandler, appObj$serverFuncSource)
|
||||
handlerManager$addHandler(appHandlers$http, "/", tail = TRUE)
|
||||
handlerManager$addWSHandler(appHandlers$ws, "/", tail = TRUE)
|
||||
|
||||
if (is.numeric(port) || is.integer(port)) {
|
||||
if (!quiet) {
|
||||
message('\n', 'Listening on http://', host, ':', port)
|
||||
}
|
||||
return(startServer(host, port, handlerManager$createHttpuvApp()))
|
||||
} else if (is.character(port)) {
|
||||
if (!quiet) {
|
||||
message('\n', 'Listening on domain socket ', port)
|
||||
}
|
||||
mask <- attr(port, 'mask')
|
||||
return(startPipeServer(port, mask, handlerManager$createHttpuvApp()))
|
||||
}
|
||||
}
|
||||
|
||||
# Run an application that was created by \code{\link{startApp}}. This
|
||||
# function should normally be called in a \code{while(TRUE)} loop.
|
||||
serviceApp <- function() {
|
||||
if (timerCallbacks$executeElapsed()) {
|
||||
for (shinysession in appsByToken$values()) {
|
||||
shinysession$manageHiddenOutputs()
|
||||
}
|
||||
|
||||
flushReact()
|
||||
|
||||
for (shinysession in appsByToken$values()) {
|
||||
shinysession$flushOutput()
|
||||
}
|
||||
}
|
||||
|
||||
# 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, 1000)
|
||||
|
||||
timeout <- max(1, min(maxTimeout, timerCallbacks$timeToNextEvent()))
|
||||
service(timeout)
|
||||
}
|
||||
|
||||
.shinyServerMinVersion <- '0.3.4'
|
||||
|
||||
#' 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).
|
||||
#'
|
||||
#' The host parameter was introduced in Shiny 0.9.0. Its default value of
|
||||
#' \code{"127.0.0.1"} means that, contrary to previous versions of Shiny, only
|
||||
#' the current machine can access locally hosted Shiny apps. To allow other
|
||||
#' clients to connect, use the value \code{"0.0.0.0"} instead (which was the
|
||||
#' value that was hard-coded into Shiny in 0.8.0 and earlier).
|
||||
#'
|
||||
#' @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}. Alternately, instead of
|
||||
#' \code{server.R} and \code{ui.R}, the directory may contain just
|
||||
#' \code{app.R}. Defaults to the working directory. Instead of a directory,
|
||||
#' this could be a list with \code{ui} and \code{server} components, or a
|
||||
#' Shiny app object created by \code{\link{shinyApp}}.
|
||||
#' @param port The TCP port that the application should listen on. If the
|
||||
#' \code{port} is not specified, and the \code{shiny.port} option is set (with
|
||||
#' \code{options(shiny.port = XX)}), then that port will be used. Otherwise,
|
||||
#' use a random port.
|
||||
#' @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. This value of this parameter can also be a
|
||||
#' function to call with the application's URL.
|
||||
#' @param host The IPv4 address that the application should listen on. Defaults
|
||||
#' to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not. See
|
||||
#' Details.
|
||||
#' @param workerId Can generally be ignored. Exists to help some editions of
|
||||
#' Shiny Server Pro route requests to the correct process.
|
||||
#' @param quiet Should Shiny status messages be shown? Defaults to FALSE.
|
||||
#' @param display.mode The mode in which to display the application. If set to
|
||||
#' the value \code{"showcase"}, shows application code and metadata from a
|
||||
#' \code{DESCRIPTION} file in the application directory alongside the
|
||||
#' application. If set to \code{"normal"}, displays the application normally.
|
||||
#' Defaults to \code{"auto"}, which displays the application in the mode given
|
||||
#' in its \code{DESCRIPTION} file, if any.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Start app in the current working directory
|
||||
#' runApp()
|
||||
#'
|
||||
#' # Start app in a subdirectory called myapp
|
||||
#' runApp("myapp")
|
||||
#' }
|
||||
#'
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # Apps can be run without a server.r and ui.r file
|
||||
#' runApp(list(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' ))
|
||||
#'
|
||||
#'
|
||||
#' # Running a Shiny app object
|
||||
#' app <- shinyApp(
|
||||
#' ui = bootstrapPage(
|
||||
#' numericInput('n', 'Number of obs', 100),
|
||||
#' plotOutput('plot')
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$plot <- renderPlot({ hist(runif(input$n)) })
|
||||
#' }
|
||||
#' )
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#' @export
|
||||
runApp <- function(appDir=getwd(),
|
||||
port=getOption('shiny.port'),
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
workerId="", quiet=FALSE,
|
||||
display.mode=c("auto", "normal", "showcase")) {
|
||||
on.exit({
|
||||
handlerManager$clear()
|
||||
}, add = TRUE)
|
||||
|
||||
|
||||
if (is.null(host) || is.na(host))
|
||||
host <- '0.0.0.0'
|
||||
|
||||
# Make warnings print immediately
|
||||
ops <- options(warn = 1)
|
||||
on.exit(options(ops), add = TRUE)
|
||||
|
||||
workerId(workerId)
|
||||
|
||||
if (nzchar(Sys.getenv('SHINY_PORT'))) {
|
||||
# If SHINY_PORT is set, we're running under Shiny Server. Check the version
|
||||
# to make sure it is compatible. Older versions of Shiny Server don't set
|
||||
# SHINY_SERVER_VERSION, those will return "" which is considered less than
|
||||
# any valid version.
|
||||
ver <- Sys.getenv('SHINY_SERVER_VERSION')
|
||||
if (compareVersion(ver, .shinyServerMinVersion) < 0) {
|
||||
warning('Shiny Server v', .shinyServerMinVersion,
|
||||
' or later is required; please upgrade!')
|
||||
}
|
||||
}
|
||||
|
||||
# Showcase mode is disabled by default; it must be explicitly enabled in
|
||||
# either the DESCRIPTION file for directory-based apps, or via
|
||||
# the display.mode parameter. The latter takes precedence.
|
||||
setShowcaseDefault(0)
|
||||
|
||||
# If appDir specifies a path, and display mode is specified in the
|
||||
# DESCRIPTION file at that path, apply it here.
|
||||
if (is.character(appDir)) {
|
||||
desc <- file.path.ci(appDir, "DESCRIPTION")
|
||||
if (file.exists(desc)) {
|
||||
con <- file(desc, encoding = checkEncoding(desc))
|
||||
on.exit(close(con), add = TRUE)
|
||||
settings <- read.dcf(con)
|
||||
if ("DisplayMode" %in% colnames(settings)) {
|
||||
mode <- settings[1,"DisplayMode"]
|
||||
if (mode == "Showcase") {
|
||||
setShowcaseDefault(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# If display mode is specified as an argument, apply it (overriding the
|
||||
# value specified in DESCRIPTION, if any).
|
||||
display.mode <- match.arg(display.mode)
|
||||
if (display.mode == "normal")
|
||||
setShowcaseDefault(0)
|
||||
else if (display.mode == "showcase")
|
||||
setShowcaseDefault(1)
|
||||
|
||||
require(shiny)
|
||||
|
||||
# determine port if we need to
|
||||
if (is.null(port)) {
|
||||
|
||||
# Try up to 20 random ports. If we don't succeed just plow ahead
|
||||
# with the final value we tried, and let the "real" startServer
|
||||
# somewhere down the line fail and throw the error to the user.
|
||||
#
|
||||
# If we (think we) succeed, save the value as .globals$lastPort,
|
||||
# and try that first next time the user wants a random port.
|
||||
|
||||
for (i in 1:20) {
|
||||
if (!is.null(.globals$lastPort)) {
|
||||
port <- .globals$lastPort
|
||||
.globals$lastPort <- NULL
|
||||
}
|
||||
else {
|
||||
# Try up to 20 random ports
|
||||
port <- p_randomInt(3000, 8000)
|
||||
}
|
||||
|
||||
# Test port to see if we can use it
|
||||
tmp <- try(startServer(host, port, list()), silent=TRUE)
|
||||
if (!inherits(tmp, 'try-error')) {
|
||||
stopServer(tmp)
|
||||
.globals$lastPort <- port
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appParts <- as.shiny.appobj(appDir)
|
||||
# Set up the onEnd before we call onStart, so that it gets called even if an
|
||||
# error happens in onStart.
|
||||
if (!is.null(appParts$onEnd))
|
||||
on.exit(appParts$onEnd(), add = TRUE)
|
||||
if (!is.null(appParts$onStart))
|
||||
appParts$onStart()
|
||||
|
||||
server <- startApp(appParts, port, host, quiet)
|
||||
|
||||
on.exit({
|
||||
stopServer(server)
|
||||
}, add = TRUE)
|
||||
|
||||
if (!is.character(port)) {
|
||||
# http://0.0.0.0/ doesn't work on QtWebKit (i.e. RStudio viewer)
|
||||
browseHost <- if (identical(host, "0.0.0.0")) "127.0.0.1" else host
|
||||
|
||||
appUrl <- paste("http://", browseHost, ":", port, sep="")
|
||||
if (is.function(launch.browser))
|
||||
launch.browser(appUrl)
|
||||
else if (launch.browser)
|
||||
utils::browseURL(appUrl)
|
||||
} else {
|
||||
appUrl <- NULL
|
||||
}
|
||||
|
||||
# call application hooks
|
||||
callAppHook("onAppStart", appUrl)
|
||||
on.exit({
|
||||
callAppHook("onAppStop", appUrl)
|
||||
}, add = TRUE)
|
||||
|
||||
.globals$retval <- NULL
|
||||
.globals$stopped <- FALSE
|
||||
shinyCallingHandlers(
|
||||
while (!.globals$stopped) {
|
||||
serviceApp()
|
||||
Sys.sleep(0.001)
|
||||
}
|
||||
)
|
||||
|
||||
return(.globals$retval)
|
||||
}
|
||||
|
||||
#' Stop the currently running Shiny app
|
||||
#'
|
||||
#' Stops the currently running Shiny app, returning control to the caller of
|
||||
#' \code{\link{runApp}}.
|
||||
#'
|
||||
#' @param returnValue The value that should be returned from
|
||||
#' \code{\link{runApp}}.
|
||||
#'
|
||||
#' @export
|
||||
stopApp <- function(returnValue = NULL) {
|
||||
.globals$retval <- returnValue
|
||||
.globals$stopped <- TRUE
|
||||
httpuv::interrupt()
|
||||
}
|
||||
|
||||
#' 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
|
||||
#' choosing a random port.
|
||||
#' @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.
|
||||
#' @param host The IPv4 address that the application should listen on. Defaults
|
||||
#' to the \code{shiny.host} option, if set, or \code{"127.0.0.1"} if not.
|
||||
#' @param display.mode The mode in which to display the example. Defaults to
|
||||
#' \code{showcase}, but may be set to \code{normal} to see the example without
|
||||
#' code or commentary.
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # List all available examples
|
||||
#' runExample()
|
||||
#'
|
||||
#' # Run one of the examples
|
||||
#' runExample("01_hello")
|
||||
#'
|
||||
#' # Print the directory containing the code for all examples
|
||||
#' system.file("examples", package="shiny")
|
||||
#' }
|
||||
#' @export
|
||||
runExample <- function(example=NA,
|
||||
port=NULL,
|
||||
launch.browser=getOption('shiny.launch.browser',
|
||||
interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
display.mode=c("auto", "normal", "showcase")) {
|
||||
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, host = host, launch.browser = launch.browser,
|
||||
display.mode = display.mode)
|
||||
}
|
||||
}
|
||||
132
R/shinyui.R
Normal file
132
R/shinyui.R
Normal file
@@ -0,0 +1,132 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
#' Load the MathJax library and typeset math expressions
|
||||
#'
|
||||
#' This function adds MathJax to the page and typeset the math expressions (if
|
||||
#' found) in the content \code{...}. It only needs to be called once in an app
|
||||
#' unless the content is rendered \emph{after} the page is loaded, e.g. via
|
||||
#' \code{\link{renderUI}}, in which case we have to call it explicitly every
|
||||
#' time we write math expressions to the output.
|
||||
#' @param ... any HTML elements to apply MathJax to
|
||||
#' @export
|
||||
#' @examples withMathJax(helpText("Some math here $$\\alpha+\\beta$$"))
|
||||
#' # now we can just write "static" content without withMathJax()
|
||||
#' div("more math here $$\\sqrt{2}$$")
|
||||
withMathJax <- function(...) {
|
||||
path <- 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'
|
||||
tagList(
|
||||
tags$head(
|
||||
singleton(tags$script(src = path, type = 'text/javascript'))
|
||||
),
|
||||
...,
|
||||
tags$script(HTML('MathJax.Hub.Queue(["Typeset", MathJax.Hub]);'))
|
||||
)
|
||||
}
|
||||
|
||||
renderPage <- function(ui, connection, showcase=0) {
|
||||
|
||||
if (showcase > 0)
|
||||
ui <- showcaseUI(ui)
|
||||
|
||||
# Wrap ui in body tag if it doesn't already have a single top-level body tag.
|
||||
if (!(inherits(ui, "shiny.tag") && ui$name == "body"))
|
||||
ui <- tags$body(ui)
|
||||
|
||||
result <- renderTags(ui)
|
||||
|
||||
deps <- c(
|
||||
list(
|
||||
htmlDependency("json2", "2014.02.04", c(href="shared"), script = "json2-min.js"),
|
||||
htmlDependency("jquery", "1.11.0", c(href="shared"), script = "jquery.min.js"),
|
||||
htmlDependency("shiny", packageVersion("shiny"), c(href="shared"),
|
||||
script = "shiny.min.js", stylesheet = "shiny.css")
|
||||
),
|
||||
result$dependencies
|
||||
)
|
||||
deps <- resolveDependencies(deps)
|
||||
deps <- lapply(deps, createWebDependency)
|
||||
depStr <- paste(sapply(deps, function(dep) {
|
||||
sprintf("%s[%s]", dep$name, dep$version)
|
||||
}), collapse = ";")
|
||||
depHtml <- renderDependencies(deps, "href")
|
||||
|
||||
# write preamble
|
||||
writeLines(c('<!DOCTYPE html>',
|
||||
'<html>',
|
||||
'<head>',
|
||||
' <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>',
|
||||
sprintf(' <script type="application/shiny-singletons">%s</script>',
|
||||
paste(result$singletons, collapse = ',')
|
||||
),
|
||||
sprintf(' <script type="application/html-dependencies">%s</script>',
|
||||
depStr
|
||||
),
|
||||
depHtml
|
||||
),
|
||||
con = connection)
|
||||
writeLines(c(result$head,
|
||||
'</head>',
|
||||
recursive=TRUE),
|
||||
con = connection)
|
||||
|
||||
writeLines(result$html, con = connection)
|
||||
|
||||
# write end document
|
||||
writeLines('</html>',
|
||||
con = connection)
|
||||
}
|
||||
|
||||
#' Create a Shiny UI handler
|
||||
#'
|
||||
#' Historically this function was used in ui.R files to register a user
|
||||
#' interface with Shiny. It is no longer required as of Shiny 0.10; simply
|
||||
#' ensure that the last expression to be returned from ui.R is a user interface.
|
||||
#' This function is kept for backwards compatibility with older applications. It
|
||||
#' returns the value that is passed to it.
|
||||
#'
|
||||
#' @param ui A user interace definition
|
||||
#' @return The user interface definition, without modifications or side effects.
|
||||
#'
|
||||
#' @export
|
||||
shinyUI <- function(ui) {
|
||||
.globals$ui <- list(ui)
|
||||
ui
|
||||
}
|
||||
|
||||
uiHttpHandler <- function(ui, uiPattern = "^/$") {
|
||||
|
||||
force(ui)
|
||||
|
||||
function(req) {
|
||||
if (!identical(req$REQUEST_METHOD, 'GET'))
|
||||
return(NULL)
|
||||
|
||||
if (!isTRUE(grepl(uiPattern, req$PATH_INFO)))
|
||||
return(NULL)
|
||||
|
||||
textConn <- textConnection(NULL, "w")
|
||||
on.exit(close(textConn))
|
||||
|
||||
showcaseMode <- .globals$showcaseDefault
|
||||
if (.globals$showcaseOverride) {
|
||||
mode <- showcaseModeOfReq(req)
|
||||
if (!is.null(mode))
|
||||
showcaseMode <- mode
|
||||
}
|
||||
uiValue <- if (is.function(ui)) {
|
||||
if (length(formals(ui)) > 0)
|
||||
ui(req)
|
||||
else
|
||||
ui()
|
||||
} else {
|
||||
ui
|
||||
}
|
||||
if (is.null(uiValue))
|
||||
return(NULL)
|
||||
|
||||
renderPage(uiValue, textConn, showcaseMode)
|
||||
html <- paste(textConnectionValue(textConn), collapse='\n')
|
||||
return(httpResponse(200, content=enc2utf8(html)))
|
||||
}
|
||||
}
|
||||
573
R/shinywrappers.R
Normal file
573
R/shinywrappers.R
Normal file
@@ -0,0 +1,573 @@
|
||||
globalVariables('func')
|
||||
|
||||
#' Mark a function as a render function
|
||||
#'
|
||||
#' Should be called by implementers of \code{renderXXX} functions in order to
|
||||
#' mark their return values as Shiny render functions, and to provide a hint to
|
||||
#' Shiny regarding what UI function is most commonly used with this type of
|
||||
#' render function. This can be used in R Markdown documents to create complete
|
||||
#' output widgets out of just the render function.
|
||||
#'
|
||||
#' @param uiFunc A function that renders Shiny UI. Must take a single argument:
|
||||
#' an output ID.
|
||||
#' @param renderFunc A function that is suitable for assigning to a Shiny output
|
||||
#' slot.
|
||||
#' @return The \code{renderFunc} function, with annotations.
|
||||
#'
|
||||
#' @export
|
||||
markRenderFunction <- function(uiFunc, renderFunc) {
|
||||
structure(renderFunc,
|
||||
class = c("shiny.render.function", "function"),
|
||||
outputFunc = uiFunc)
|
||||
}
|
||||
|
||||
useRenderFunction <- function(renderFunc, inline = FALSE) {
|
||||
outputFunction <- attr(renderFunc, "outputFunc")
|
||||
id <- createUniqueId(8, "out")
|
||||
o <- getDefaultReactiveDomain()$output
|
||||
if (!is.null(o))
|
||||
o[[id]] <- renderFunc
|
||||
if (is.logical(formals(outputFunction)[["inline"]])) {
|
||||
outputFunction(id, inline = inline)
|
||||
} else outputFunction(id)
|
||||
}
|
||||
|
||||
#' @export
|
||||
#' @method as.tags shiny.render.function
|
||||
as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
useRenderFunction(x, inline = inline)
|
||||
}
|
||||
|
||||
#' Image file output
|
||||
#'
|
||||
#' Renders a reactive image that is suitable for assigning to an \code{output}
|
||||
#' slot.
|
||||
#'
|
||||
#' The expression \code{expr} must return a list containing the attributes for
|
||||
#' the \code{img} object on the client web page. For the image to display,
|
||||
#' properly, the list must have at least one entry, \code{src}, which is the
|
||||
#' path to the image file. It may also useful to have a \code{contentType}
|
||||
#' entry specifying the MIME type of the image. If one is not provided,
|
||||
#' \code{renderImage} will try to autodetect the type, based on the file
|
||||
#' extension.
|
||||
#'
|
||||
#' Other elements such as \code{width}, \code{height}, \code{class}, and
|
||||
#' \code{alt}, can also be added to the list, and they will be used as
|
||||
#' attributes in the \code{img} object.
|
||||
#'
|
||||
#' The corresponding HTML output tag should be \code{div} or \code{img} and have
|
||||
#' the CSS class name \code{shiny-image-output}.
|
||||
#'
|
||||
#' @seealso For more details on how the images are generated, and how to control
|
||||
#' the output, see \code{\link{plotPNG}}.
|
||||
#'
|
||||
#' @param expr An expression that returns a list.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param deleteFile Should the file in \code{func()$src} be deleted after
|
||||
#' it is sent to the client browser? Generally speaking, if the image is a
|
||||
#' temp file generated within \code{func}, then this should be \code{TRUE};
|
||||
#' if the image is not a temp file, this should be \code{FALSE}.
|
||||
#'
|
||||
#' @export
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#'
|
||||
#' shinyServer(function(input, output, clientData) {
|
||||
#'
|
||||
#' # A plot of fixed size
|
||||
#' output$plot1 <- renderImage({
|
||||
#' # A temp file to save the output. It will be deleted after renderImage
|
||||
#' # sends it, because deleteFile=TRUE.
|
||||
#' outfile <- tempfile(fileext='.png')
|
||||
#'
|
||||
#' # Generate a png
|
||||
#' png(outfile, width=400, height=400)
|
||||
#' hist(rnorm(input$n))
|
||||
#' dev.off()
|
||||
#'
|
||||
#' # Return a list
|
||||
#' list(src = outfile,
|
||||
#' alt = "This is alternate text")
|
||||
#' }, deleteFile = TRUE)
|
||||
#'
|
||||
#' # A dynamically-sized plot
|
||||
#' output$plot2 <- renderImage({
|
||||
#' # Read plot2's width and height. These are reactive values, so this
|
||||
#' # expression will re-run whenever these values change.
|
||||
#' width <- clientData$output_plot2_width
|
||||
#' height <- clientData$output_plot2_height
|
||||
#'
|
||||
#' # A temp file to save the output.
|
||||
#' outfile <- tempfile(fileext='.png')
|
||||
#'
|
||||
#' png(outfile, width=width, height=height)
|
||||
#' hist(rnorm(input$obs))
|
||||
#' dev.off()
|
||||
#'
|
||||
#' # Return a list containing the filename
|
||||
#' list(src = outfile,
|
||||
#' width = width,
|
||||
#' height = height,
|
||||
#' alt = "This is alternate text")
|
||||
#' }, deleteFile = TRUE)
|
||||
#'
|
||||
#' # Send a pre-rendered image, and don't delete the image after sending it
|
||||
#' output$plot3 <- renderImage({
|
||||
#' # When input$n is 1, filename is ./images/image1.jpeg
|
||||
#' filename <- normalizePath(file.path('./images',
|
||||
#' paste('image', input$n, '.jpeg', sep='')))
|
||||
#'
|
||||
#' # Return a list containing the filename
|
||||
#' list(src = filename)
|
||||
#' }, deleteFile = FALSE)
|
||||
#' })
|
||||
#'
|
||||
#' }
|
||||
renderImage <- function(expr, env=parent.frame(), quoted=FALSE,
|
||||
deleteFile=TRUE) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
return(markRenderFunction(imageOutput, function(shinysession, name, ...) {
|
||||
imageinfo <- func()
|
||||
# Should the file be deleted after being sent? If .deleteFile not set or if
|
||||
# TRUE, then delete; otherwise don't delete.
|
||||
if (deleteFile) {
|
||||
on.exit(unlink(imageinfo$src))
|
||||
}
|
||||
|
||||
# If contentType not specified, autodetect based on extension
|
||||
contentType <- imageinfo$contentType %OR% getContentType(imageinfo$src)
|
||||
|
||||
# Extra values are everything in imageinfo except 'src' and 'contentType'
|
||||
extra_attr <- imageinfo[!names(imageinfo) %in% c('src', 'contentType')]
|
||||
|
||||
# Return a list with src, and other img attributes
|
||||
c(src = shinysession$fileUrl(name, file=imageinfo$src, contentType=contentType),
|
||||
extra_attr)
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
#' 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 expr An expression 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}} and
|
||||
#' \code{\link[xtable]{print.xtable}}.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns an R object that can be used with
|
||||
#' \code{\link[xtable]{xtable}} (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @export
|
||||
renderTable <- function(expr, ..., env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderTable: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
markRenderFunction(tableOutput, function() {
|
||||
classNames <- getOption('shiny.table.class') %OR% 'data table table-bordered table-condensed'
|
||||
data <- func()
|
||||
|
||||
if (is.null(data) || identical(data, data.frame()))
|
||||
return("")
|
||||
|
||||
return(paste(
|
||||
capture.output(
|
||||
print(xtable(data, ...),
|
||||
type='html',
|
||||
html.table.attributes=paste('class="',
|
||||
htmlEscape(classNames, TRUE),
|
||||
'"',
|
||||
sep=''), ...)),
|
||||
collapse="\n"))
|
||||
})
|
||||
}
|
||||
|
||||
#' Printable Output
|
||||
#'
|
||||
#' Makes a reactive version of the given function that captures any printed
|
||||
#' output, and also captures its printable result (unless
|
||||
#' \code{\link{invisible}}), into a string. The resulting 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.
|
||||
#'
|
||||
#' Note that unlike most other Shiny output functions, if the given function
|
||||
#' returns \code{NULL} then \code{NULL} will actually be visible in the output.
|
||||
#' To display nothing, make your function return \code{\link{invisible}()}.
|
||||
#'
|
||||
#' @param expr An expression that may print output and/or return a printable R
|
||||
#' object.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' @param func A function that may print output and/or return a printable R
|
||||
#' object (deprecated; use \code{expr} instead).
|
||||
#' @param width The value for \code{\link{options}('width')}.
|
||||
#' @seealso \code{\link{renderText}} for displaying the value returned from a
|
||||
#' function, instead of the printed output.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#' @export
|
||||
renderPrint <- function(expr, env = parent.frame(), quoted = FALSE, func = NULL,
|
||||
width = getOption('width')) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderPrint: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
markRenderFunction(verbatimTextOutput, function() {
|
||||
op <- options(width = width)
|
||||
on.exit(options(op), add = TRUE)
|
||||
paste(capture.output(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 expr An expression that returns an R object that can be used as an
|
||||
#' argument to \code{cat}.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns an R object that can be used as an
|
||||
#' argument to \code{cat}.(deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @seealso \code{\link{renderPrint}} for capturing the print output of a
|
||||
#' function, rather than the returned text value.
|
||||
#'
|
||||
#' @example res/text-example.R
|
||||
#'
|
||||
#' @export
|
||||
renderText <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderText: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
markRenderFunction(textOutput, function() {
|
||||
value <- func()
|
||||
return(paste(capture.output(cat(value)), 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 expr An expression that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' or a list of such objects.
|
||||
#' @param env The environment in which to evaluate \code{expr}.
|
||||
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
|
||||
#' is useful if you want to save an expression in a variable.
|
||||
#' @param func A function that returns a Shiny tag object, \code{\link{HTML}},
|
||||
#' or a list of such objects (deprecated; use \code{expr} instead).
|
||||
#'
|
||||
#' @seealso conditionalPanel
|
||||
#'
|
||||
#' @export
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' output$moreControls <- renderUI({
|
||||
#' list(
|
||||
#'
|
||||
#' )
|
||||
#' })
|
||||
#' }
|
||||
renderUI <- function(expr, env=parent.frame(), quoted=FALSE, func=NULL) {
|
||||
if (!is.null(func)) {
|
||||
shinyDeprecated(msg="renderUI: argument 'func' is deprecated. Please use 'expr' instead.")
|
||||
} else {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
}
|
||||
|
||||
markRenderFunction(uiOutput, function(shinysession, name, ...) {
|
||||
result <- func()
|
||||
if (is.null(result) || length(result) == 0)
|
||||
return(NULL)
|
||||
|
||||
result <- takeSingletons(result, shinysession$singletons, desingleton=FALSE)$ui
|
||||
result <- surroundSingletons(result)
|
||||
dependencies <- lapply(resolveDependencies(findDependencies(result)),
|
||||
createWebDependency)
|
||||
names(dependencies) <- NULL
|
||||
|
||||
# renderTags returns a list with head, singletons, and html
|
||||
output <- list(
|
||||
html = doRenderTags(result),
|
||||
deps = dependencies
|
||||
)
|
||||
|
||||
return(output)
|
||||
})
|
||||
}
|
||||
|
||||
#' File Downloads
|
||||
#'
|
||||
#' Allows content from the Shiny application to be made available to the user as
|
||||
#' file downloads (for example, downloading the currently visible data as a CSV
|
||||
#' file). Both filename and contents can be calculated dynamically at the time
|
||||
#' the user initiates the download. Assign the return value to a slot on
|
||||
#' \code{output} in your server function, and in the UI use
|
||||
#' \code{\link{downloadButton}} or \code{\link{downloadLink}} to make the
|
||||
#' download available.
|
||||
#'
|
||||
#' @param filename A string of the filename, including extension, that the
|
||||
#' user's web browser should default to when downloading the file; or a
|
||||
#' function that returns such a string. (Reactive values and functions may be
|
||||
#' used from this function.)
|
||||
#' @param content A function that takes a single argument \code{file} that is a
|
||||
#' file path (string) of a nonexistent temp file, and writes the content to
|
||||
#' that file path. (Reactive values and functions may be used from this
|
||||
#' function.)
|
||||
#' @param contentType A string of the download's
|
||||
#' \href{http://en.wikipedia.org/wiki/Internet_media_type}{content type}, for
|
||||
#' example \code{"text/csv"} or \code{"image/png"}. If \code{NULL} or
|
||||
#' \code{NA}, the content type will be guessed based on the filename
|
||||
#' extension, or \code{application/octet-stream} if the extension is unknown.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # In server.R:
|
||||
#' output$downloadData <- downloadHandler(
|
||||
#' filename = function() {
|
||||
#' paste('data-', Sys.Date(), '.csv', sep='')
|
||||
#' },
|
||||
#' content = function(file) {
|
||||
#' write.csv(data, file)
|
||||
#' }
|
||||
#' )
|
||||
#'
|
||||
#' # In ui.R:
|
||||
#' downloadLink('downloadData', 'Download')
|
||||
#' }
|
||||
#'
|
||||
#' @export
|
||||
downloadHandler <- function(filename, content, contentType=NA) {
|
||||
return(markRenderFunction(downloadButton, function(shinysession, name, ...) {
|
||||
shinysession$registerDownload(name, filename, contentType, content)
|
||||
}))
|
||||
}
|
||||
|
||||
#' Table output with the JavaScript library DataTables
|
||||
#'
|
||||
#' Makes a reactive version of the given function that returns a data frame (or
|
||||
#' matrix), which will be rendered with the DataTables library. Paging,
|
||||
#' searching, filtering, and sorting can be done on the R side using Shiny as
|
||||
#' the server infrastructure.
|
||||
#'
|
||||
#' For the \code{options} argument, the character elements that have the class
|
||||
#' \code{"AsIs"} (usually returned from \code{\link{I}()}) will be evaluated in
|
||||
#' JavaScript. This is useful when the type of the option value is not supported
|
||||
#' in JSON, e.g., a JavaScript function, which can be obtained by evaluating a
|
||||
#' character string. Note this only applies to the root-level elements of the
|
||||
#' options list, and the \code{I()} notation does not work for lower-level
|
||||
#' elements in the list.
|
||||
#' @param expr An expression that returns a data frame or a matrix.
|
||||
#' @param options A list of initialization options to be passed to DataTables,
|
||||
#' or a function to return such a list.
|
||||
#' @param searchDelay The delay for searching, in milliseconds (to avoid too
|
||||
#' frequent search requests).
|
||||
#' @param callback A JavaScript function to be applied to the DataTable object.
|
||||
#' This is useful for DataTables plug-ins, which often require the DataTable
|
||||
#' instance to be available (\url{http://datatables.net/extensions/}).
|
||||
#' @param escape Whether to escape HTML entities in the table: \code{TRUE} means
|
||||
#' to escape the whole table, and \code{FALSE} means not to escape it.
|
||||
#' Alternatively, you can specify numeric column indices or column names to
|
||||
#' indicate which columns to escape, e.g. \code{1:5} (the first 5 columns),
|
||||
#' \code{c(1, 3, 4)}, or \code{c(-1, -3)} (all columns except the first and
|
||||
#' third), or \code{c('Species', 'Sepal.Length')}.
|
||||
#' @references \url{http://datatables.net}
|
||||
#' @note This function only provides the server-side version of DataTables
|
||||
#' (using R to process the data object on the server side). There is a
|
||||
#' separate package \pkg{DT} (\url{https://github.com/rstudio/DT}) that allows
|
||||
#' you to create both server-side and client-side DataTables, and supports
|
||||
#' additional DataTables features. Consider using \code{DT::renderDataTable()}
|
||||
#' and \code{DT::dataTableOutput()} (see
|
||||
#' \url{http://rstudio.github.io/DT/shiny.html} for more information).
|
||||
#' @export
|
||||
#' @inheritParams renderPlot
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' # pass a callback function to DataTables using I()
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' fluidRow(
|
||||
#' column(12,
|
||||
#' dataTableOutput('table')
|
||||
#' )
|
||||
#' )
|
||||
#' ),
|
||||
#' server = function(input, output) {
|
||||
#' output$table <- renderDataTable(iris,
|
||||
#' options = list(
|
||||
#' pageLength = 5,
|
||||
#' initComplete = I("function(settings, json) {alert('Done.');}")
|
||||
#' )
|
||||
#' )
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
renderDataTable <- function(expr, options = NULL, searchDelay = 500,
|
||||
callback = 'function(oTable) {}', escape = TRUE,
|
||||
env = parent.frame(), quoted = FALSE) {
|
||||
installExprFunction(expr, "func", env, quoted)
|
||||
|
||||
markRenderFunction(dataTableOutput, function(shinysession, name, ...) {
|
||||
if (is.function(options)) options <- options()
|
||||
options <- checkDT9(options)
|
||||
res <- checkAsIs(options)
|
||||
data <- func()
|
||||
if (length(dim(data)) != 2) return() # expects a rectangular data object
|
||||
if (is.data.frame(data)) data <- as.data.frame(data)
|
||||
action <- shinysession$registerDataObj(name, data, dataTablesJSON)
|
||||
colnames <- colnames(data)
|
||||
# if escape is column names, turn names to numeric indices
|
||||
if (is.character(escape)) {
|
||||
escape <- setNames(seq_len(ncol(data)), colnames)[escape]
|
||||
if (any(is.na(escape)))
|
||||
stop("Some column names in the 'escape' argument not found in data")
|
||||
}
|
||||
colnames[escape] <- htmlEscape(colnames[escape])
|
||||
if (!is.logical(escape)) {
|
||||
if (!is.numeric(escape))
|
||||
stop("'escape' must be TRUE, FALSE, or a numeric vector, or column names")
|
||||
escape <- paste(escape, collapse = ',')
|
||||
}
|
||||
list(
|
||||
colnames = colnames, action = action, options = res$options,
|
||||
evalOptions = if (length(res$eval)) I(res$eval), searchDelay = searchDelay,
|
||||
callback = paste(callback, collapse = '\n'), escape = escape
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
# a data frame containing the DataTables 1.9 and 1.10 names
|
||||
DT10Names <- function() {
|
||||
rbind(
|
||||
read.table(system.file('www/shared/datatables/upgrade1.10.txt', package = 'shiny'),
|
||||
stringsAsFactors = FALSE),
|
||||
c('aoColumns', 'Removed') # looks like an omission on the upgrade guide
|
||||
)
|
||||
}
|
||||
|
||||
# check DataTables 1.9.x options, and give instructions for upgrading to 1.10.x
|
||||
checkDT9 <- function(options) {
|
||||
nms <- names(options)
|
||||
if (length(nms) == 0L) return(options)
|
||||
DT10 <- DT10Names()
|
||||
# e.g. the top level option name for oLanguage.sSearch should be oLanguage
|
||||
i <- nms %in% gsub('[.].*', '', DT10[, 1])
|
||||
if (!any(i)) return(options) # did not see old option names, ready to go!
|
||||
msg <- paste(
|
||||
'shiny (>= 0.10.2) has upgraded DataTables from 1.9.4 to 1.10.2, ',
|
||||
'and DataTables 1.10.x uses different parameter names with 1.9.x. ',
|
||||
'Please follow the upgrade guide https://datatables.net/upgrade/1.10-convert',
|
||||
' to change your DataTables parameter names:\n\n',
|
||||
paste(formatUL(nms[i]), collapse = '\n'), '\n', sep = ''
|
||||
)
|
||||
j <- gsub('[.].*', '', DT10[, 1]) %in% nms
|
||||
# I cannot help you upgrade automatically in these cases, so I have to stop
|
||||
if (any(grepl('[.]', DT10[j, 1])) || any(grepl('[.]', DT10[j, 2]))) stop(msg)
|
||||
warning(msg)
|
||||
nms10 <- DT10[match(nms[i], DT10[, 1]), 2]
|
||||
if (any(nms10 == 'Removed')) stop(
|
||||
"These parameters have been removed in DataTables 1.10.x:\n\n",
|
||||
paste(formatUL(nms[i][nms10 == 'Removed']), collapse = '\n'),
|
||||
"\n\n", msg
|
||||
)
|
||||
names(options)[i] <- nms10
|
||||
options
|
||||
}
|
||||
|
||||
# Deprecated functions ------------------------------------------------------
|
||||
|
||||
#' Plot output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderPlot}}.
|
||||
#' @param func A function.
|
||||
#' @param width Width.
|
||||
#' @param height Height.
|
||||
#' @param ... Other arguments to pass on.
|
||||
#' @export
|
||||
reactivePlot <- function(func, width='auto', height='auto', ...) {
|
||||
shinyDeprecated(new="renderPlot")
|
||||
renderPlot({ func() }, width=width, height=height, ...)
|
||||
}
|
||||
|
||||
#' Table output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderTable}}.
|
||||
#' @param func A function.
|
||||
#' @param ... Other arguments to pass on.
|
||||
#' @export
|
||||
reactiveTable <- function(func, ...) {
|
||||
shinyDeprecated(new="renderTable")
|
||||
renderTable({ func() })
|
||||
}
|
||||
|
||||
#' Print output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderPrint}}.
|
||||
#' @param func A function.
|
||||
#' @export
|
||||
reactivePrint <- function(func) {
|
||||
shinyDeprecated(new="renderPrint")
|
||||
renderPrint({ func() })
|
||||
}
|
||||
|
||||
#' UI output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderUI}}.
|
||||
#' @param func A function.
|
||||
#' @export
|
||||
reactiveUI <- function(func) {
|
||||
shinyDeprecated(new="renderUI")
|
||||
renderUI({ func() })
|
||||
}
|
||||
|
||||
#' Text output (deprecated)
|
||||
#'
|
||||
#' See \code{\link{renderText}}.
|
||||
#' @param func A function.
|
||||
#' @export
|
||||
reactiveText <- function(func) {
|
||||
shinyDeprecated(new="renderText")
|
||||
renderText({ func() })
|
||||
}
|
||||
179
R/showcase.R
Normal file
179
R/showcase.R
Normal file
@@ -0,0 +1,179 @@
|
||||
#' @include globals.R
|
||||
NULL
|
||||
|
||||
# Given the name of a license, return the appropriate link HTML for the
|
||||
# license, which may just be the name of the license if the name is
|
||||
# unrecognized.
|
||||
#
|
||||
# Recognizes the 'standard' set of licenses used for R packages
|
||||
# (see http://cran.r-project.org/doc/manuals/R-exts.html)
|
||||
licenseLink <- function(licenseName) {
|
||||
licenses <- list(
|
||||
"GPL-2" = "https://gnu.org/licenses/gpl-2.0.txt",
|
||||
"GPL-3" = "https://gnu.org/licenses/gpl-3.0.txt",
|
||||
"LGPL-3" = "https://www.gnu.org/licenses/lgpl-3.0.txt",
|
||||
"LGPL-2" = "http://www.gnu.org/licenses/old-licenses/lgpl-2.0.txt",
|
||||
"LGPL-2.1" = "http://www.gnu.org/licenses/lgpl-2.1.txt",
|
||||
"AGPL-3" = "http://www.gnu.org/licenses/agpl-3.0.txt",
|
||||
"Artistic-2.0" = "http://www.r-project.org/Licenses/Artistic-2.0",
|
||||
"BSD_2_clause" = "http://www.r-project.org/Licenses/BSD_2_clause",
|
||||
"BSD_3_clause" = "http://www.r-project.org/Licenses/BSD_3_clause",
|
||||
"MIT" = "http://www.r-project.org/Licenses/MIT")
|
||||
if (exists(licenseName, where = licenses)) {
|
||||
tags$a(href=licenses[[licenseName]], licenseName)
|
||||
} else {
|
||||
licenseName
|
||||
}
|
||||
}
|
||||
|
||||
# Returns tags containing showcase directives intended for the <HEAD> of the
|
||||
# document.
|
||||
showcaseHead <- function() {
|
||||
|
||||
deps <- list(
|
||||
htmlDependency("jqueryui", "1.10.4", c(href="shared/jqueryui/1.10.4"),
|
||||
script = "jquery-ui.min.js"),
|
||||
htmlDependency("showdown", "0.3.1", c(href="shared/showdown/compressed"),
|
||||
script = "showdown.js"),
|
||||
htmlDependency("highlight.js", "6.2", c(href="shared/highlight"),
|
||||
script = "highlight.pack.js")
|
||||
)
|
||||
|
||||
mdfile <- file.path.ci(getwd(), 'Readme.md')
|
||||
html <- with(tags, tagList(
|
||||
script(src="shared/shiny-showcase.js"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/highlight/rstudio.css"),
|
||||
link(rel="stylesheet", type="text/css",
|
||||
href="shared/shiny-showcase.css"),
|
||||
if (file.exists(mdfile))
|
||||
script(type="text/markdown", id="showcase-markdown-content",
|
||||
paste(readUTF8(mdfile), collapse="\n"))
|
||||
else ""
|
||||
))
|
||||
|
||||
return(attachDependencies(html, deps))
|
||||
}
|
||||
|
||||
# Returns tags containing the application metadata (title and author) in
|
||||
# showcase mode.
|
||||
appMetadata <- function(desc) {
|
||||
cols <- colnames(desc)
|
||||
if ("Title" %in% cols)
|
||||
with(tags, h4(class="text-muted shiny-showcase-apptitle", desc[1,"Title"],
|
||||
if ("Author" %in% cols) small(
|
||||
br(), "by",
|
||||
if ("AuthorUrl" %in% cols)
|
||||
a(href=desc[1,"AuthorUrl"], class="shiny-showcase-appauthor",
|
||||
desc[1,"Author"])
|
||||
else
|
||||
desc[1,"Author"],
|
||||
if ("AuthorEmail" %in% cols)
|
||||
a(href=paste("mailto:", desc[1,"AuthorEmail"], sep = ''),
|
||||
class="shiny-showcase-appauthoreemail",
|
||||
desc[1,"AuthorEmail"])
|
||||
else "")
|
||||
else ""))
|
||||
else ""
|
||||
}
|
||||
|
||||
# Returns tags containing the application's code in Bootstrap-style tabs in
|
||||
# showcase mode.
|
||||
showcaseCodeTabs <- function(codeLicense) {
|
||||
rFiles <- list.files(pattern = "\\.[rR]$")
|
||||
with(tags, div(id="showcase-code-tabs",
|
||||
a(id="showcase-code-position-toggle",
|
||||
class="btn btn-default btn-sm",
|
||||
onclick="toggleCodePosition()",
|
||||
icon("level-up"),
|
||||
"show with app"),
|
||||
ul(class="nav nav-tabs",
|
||||
lapply(rFiles, function(rFile) {
|
||||
li(class=if (tolower(rFile) %in% c("app.r", "server.r")) "active" else "",
|
||||
a(href=paste("#", gsub(".", "_", rFile, fixed=TRUE),
|
||||
"_code", sep=""),
|
||||
"data-toggle"="tab", rFile))
|
||||
})),
|
||||
div(class="tab-content", id="showcase-code-content",
|
||||
lapply(rFiles, function(rFile) {
|
||||
div(class=paste("tab-pane",
|
||||
if (tolower(rFile) %in% c("app.r", "server.r")) " active"
|
||||
else "",
|
||||
sep=""),
|
||||
id=paste(gsub(".", "_", rFile, fixed=TRUE),
|
||||
"_code", sep=""),
|
||||
pre(class="shiny-code",
|
||||
# we need to prevent the indentation of <code> ... </code>
|
||||
HTML(format(tags$code(
|
||||
class="language-r",
|
||||
paste(readUTF8(file.path.ci(getwd(), rFile)), collapse="\n")
|
||||
), indent = FALSE))))
|
||||
})),
|
||||
codeLicense))
|
||||
}
|
||||
|
||||
# Returns tags containing the showcase application information (readme and
|
||||
# code).
|
||||
showcaseAppInfo <- function() {
|
||||
descfile <- file.path.ci(getwd(), "DESCRIPTION")
|
||||
hasDesc <- file.exists(descfile)
|
||||
readmemd <- file.path.ci(getwd(), "Readme.md")
|
||||
hasReadme <- file.exists(readmemd)
|
||||
if (hasDesc) {
|
||||
con <- textConnection(readUTF8(descfile))
|
||||
on.exit(close(con), add = TRUE)
|
||||
desc <- read.dcf(con)
|
||||
}
|
||||
with(tags,
|
||||
div(class="container-fluid shiny-code-container well",
|
||||
id="showcase-well",
|
||||
div(class="row",
|
||||
if (hasDesc || hasReadme) {
|
||||
div(id="showcase-app-metadata", class="col-sm-4",
|
||||
if (hasDesc) appMetadata(desc) else "",
|
||||
if (hasReadme) div(id="readme-md"))
|
||||
} else "",
|
||||
div(id="showcase-code-inline",
|
||||
class=if (hasReadme || hasDesc) "col-sm-8" else "col-sm-10 col-sm-offset-1",
|
||||
showcaseCodeTabs(
|
||||
if (hasDesc && "License" %in% colnames(desc)) {
|
||||
small(class="showcase-code-license text-muted",
|
||||
"Code license: ",
|
||||
licenseLink(desc[1,"License"]))
|
||||
} else "")))))
|
||||
}
|
||||
|
||||
|
||||
# Returns the body of the showcase document, given the HTML it should wrap.
|
||||
showcaseBody <- function(htmlBody) {
|
||||
with(tags, tagList(
|
||||
table(id="showcase-app-code",
|
||||
tr(td(id="showcase-app-container",
|
||||
class="showcase-app-container-expanded",
|
||||
htmlBody,
|
||||
td(id="showcase-sxs-code",
|
||||
class="showcase-sxs-code-collapsed")))),
|
||||
showcaseAppInfo()))
|
||||
}
|
||||
|
||||
# Sets the defaults for showcase mode (for app boot).
|
||||
setShowcaseDefault <- function(showcaseDefault) {
|
||||
.globals$showcaseDefault <- showcaseDefault
|
||||
.globals$showcaseOverride <- as.logical(showcaseDefault)
|
||||
}
|
||||
|
||||
|
||||
# Given a UI tag/tagList, wrap it in appropriate tags for showcase mode.
|
||||
showcaseUI <- function(ui) {
|
||||
# If top-level tag is a body, replace its children with children wrapped in
|
||||
# showcase stuff.
|
||||
if (inherits(ui, "shiny.tag") && ui$name == "body") {
|
||||
ui$children <- showcaseUI(ui$children)
|
||||
return(ui)
|
||||
}
|
||||
|
||||
tagList(
|
||||
tags$head(showcaseHead()),
|
||||
showcaseBody(ui)
|
||||
)
|
||||
}
|
||||
26
R/slider.R
Normal file
26
R/slider.R
Normal file
@@ -0,0 +1,26 @@
|
||||
hasDecimals <- function(value) {
|
||||
truncatedValue <- round(value)
|
||||
return (!identical(value, truncatedValue))
|
||||
}
|
||||
|
||||
#' @rdname 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)
|
||||
}
|
||||
70
R/stack.R
Normal file
70
R/stack.R
Normal file
@@ -0,0 +1,70 @@
|
||||
# A Stack object backed by a list. The backing list will grow or shrink as
|
||||
# the stack changes in size.
|
||||
Stack <- R6Class(
|
||||
'Stack',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
|
||||
initialize = function(init = 20L) {
|
||||
# init is the initial size of the list. It is also used as the minimum
|
||||
# size of the list as it shrinks.
|
||||
private$stack <- vector("list", init)
|
||||
private$init <- init
|
||||
},
|
||||
|
||||
push = function(..., .list = NULL) {
|
||||
args <- c(list(...), .list)
|
||||
new_size <- count + length(args)
|
||||
|
||||
# Grow if needed; double in size
|
||||
while (new_size > length(stack)) {
|
||||
stack[length(stack) * 2] <<- list(NULL)
|
||||
}
|
||||
stack[count + seq_along(args)] <<- args
|
||||
count <<- new_size
|
||||
|
||||
invisible(self)
|
||||
},
|
||||
|
||||
pop = function() {
|
||||
if (count == 0L)
|
||||
return(NULL)
|
||||
|
||||
value <- stack[[count]]
|
||||
stack[count] <<- list(NULL)
|
||||
count <<- count - 1L
|
||||
|
||||
# Shrink list if < 1/4 of the list is used, down to a minimum size of `init`
|
||||
len <- length(stack)
|
||||
if (len > init && count < len/4) {
|
||||
new_len <- max(init, ceiling(len/2))
|
||||
stack <<- stack[seq_len(new_len)]
|
||||
}
|
||||
|
||||
value
|
||||
},
|
||||
|
||||
peek = function() {
|
||||
if (count == 0L)
|
||||
return(NULL)
|
||||
stack[[count]]
|
||||
},
|
||||
|
||||
size = function() {
|
||||
count
|
||||
},
|
||||
|
||||
# Return the entire stack as a list, where the first item in the list is the
|
||||
# oldest item in the stack, and the last item is the most recently added.
|
||||
as_list = function() {
|
||||
stack[seq_len(count)]
|
||||
}
|
||||
),
|
||||
|
||||
private = list(
|
||||
stack = NULL, # A list that holds the items
|
||||
count = 0L, # Current number of items in the stack
|
||||
init = 20L # Initial and minimum size of the stack
|
||||
)
|
||||
)
|
||||
191
R/tar.R
Normal file
191
R/tar.R
Normal file
@@ -0,0 +1,191 @@
|
||||
# This file was pulled from the R code base as of
|
||||
# Thursday, November 22, 2012 at 6:24:55 AM UTC
|
||||
# and edited to remove everything but the copyright
|
||||
# header and untar2, and to make untar2 more tolerant
|
||||
# of the 'x' and 'g' extended block indicators, the
|
||||
# latter of which is used in tar files generated by
|
||||
# GitHub.
|
||||
|
||||
|
||||
# File src/library/utils/R/tar.R
|
||||
# Part of the R package, http://www.R-project.org
|
||||
#
|
||||
# Copyright (C) 1995-2012 The R Core Team
|
||||
#
|
||||
# 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 2 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.
|
||||
#
|
||||
# A copy of the GNU General Public License is available at
|
||||
# http://www.r-project.org/Licenses/
|
||||
|
||||
untar2 <- function(tarfile, files = NULL, list = FALSE, exdir = ".")
|
||||
{
|
||||
getOct <- function(x, offset, len)
|
||||
{
|
||||
x <- 0L
|
||||
for(i in offset + seq_len(len)) {
|
||||
z <- block[i]
|
||||
if(!as.integer(z)) break; # terminate on nul
|
||||
switch(rawToChar(z),
|
||||
" " = {},
|
||||
"0"=,"1"=,"2"=,"3"=,"4"=,"5"=,"6"=,"7"=
|
||||
{x <- 8*x + (as.integer(z)-48)},
|
||||
stop("invalid octal digit")
|
||||
)
|
||||
}
|
||||
x
|
||||
}
|
||||
|
||||
mydir.create <- function(path, ...) {
|
||||
## for Windows' sake
|
||||
path <- sub("[\\/]$", "", path)
|
||||
if(file_test("-d", path)) return()
|
||||
if(!dir.create(path, showWarnings = TRUE, recursive = TRUE, ...))
|
||||
stop(gettextf("failed to create directory %s", sQuote(path)),
|
||||
domain = NA)
|
||||
}
|
||||
|
||||
warn1 <- character()
|
||||
|
||||
## A tar file is a set of 512 byte records,
|
||||
## a header record followed by file contents (zero-padded).
|
||||
## See http://en.wikipedia.org/wiki/Tar_%28file_format%29
|
||||
if(is.character(tarfile) && length(tarfile) == 1L) {
|
||||
con <- gzfile(path.expand(tarfile), "rb") # reads compressed formats
|
||||
on.exit(close(con))
|
||||
} else if(inherits(tarfile, "connection")) con <- tarfile
|
||||
else stop("'tarfile' must be a character string or a connection")
|
||||
if (!missing(exdir)) {
|
||||
mydir.create(exdir)
|
||||
od <- setwd(exdir)
|
||||
on.exit(setwd(od), add = TRUE)
|
||||
}
|
||||
contents <- character()
|
||||
llink <- lname <- NULL
|
||||
repeat{
|
||||
block <- readBin(con, "raw", n = 512L)
|
||||
if(!length(block)) break
|
||||
if(length(block) < 512L) stop("incomplete block on file")
|
||||
if(all(block == 0)) break
|
||||
ns <- max(which(block[1:100] > 0))
|
||||
name <- rawToChar(block[seq_len(ns)])
|
||||
magic <- rawToChar(block[258:262])
|
||||
if ((magic == "ustar") && block[346] > 0) {
|
||||
ns <- max(which(block[346:500] > 0))
|
||||
prefix <- rawToChar(block[345+seq_len(ns)])
|
||||
name <- file.path(prefix, name)
|
||||
}
|
||||
## mode zero-padded 8 bytes (including nul) at 101
|
||||
## Aargh: bsdtar has this one incorrectly with 6 bytes+space
|
||||
mode <- as.octmode(getOct(block, 100, 8))
|
||||
size <- getOct(block, 124, 12)
|
||||
ts <- getOct(block, 136, 12)
|
||||
ft <- as.POSIXct(as.numeric(ts), origin="1970-01-01", tz="UTC")
|
||||
csum <- getOct(block, 148, 8)
|
||||
block[149:156] <- charToRaw(" ")
|
||||
xx <- as.integer(block)
|
||||
checksum <- sum(xx) %% 2^24 # 6 bytes
|
||||
if(csum != checksum) {
|
||||
## try it with signed bytes.
|
||||
checksum <- sum(ifelse(xx > 127, xx - 128, xx)) %% 2^24 # 6 bytes
|
||||
if(csum != checksum)
|
||||
warning(gettextf("checksum error for entry '%s'", name),
|
||||
domain = NA)
|
||||
}
|
||||
type <- block[157L]
|
||||
ctype <- rawToChar(type)
|
||||
if(type == 0L || ctype == "0") {
|
||||
if(!is.null(lname)) {name <- lname; lname <- NULL}
|
||||
contents <- c(contents, name)
|
||||
remain <- size
|
||||
dothis <- !list
|
||||
if(dothis && length(files)) dothis <- name %in% files
|
||||
if(dothis) {
|
||||
mydir.create(dirname(name))
|
||||
out <- file(name, "wb")
|
||||
}
|
||||
for(i in seq_len(ceiling(size/512L))) {
|
||||
block <- readBin(con, "raw", n = 512L)
|
||||
if(length(block) < 512L)
|
||||
stop("incomplete block on file")
|
||||
if (dothis) {
|
||||
writeBin(block[seq_len(min(512L, remain))], out)
|
||||
remain <- remain - 512L
|
||||
}
|
||||
}
|
||||
if(dothis) {
|
||||
close(out)
|
||||
Sys.chmod(name, mode, FALSE) # override umask
|
||||
Sys.setFileTime(name, ft)
|
||||
}
|
||||
} else if(ctype %in% c("1", "2")) { # hard and symbolic links
|
||||
contents <- c(contents, name)
|
||||
ns <- max(which(block[158:257] > 0))
|
||||
name2 <- rawToChar(block[157L + seq_len(ns)])
|
||||
if(!is.null(lname)) {name <- lname; lname <- NULL}
|
||||
if(!is.null(llink)) {name2 <- llink; llink <- NULL}
|
||||
if(!list) {
|
||||
if(ctype == "1") {
|
||||
if (!file.link(name2, name)) { # will give a warning
|
||||
## link failed, so try a file copy
|
||||
if(file.copy(name2, name))
|
||||
warn1 <- c(warn1, "restoring hard link as a file copy")
|
||||
else
|
||||
warning(gettextf("failed to copy %s to %s", sQuote(name2), sQuote(name)), domain = NA)
|
||||
}
|
||||
} else {
|
||||
if(isWindows()) {
|
||||
## this will not work for links to dirs
|
||||
from <- file.path(dirname(name), name2)
|
||||
if (!file.copy(from, name))
|
||||
warning(gettextf("failed to copy %s to %s", sQuote(from), sQuote(name)), domain = NA)
|
||||
else
|
||||
warn1 <- c(warn1, "restoring symbolic link as a file copy")
|
||||
} else {
|
||||
if(!file.symlink(name2, name)) { # will give a warning
|
||||
## so try a file copy: will not work for links to dirs
|
||||
from <- file.path(dirname(name), name2)
|
||||
if (file.copy(from, name))
|
||||
warn1 <- c(warn1, "restoring symbolic link as a file copy")
|
||||
else
|
||||
warning(gettextf("failed to copy %s to %s", sQuote(from), sQuote(name)), domain = NA)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(ctype == "5") {
|
||||
contents <- c(contents, name)
|
||||
if(!list) {
|
||||
mydir.create(name)
|
||||
Sys.chmod(name, mode, TRUE) # FIXME: check result
|
||||
## no point is setting time, as dir will be populated later.
|
||||
}
|
||||
} else if(ctype %in% c("L", "K")) {
|
||||
## This is a GNU extension that should no longer be
|
||||
## in use, but it is.
|
||||
name_size <- 512L * ceiling(size/512L)
|
||||
block <- readBin(con, "raw", n = name_size)
|
||||
if(length(block) < name_size)
|
||||
stop("incomplete block on file")
|
||||
ns <- max(which(block > 0)) # size on file may or may not include final nul
|
||||
if(ctype == "L")
|
||||
lname <- rawToChar(block[seq_len(ns)])
|
||||
else
|
||||
llink <- rawToChar(block[seq_len(ns)])
|
||||
} else if(ctype %in% c("x", "g")) {
|
||||
readBin(con, "raw", n = 512L*ceiling(size/512L))
|
||||
} else stop("unsupported entry type ", sQuote(ctype))
|
||||
}
|
||||
if(length(warn1)) {
|
||||
warn1 <- unique(warn1)
|
||||
for (w in warn1) warning(w, domain = NA)
|
||||
}
|
||||
if(list) contents else invisible(0L)
|
||||
}
|
||||
73
R/timer.R
Normal file
73
R/timer.R
Normal file
@@ -0,0 +1,73 @@
|
||||
# Return the current time, in milliseconds from epoch, with
|
||||
# unspecified time zone.
|
||||
now <- function() {
|
||||
as.numeric(Sys.time()) * 1000
|
||||
}
|
||||
|
||||
TimerCallbacks <- R6Class(
|
||||
'TimerCallbacks',
|
||||
portable = FALSE,
|
||||
class = FALSE,
|
||||
public = list(
|
||||
.nextId = 0L,
|
||||
.funcs = 'Map',
|
||||
.times = data.frame(),
|
||||
|
||||
initialize = function() {
|
||||
.funcs <<- Map$new()
|
||||
},
|
||||
clear = function() {
|
||||
.nextId <<- 0L
|
||||
.funcs$clear()
|
||||
.times <<- data.frame()
|
||||
},
|
||||
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(FALSE)
|
||||
|
||||
for (id in elapsed$id) {
|
||||
thisFunc <- .funcs$remove(as.character(id))
|
||||
# TODO: Catch exception, and...?
|
||||
# TODO: Detect NULL, and...?
|
||||
thisFunc()
|
||||
}
|
||||
return(TRUE)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
timerCallbacks <- TimerCallbacks$new()
|
||||
486
R/update-input.R
Normal file
486
R/update-input.R
Normal file
@@ -0,0 +1,486 @@
|
||||
#' Change the value of a text input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#'
|
||||
#' @seealso \code{\link{textInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' # This will change the value of input$inText, based on x
|
||||
#' updateTextInput(session, "inText", value = paste("New text", x))
|
||||
#'
|
||||
#' # Can also set the label, this time for input$inText2
|
||||
#' updateTextInput(session, "inText2",
|
||||
#' label = paste("New label", x),
|
||||
#' value = paste("New text", x))
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateTextInput <- function(session, inputId, label = NULL, value = NULL) {
|
||||
message <- dropNulls(list(label=label, value=value))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a checkbox input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#'
|
||||
#' @seealso \code{\link{checkboxInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # TRUE if input$controller is even, FALSE otherwise.
|
||||
#' x_even <- input$controller %% 2 == 0
|
||||
#'
|
||||
#' updateCheckboxInput(session, "inCheckbox", value = x_even)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxInput <- updateTextInput
|
||||
|
||||
|
||||
#' Change the value of a date input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The desired date value. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @param min The minimum allowed date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @param max The maximum allowed date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#'
|
||||
#' @seealso \code{\link{dateInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' updateDateInput(session, "inDate",
|
||||
#' label = paste("Date label", x),
|
||||
#' value = paste("2013-04-", x, sep=""),
|
||||
#' min = paste("2013-04-", x-1, sep=""),
|
||||
#' max = paste("2013-04-", x+1, sep="")
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateDateInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL) {
|
||||
|
||||
# If value is a date object, convert it to a string with yyyy-mm-dd format
|
||||
# Same for min and max
|
||||
if (inherits(value, "Date")) value <- format(value, "%Y-%m-%d")
|
||||
if (inherits(min, "Date")) min <- format(min, "%Y-%m-%d")
|
||||
if (inherits(max, "Date")) max <- format(max, "%Y-%m-%d")
|
||||
|
||||
message <- dropNulls(list(label=label, value=value, min=min, max=max))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' Change the start and end values of a date range input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param start The start date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @param end The end date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @param min The minimum allowed date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#' @param max The maximum allowed date. Either a Date object, or a string in
|
||||
#' \code{yyyy-mm-dd} format.
|
||||
#'
|
||||
#' @seealso \code{\link{dateRangeInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' updateDateRangeInput(session, "inDateRange",
|
||||
#' label = paste("Date range label", x),
|
||||
#' start = paste("2013-01-", x, sep=""))
|
||||
#' end = paste("2013-12-", x, sep=""))
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateDateRangeInput <- function(session, inputId, label = NULL,
|
||||
start = NULL, end = NULL, min = NULL,
|
||||
max = NULL) {
|
||||
# Make sure start and end are strings, not date objects. This is for
|
||||
# consistency across different locales.
|
||||
if (inherits(start, "Date")) start <- format(start, '%Y-%m-%d')
|
||||
if (inherits(end, "Date")) end <- format(end, '%Y-%m-%d')
|
||||
if (inherits(min, "Date")) min <- format(min, '%Y-%m-%d')
|
||||
if (inherits(max, "Date")) max <- format(max, '%Y-%m-%d')
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = label,
|
||||
value = c(start, end),
|
||||
min = min,
|
||||
max = max
|
||||
))
|
||||
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' Change the selected tab on the client
|
||||
#'
|
||||
#' @param session The \code{session} object passed to function given to
|
||||
#' \code{shinyServer}.
|
||||
#' @param inputId The id of the \code{tabsetPanel}, \code{navlistPanel},
|
||||
#' or \code{navbarPage} object.
|
||||
#' @param selected The name of the tab to make active.
|
||||
#'
|
||||
#' @seealso \code{\link{tabsetPanel}}, \code{\link{navlistPanel}},
|
||||
#' \code{\link{navbarPage}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # TRUE if input$controller is even, FALSE otherwise.
|
||||
#' x_even <- input$controller %% 2 == 0
|
||||
#'
|
||||
#' # Change the selected tab.
|
||||
#' # Note that the tabset container must have been created with an 'id' argument
|
||||
#' if (x_even) {
|
||||
#' updateTabsetPanel(session, "inTabset", selected = "panel2")
|
||||
#' } else {
|
||||
#' updateTabsetPanel(session, "inTabset", selected = "panel1")
|
||||
#' }
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateTabsetPanel <- function(session, inputId, selected = NULL) {
|
||||
message <- dropNulls(list(value = selected))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a number input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#' @param min Minimum value.
|
||||
#' @param max Maximum value.
|
||||
#' @param step Step size.
|
||||
#'
|
||||
#' @seealso \code{\link{numericInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' updateNumericInput(session, "inNumber", value = x)
|
||||
#'
|
||||
#' updateNumericInput(session, "inNumber2",
|
||||
#' label = paste("Number label ", x),
|
||||
#' value = x, min = x-10, max = x+10, step = 5)
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateNumericInput <- function(session, inputId, label = NULL, value = NULL,
|
||||
min = NULL, max = NULL, step = NULL) {
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = label, value = formatNoSci(value),
|
||||
min = formatNoSci(min), max = formatNoSci(max), step = formatNoSci(step)
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' Change the value of a slider input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @param value The value to set for the input object.
|
||||
#' @param min Minimum value.
|
||||
#' @param max Maximum value.
|
||||
#' @param step Step size.
|
||||
#'
|
||||
#' @seealso \code{\link{sliderInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' ## Only run this example in interactive R sessions
|
||||
#' if (interactive()) {
|
||||
#' shinyApp(
|
||||
#' ui = fluidPage(
|
||||
#' sidebarLayout(
|
||||
#' sidebarPanel(
|
||||
#' p("The first slider controls the second"),
|
||||
#' slider2Input("control", "Controller:", min=0, max=20, value=10,
|
||||
#' step=1),
|
||||
#' slider2Input("receive", "Receiver:", min=0, max=20, value=10,
|
||||
#' step=1)
|
||||
#' ),
|
||||
#' mainPanel()
|
||||
#' )
|
||||
#' ),
|
||||
#' server = function(input, output, session) {
|
||||
#' observe({
|
||||
#' val <- input$control
|
||||
#' # Control the value, min, max, and step.
|
||||
#' # Step size is 2 when input value is even; 1 when value is odd.
|
||||
#' updateSliderInput(session, "receive", value = val,
|
||||
#' min = floor(val/2), max = val+4, step = (val+1)%%2 + 1)
|
||||
#' })
|
||||
#' }
|
||||
#' )
|
||||
#' }
|
||||
#' @export
|
||||
updateSliderInput <- updateNumericInput
|
||||
|
||||
updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, inline = FALSE,
|
||||
type = 'checkbox') {
|
||||
|
||||
choices <- choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
|
||||
options <- if (length(choices))
|
||||
format(tagList(
|
||||
generateOptions(inputId, choices, selected, inline, type = type)
|
||||
))
|
||||
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' Change the value of a checkbox group input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @inheritParams checkboxGroupInput
|
||||
#'
|
||||
#' @seealso \code{\link{checkboxGroupInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' # Create a list of new options, where the name of the items is something
|
||||
#' # like 'option label x 1', and the values are 'option-x-1'.
|
||||
#' cb_options <- list()
|
||||
#' cb_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
|
||||
#' cb_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
|
||||
#'
|
||||
#' # Change values for input$inCheckboxGroup
|
||||
#' updateCheckboxGroupInput(session, "inCheckboxGroup", choices = cb_options)
|
||||
#'
|
||||
#' # Can also set the label and select items
|
||||
#' updateCheckboxGroupInput(session, "inCheckboxGroup2",
|
||||
#' label = paste("checkboxgroup label", x),
|
||||
#' choices = cb_options,
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateCheckboxGroupInput <- function(session, inputId, label = NULL,
|
||||
choices = NULL, selected = NULL,
|
||||
inline = FALSE) {
|
||||
updateInputOptions(session, inputId, label, choices, selected, inline)
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a radio input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @inheritParams radioButtons
|
||||
#'
|
||||
#' @seealso \code{\link{radioButtons}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' r_options <- list()
|
||||
#' r_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
|
||||
#' r_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
|
||||
#'
|
||||
#' # Change values for input$inRadio
|
||||
#' updateRadioButtons(session, "inRadio", choices = r_options)
|
||||
#'
|
||||
#' # Can also set the label and select an item
|
||||
#' updateRadioButtons(session, "inRadio2",
|
||||
#' label = paste("Radio label", x),
|
||||
#' choices = r_options,
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateRadioButtons <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, inline = FALSE) {
|
||||
# you must select at least one radio button
|
||||
if (is.null(selected) && !is.null(choices)) selected <- choices[[1]]
|
||||
updateInputOptions(session, inputId, label, choices, selected, inline, type = 'radio')
|
||||
}
|
||||
|
||||
|
||||
#' Change the value of a select input on the client
|
||||
#'
|
||||
#' @template update-input
|
||||
#' @inheritParams selectInput
|
||||
#'
|
||||
#' @seealso \code{\link{selectInput}}
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' shinyServer(function(input, output, session) {
|
||||
#'
|
||||
#' observe({
|
||||
#' # We'll use the input$controller variable multiple times, so save it as x
|
||||
#' # for convenience.
|
||||
#' x <- input$controller
|
||||
#'
|
||||
#' # Create a list of new options, where the name of the items is something
|
||||
#' # like 'option label x 1', and the values are 'option-x-1'.
|
||||
#' s_options <- list()
|
||||
#' s_options[[sprintf("option label %d 1", x)]] <- sprintf("option-%d-1", x)
|
||||
#' s_options[[sprintf("option label %d 2", x)]] <- sprintf("option-%d-2", x)
|
||||
#'
|
||||
#' # Change values for input$inSelect
|
||||
#' updateSelectInput(session, "inSelect", choices = s_options)
|
||||
#'
|
||||
#' # Can also set the label and select an item (or more than one if it's a
|
||||
#' # multi-select)
|
||||
#' updateSelectInput(session, "inSelect2",
|
||||
#' label = paste("Select label", x),
|
||||
#' choices = s_options,
|
||||
#' selected = sprintf("option-%d-2", x)
|
||||
#' )
|
||||
#' })
|
||||
#' })
|
||||
#' }
|
||||
#' @export
|
||||
updateSelectInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL) {
|
||||
choices <- if (!is.null(choices)) choicesWithNames(choices)
|
||||
if (!is.null(selected))
|
||||
selected <- validateSelected(selected, choices, inputId)
|
||||
options <- if (!is.null(choices)) selectOptions(choices, selected)
|
||||
message <- dropNulls(list(label = label, options = options, value = selected))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
#' @rdname updateSelectInput
|
||||
#' @inheritParams selectizeInput
|
||||
#' @param server whether to store \code{choices} on the server side, and load
|
||||
#' the select options dynamically on searching, instead of writing all
|
||||
#' \code{choices} into the page at once (i.e., only use the client-side
|
||||
#' version of \pkg{selectize.js})
|
||||
#' @export
|
||||
updateSelectizeInput <- function(session, inputId, label = NULL, choices = NULL,
|
||||
selected = NULL, options = list(),
|
||||
server = FALSE) {
|
||||
if (length(options)) {
|
||||
res <- checkAsIs(options)
|
||||
cfg <- tags$script(
|
||||
type = 'application/json',
|
||||
`data-for` = inputId,
|
||||
`data-eval` = if (length(res$eval)) HTML(toJSON(res$eval)),
|
||||
HTML(toJSON(res$options))
|
||||
)
|
||||
session$sendInputMessage(inputId, list(config = as.character(cfg)))
|
||||
}
|
||||
if (!server) {
|
||||
return(updateSelectInput(session, inputId, label, choices, selected))
|
||||
}
|
||||
value <- unname(selected)
|
||||
selected <- choicesWithNames(selected)
|
||||
message <- dropNulls(list(
|
||||
label = label,
|
||||
value = value,
|
||||
selected = if (length(selected)) {
|
||||
columnToRowData(list(label = names(selected), value = selected))
|
||||
},
|
||||
url = session$registerDataObj(inputId, choices, selectizeJSON)
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
}
|
||||
|
||||
selectizeJSON <- function(data, req) {
|
||||
query <- parseQueryString(req$QUERY_STRING)
|
||||
# extract the query variables, conjunction (and/or), search string, maximum options
|
||||
var <- unlist(jsonlite::fromJSON(query$field))
|
||||
cjn <- if (query$conju == 'and') all else any
|
||||
# all keywords in lower-case, for case-insensitive matching
|
||||
key <- unique(strsplit(tolower(query$query), '\\s+')[[1]])
|
||||
if (identical(key, '')) key <- character(0)
|
||||
mop <- query$maxop
|
||||
|
||||
# convert a single vector to a data frame so it returns {label: , value: }
|
||||
# later in JSON; other objects return arbitrary JSON {x: , y: , foo: , ...}
|
||||
data <- if (is.atomic(data)) {
|
||||
data.frame(label = names(choicesWithNames(data)), value = data,
|
||||
stringsAsFactors = FALSE)
|
||||
} else as.data.frame(data, stringsAsFactors = FALSE)
|
||||
|
||||
# start searching for keywords in all specified columns
|
||||
idx <- logical(nrow(data))
|
||||
if (length(key)) for (v in var) {
|
||||
matches <- do.call(
|
||||
cbind,
|
||||
lapply(key, function(k) {
|
||||
grepl(k, tolower(as.character(data[[v]])), fixed = TRUE)
|
||||
})
|
||||
)
|
||||
# merge column matches using OR, and match multiple keywords in one column
|
||||
# using the conjunction setting (AND or OR)
|
||||
idx <- idx | apply(matches, 1, cjn)
|
||||
}
|
||||
# only return the first n rows (n = maximum options in configuration)
|
||||
idx <- head(if (length(key)) which(idx) else seq_along(idx), mop)
|
||||
data <- data[idx, ]
|
||||
|
||||
res <- toJSON(columnToRowData(data))
|
||||
httpResponse(200, 'application/json', enc2utf8(res))
|
||||
}
|
||||
71
README.md
71
README.md
@@ -1,10 +1,65 @@
|
||||
# Shiny
|
||||
### A web framework for R (eventually--Ruby for now)
|
||||
|
||||
```sh
|
||||
sudo apt-get install ruby1.9.1 ruby1.9.1-dev
|
||||
sudo gem install bundler
|
||||
cd shiny
|
||||
bundle install --path vendor
|
||||
./run.sh
|
||||
```
|
||||
[](https://travis-ci.org/rstudio/shiny)
|
||||
|
||||
Shiny is a new package from RStudio that makes it incredibly easy to build interactive web applications with R.
|
||||
|
||||
For an introduction and examples, visit the [Shiny Dev Center](http://shiny.rstudio.com/).
|
||||
|
||||
## Features
|
||||
|
||||
* Build useful web applications with only a few lines of code—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 [Bootstrap](http://getbootstrap.com/2.3.2/).
|
||||
* 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 [httpuv](https://github.com/rstudio/httpuv) package.
|
||||
* Uses a [reactive](http://en.wikipedia.org/wiki/Reactive_programming) programming model that eliminates messy event handling code, so you can focus on the code that really matters.
|
||||
* Develop and redistribute your own Shiny widgets that other developers can easily drop into their own applications (coming soon!).
|
||||
|
||||
## Installation
|
||||
|
||||
To install the stable version from CRAN, simply run the following from an R console:
|
||||
|
||||
```r
|
||||
install.packages("shiny")
|
||||
```
|
||||
|
||||
To install the latest development builds directly from GitHub, run this instead:
|
||||
|
||||
```r
|
||||
if (!require("devtools"))
|
||||
install.packages("devtools")
|
||||
devtools::install_github("rstudio/shiny")
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
To learn more we highly recommend you check out the [Shiny Tutorial](http://shiny.rstudio.com/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. If you have general questions about using Shiny, please use the Shiny [mailing list](https://groups.google.com/forum/#!forum/shiny-discuss). For bug reports, please use the [issue tracker](https://github.com/rstudio/shiny/issues).
|
||||
|
||||
## Bootstrap 3 migration
|
||||
|
||||
Shiny versions 0.10.2.2 and below used the Bootstrap 2 web framework. After 0.10.2.2, Shiny switched to Bootstrap 3. For most users, the upgrade should be seamless. However, if you have have customized your HTML-generating code to use features specific to Bootstrap 2, you may need to update your code to work with Bootstrap 3.
|
||||
|
||||
If you do not wish to update your code at this time, you can use the [shinybootstrap2](https://github.com/rstudio/shinybootstrap2) package for backward compatibility.
|
||||
|
||||
If you prefer to install an older version of Shiny, you can do it using the devtools package:
|
||||
|
||||
```R
|
||||
devtools::install_version("shiny", version = "0.10.2.2")
|
||||
```
|
||||
|
||||
## Development notes
|
||||
|
||||
The Javascript code in Shiny is minified using tools that run on Node.js. See the tools/ directory for more information.
|
||||
|
||||
## 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
|
||||
|
||||
0
cran-comments.md
Normal file
0
cran-comments.md
Normal 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)
|
||||
})
|
||||
@@ -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>
|
||||
@@ -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
19
hash.rb
@@ -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
|
||||
7
inst/examples/01_hello/DESCRIPTION
Normal file
7
inst/examples/01_hello/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Hello Shiny!
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/01_hello/Readme.md
Normal file
4
inst/examples/01_hello/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
This small Shiny application demonstrates Shiny's automatic UI updates. Move
|
||||
the *Number of bins* slider and notice how the `renderPlot` expression is
|
||||
automatically re-evaluated when its dependant, `input$bins`, changes,
|
||||
causing a histogram with a new number of bins to be rendered.
|
||||
21
inst/examples/01_hello/server.R
Normal file
21
inst/examples/01_hello/server.R
Normal file
@@ -0,0 +1,21 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic required to draw a histogram
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Expression that generates a histogram. The expression is
|
||||
# wrapped in a call to renderPlot 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 <- renderPlot({
|
||||
x <- faithful[, 2] # Old Faithful Geyser data
|
||||
bins <- seq(min(x), max(x), length.out = input$bins + 1)
|
||||
|
||||
# draw the histogram with the specified number of bins
|
||||
hist(x, breaks = bins, col = 'darkgray', border = 'white')
|
||||
})
|
||||
|
||||
})
|
||||
24
inst/examples/01_hello/ui.R
Normal file
24
inst/examples/01_hello/ui.R
Normal file
@@ -0,0 +1,24 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for application that draws a histogram
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Hello Shiny!"),
|
||||
|
||||
# Sidebar with a slider input for the number of bins
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
sliderInput("bins",
|
||||
"Number of bins:",
|
||||
min = 1,
|
||||
max = 50,
|
||||
value = 30)
|
||||
),
|
||||
|
||||
# Show a plot of the generated distribution
|
||||
mainPanel(
|
||||
plotOutput("distPlot")
|
||||
)
|
||||
)
|
||||
))
|
||||
8
inst/examples/02_text/DESCRIPTION
Normal file
8
inst/examples/02_text/DESCRIPTION
Normal file
@@ -0,0 +1,8 @@
|
||||
Title: Shiny Text
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
|
||||
1
inst/examples/02_text/Readme.md
Normal file
1
inst/examples/02_text/Readme.md
Normal file
@@ -0,0 +1 @@
|
||||
This example demonstrates output of raw text from R using the `renderPrint` function in `server.R` and the `verbatimTextOutput` function in `ui.R`. In this case, a textual summary of the data is shown using R's built-in `summary` function.
|
||||
26
inst/examples/02_text/server.R
Normal file
26
inst/examples/02_text/server.R
Normal file
@@ -0,0 +1,26 @@
|
||||
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({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# Generate a summary of the dataset
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
27
inst/examples/02_text/ui.R
Normal file
27
inst/examples/02_text/ui.R
Normal file
@@ -0,0 +1,27 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Shiny Text"),
|
||||
|
||||
# Sidebar with controls to select a dataset and specify the
|
||||
# number of observations to view
|
||||
sidebarLayout(
|
||||
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")
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/03_reactivity/DESCRIPTION
Normal file
7
inst/examples/03_reactivity/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Reactivity
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
5
inst/examples/03_reactivity/Readme.md
Normal file
5
inst/examples/03_reactivity/Readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
This example demonstrates a core feature of Shiny: **reactivity**. In `server.R`, a reactive called `datasetInput` is declared.
|
||||
|
||||
Notice that the reactive expression depends on the input expression `input$dataset`, and that it's used by both the output expression `output$summary` and `output$view`. Try changing the dataset (using *Choose a dataset*) while looking at the reactive and then at the outputs; you will see first the reactive and then its dependencies flash.
|
||||
|
||||
Notice also that the reactive expression doesn't just update whenever anything changes--only the inputs it depends on will trigger an update. Change the "Caption" field and notice how only the `output$caption` expression is re-evaluated; the reactive and its dependents are left alone.
|
||||
53
inst/examples/03_reactivity/server.R
Normal file
53
inst/examples/03_reactivity/server.R
Normal file
@@ -0,0 +1,53 @@
|
||||
library(shiny)
|
||||
library(datasets)
|
||||
|
||||
# Define server logic required to summarize and view the selected
|
||||
# dataset
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# By declaring datasetInput as a reactive expression 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)
|
||||
#
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# The output$caption is computed based on a reactive expression
|
||||
# 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 expressions
|
||||
# below don't depend on input$caption, those expressions are
|
||||
# NOT called when input$caption changes.
|
||||
output$caption <- renderText({
|
||||
input$caption
|
||||
})
|
||||
|
||||
# The output$summary depends on the datasetInput reactive
|
||||
# expression, so will be re-executed whenever datasetInput is
|
||||
# invalidated
|
||||
# (i.e. whenever the input$dataset changes)
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# The output$view depends on both the databaseInput reactive
|
||||
# expression and input$obs, so will be re-executed whenever
|
||||
# input$dataset or input$obs is changed.
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
34
inst/examples/03_reactivity/ui.R
Normal file
34
inst/examples/03_reactivity/ui.R
Normal file
@@ -0,0 +1,34 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("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
|
||||
sidebarLayout(
|
||||
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", container = span)),
|
||||
|
||||
verbatimTextOutput("summary"),
|
||||
|
||||
tableOutput("view")
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/04_mpg/DESCRIPTION
Normal file
7
inst/examples/04_mpg/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Miles Per Gallon
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/04_mpg/Readme.md
Normal file
4
inst/examples/04_mpg/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
This example demonstrates the following concepts:
|
||||
|
||||
* **Global variables**: The `mpgData` variable is declared outside the `shinyServer` function. This makes it available anywhere inside `shinyServer`. The code in `server.R` outside `shinyServer` is only run once when the app starts up, so it can't contain user input.
|
||||
* **Reactive expressions**: `formulaText` is a reactive expression. Note how it re-evaluates when the Variable field is changed, but not when the Show Outliers box is ticked.
|
||||
34
inst/examples/04_mpg/server.R
Normal file
34
inst/examples/04_mpg/server.R
Normal file
@@ -0,0 +1,34 @@
|
||||
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 formula text in a reactive expression since it is
|
||||
# shared by the output$caption and output$mpgPlot functions
|
||||
formulaText <- reactive({
|
||||
paste("mpg ~", input$variable)
|
||||
})
|
||||
|
||||
# Return the formula text for printing as a caption
|
||||
output$caption <- renderText({
|
||||
formulaText()
|
||||
})
|
||||
|
||||
# Generate a plot of the requested variable against mpg and
|
||||
# only include outliers if requested
|
||||
output$mpgPlot <- renderPlot({
|
||||
boxplot(as.formula(formulaText()),
|
||||
data = mpgData,
|
||||
outline = input$outliers)
|
||||
})
|
||||
})
|
||||
29
inst/examples/04_mpg/ui.R
Normal file
29
inst/examples/04_mpg/ui.R
Normal file
@@ -0,0 +1,29 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for miles per gallon application
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Miles Per Gallon"),
|
||||
|
||||
# Sidebar with controls to select the variable to plot against
|
||||
# mpg and to specify whether outliers should be included
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("variable", "Variable:",
|
||||
c("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")
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/05_sliders/DESCRIPTION
Normal file
7
inst/examples/05_sliders/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Sliders
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
3
inst/examples/05_sliders/Readme.md
Normal file
3
inst/examples/05_sliders/Readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
This example demonstrates Shiny's versatile `sliderInput` widget.
|
||||
|
||||
Slider inputs can be used to select single values, to select a continuous range of values, and even to animate over a range.
|
||||
29
inst/examples/05_sliders/server.R
Normal file
29
inst/examples/05_sliders/server.R
Normal file
@@ -0,0 +1,29 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for slider examples
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Reactive expression to compose a data frame containing all of
|
||||
# the values
|
||||
sliderValues <- reactive({
|
||||
|
||||
# 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 <- renderTable({
|
||||
sliderValues()
|
||||
})
|
||||
})
|
||||
43
inst/examples/05_sliders/ui.R
Normal file
43
inst/examples/05_sliders/ui.R
Normal file
@@ -0,0 +1,43 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for slider demo application
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("Sliders"),
|
||||
|
||||
# Sidebar with sliders that demonstrate various available
|
||||
# options
|
||||
sidebarLayout(
|
||||
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,
|
||||
pre = "$", sep = ",", 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=TRUE))
|
||||
),
|
||||
|
||||
# Show a table summarizing the values entered
|
||||
mainPanel(
|
||||
tableOutput("values")
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/06_tabsets/DESCRIPTION
Normal file
7
inst/examples/06_tabsets/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Tabsets
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
9
inst/examples/06_tabsets/Readme.md
Normal file
9
inst/examples/06_tabsets/Readme.md
Normal file
@@ -0,0 +1,9 @@
|
||||
This example demonstrates the `tabsetPanel` and `tabPanel` widgets.
|
||||
|
||||
Notice that outputs that are not visible are not re-evaluated until they become visible. Try this:
|
||||
|
||||
1. Scroll to the bottom of `server.R`
|
||||
2. Change the number of observations, and observe that only `output$plot` is evaluated.
|
||||
3. Click the Summary tab, and observe that `output$summary` is evaluated.
|
||||
4. Change the number of observations again, and observe that now only `output$summary` is evaluated.
|
||||
|
||||
44
inst/examples/06_tabsets/server.R
Normal file
44
inst/examples/06_tabsets/server.R
Normal file
@@ -0,0 +1,44 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for random distribution application
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Reactive expression 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 expression
|
||||
data <- reactive({
|
||||
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 expression are both tracked, and
|
||||
# all expressions are called in the sequence implied by the
|
||||
# dependency graph
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
hist(data(),
|
||||
main=paste('r', dist, '(', n, ')', sep=''))
|
||||
})
|
||||
|
||||
# Generate a summary of the data
|
||||
output$summary <- renderPrint({
|
||||
summary(data())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the data
|
||||
output$table <- renderTable({
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
})
|
||||
38
inst/examples/06_tabsets/ui.R
Normal file
38
inst/examples/06_tabsets/ui.R
Normal file
@@ -0,0 +1,38 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for random distribution application
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title
|
||||
titlePanel("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
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
radioButtons("dist", "Distribution type:",
|
||||
c("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(type = "tabs",
|
||||
tabPanel("Plot", plotOutput("plot")),
|
||||
tabPanel("Summary", verbatimTextOutput("summary")),
|
||||
tabPanel("Table", tableOutput("table"))
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/07_widgets/DESCRIPTION
Normal file
7
inst/examples/07_widgets/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Widgets
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
1
inst/examples/07_widgets/Readme.md
Normal file
1
inst/examples/07_widgets/Readme.md
Normal file
@@ -0,0 +1 @@
|
||||
This example demonstrates some additional widgets included in Shiny, such as `helpText` and `submitButton`. The latter is used to delay rendering output until the user explicitly requests it.
|
||||
26
inst/examples/07_widgets/server.R
Normal file
26
inst/examples/07_widgets/server.R
Normal file
@@ -0,0 +1,26 @@
|
||||
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({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
# Generate a summary of the dataset
|
||||
output$summary <- renderPrint({
|
||||
dataset <- datasetInput()
|
||||
summary(dataset)
|
||||
})
|
||||
|
||||
# Show the first "n" observations
|
||||
output$view <- renderTable({
|
||||
head(datasetInput(), n = input$obs)
|
||||
})
|
||||
})
|
||||
43
inst/examples/07_widgets/ui.R
Normal file
43
inst/examples/07_widgets/ui.R
Normal file
@@ -0,0 +1,43 @@
|
||||
library(shiny)
|
||||
|
||||
# Define UI for dataset viewer application
|
||||
shinyUI(fluidPage(
|
||||
|
||||
# Application title.
|
||||
titlePanel("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.
|
||||
sidebarLayout(
|
||||
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")
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/08_html/DESCRIPTION
Normal file
7
inst/examples/08_html/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Custom HTML UI
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/08_html/Readme.md
Normal file
4
inst/examples/08_html/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Normally we use the built-in functions, such as `textInput()`, to generate
|
||||
the HTML UI in the R script `ui.R`. Actually **shiny** also works with a
|
||||
custom HTML page `www/index.html`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#html-ui) for more details.
|
||||
42
inst/examples/08_html/server.R
Normal file
42
inst/examples/08_html/server.R
Normal file
@@ -0,0 +1,42 @@
|
||||
library(shiny)
|
||||
|
||||
# Define server logic for random distribution application
|
||||
shinyServer(function(input, output) {
|
||||
|
||||
# Reactive expression to generate the requested distribution. This is
|
||||
# called whenever the inputs change. The output expressions defined
|
||||
# below then all used the value computed from this expression
|
||||
data <- reactive({
|
||||
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 expression are both tracked, and all expressions
|
||||
# are called in the sequence implied by the dependency graph
|
||||
output$plot <- renderPlot({
|
||||
dist <- input$dist
|
||||
n <- input$n
|
||||
|
||||
hist(data(),
|
||||
main=paste('r', dist, '(', n, ')', sep=''))
|
||||
})
|
||||
|
||||
# Generate a summary of the data
|
||||
output$summary <- renderPrint({
|
||||
summary(data())
|
||||
})
|
||||
|
||||
# Generate an HTML table view of the data
|
||||
output$table <- renderTable({
|
||||
data.frame(x=data())
|
||||
})
|
||||
|
||||
})
|
||||
@@ -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>
|
||||
7
inst/examples/09_upload/DESCRIPTION
Normal file
7
inst/examples/09_upload/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: File Upload
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/09_upload/Readme.md
Normal file
4
inst/examples/09_upload/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
We can add a file upload input in the UI using the function `fileInput()`,
|
||||
e.g. `fileInput('foo')`. In `server.R`, we can access the uploaded files via
|
||||
`input$foo`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#uploads) for more details.
|
||||
20
inst/examples/09_upload/server.R
Normal file
20
inst/examples/09_upload/server.R
Normal file
@@ -0,0 +1,20 @@
|
||||
library(shiny)
|
||||
|
||||
shinyServer(function(input, output) {
|
||||
output$contents <- renderTable({
|
||||
|
||||
# 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 'datapath' columns. The 'datapath'
|
||||
# column will contain the local filenames where the data can
|
||||
# be found.
|
||||
|
||||
inFile <- input$file1
|
||||
|
||||
if (is.null(inFile))
|
||||
return(NULL)
|
||||
|
||||
read.csv(inFile$datapath, header=input$header, sep=input$sep,
|
||||
quote=input$quote)
|
||||
})
|
||||
})
|
||||
28
inst/examples/09_upload/ui.R
Normal file
28
inst/examples/09_upload/ui.R
Normal file
@@ -0,0 +1,28 @@
|
||||
library(shiny)
|
||||
|
||||
shinyUI(fluidPage(
|
||||
titlePanel("Uploading Files"),
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
fileInput('file1', 'Choose CSV File',
|
||||
accept=c('text/csv',
|
||||
'text/comma-separated-values,text/plain',
|
||||
'.csv')),
|
||||
tags$hr(),
|
||||
checkboxInput('header', 'Header', TRUE),
|
||||
radioButtons('sep', 'Separator',
|
||||
c(Comma=',',
|
||||
Semicolon=';',
|
||||
Tab='\t'),
|
||||
','),
|
||||
radioButtons('quote', 'Quote',
|
||||
c(None='',
|
||||
'Double Quote'='"',
|
||||
'Single Quote'="'"),
|
||||
'"')
|
||||
),
|
||||
mainPanel(
|
||||
tableOutput('contents')
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/10_download/DESCRIPTION
Normal file
7
inst/examples/10_download/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: File Download
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/10_download/Readme.md
Normal file
4
inst/examples/10_download/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
We can add a download button to the UI using `downloadButton()`, and write
|
||||
the content of the file in `downloadHandler()` in `server.R`. See [the
|
||||
tutorial](http://rstudio.github.io/shiny/tutorial/#downloads) for more
|
||||
details.
|
||||
21
inst/examples/10_download/server.R
Normal file
21
inst/examples/10_download/server.R
Normal file
@@ -0,0 +1,21 @@
|
||||
shinyServer(function(input, output) {
|
||||
datasetInput <- reactive({
|
||||
switch(input$dataset,
|
||||
"rock" = rock,
|
||||
"pressure" = pressure,
|
||||
"cars" = cars)
|
||||
})
|
||||
|
||||
output$table <- renderTable({
|
||||
datasetInput()
|
||||
})
|
||||
|
||||
output$downloadData <- downloadHandler(
|
||||
filename = function() {
|
||||
paste(input$dataset, '.csv', sep='')
|
||||
},
|
||||
content = function(file) {
|
||||
write.csv(datasetInput(), file)
|
||||
}
|
||||
)
|
||||
})
|
||||
13
inst/examples/10_download/ui.R
Normal file
13
inst/examples/10_download/ui.R
Normal file
@@ -0,0 +1,13 @@
|
||||
shinyUI(fluidPage(
|
||||
titlePanel('Downloading Data'),
|
||||
sidebarLayout(
|
||||
sidebarPanel(
|
||||
selectInput("dataset", "Choose a dataset:",
|
||||
choices = c("rock", "pressure", "cars")),
|
||||
downloadButton('downloadData', 'Download')
|
||||
),
|
||||
mainPanel(
|
||||
tableOutput('table')
|
||||
)
|
||||
)
|
||||
))
|
||||
7
inst/examples/11_timer/DESCRIPTION
Normal file
7
inst/examples/11_timer/DESCRIPTION
Normal file
@@ -0,0 +1,7 @@
|
||||
Title: Timer
|
||||
Author: RStudio, Inc.
|
||||
AuthorUrl: http://www.rstudio.com/
|
||||
License: MIT
|
||||
DisplayMode: Showcase
|
||||
Tags: getting-started
|
||||
Type: Shiny
|
||||
4
inst/examples/11_timer/Readme.md
Normal file
4
inst/examples/11_timer/Readme.md
Normal file
@@ -0,0 +1,4 @@
|
||||
The function `invalidateLater()` can be used to invalidate an observer or
|
||||
reactive expression in a given number of milliseconds. In this example, the
|
||||
output `currentTime` is updated every second, so it shows the current time
|
||||
on a second basis.
|
||||
6
inst/examples/11_timer/server.R
Normal file
6
inst/examples/11_timer/server.R
Normal file
@@ -0,0 +1,6 @@
|
||||
shinyServer(function(input, output, session) {
|
||||
output$currentTime <- renderText({
|
||||
invalidateLater(1000, session)
|
||||
paste("The current time is", Sys.time())
|
||||
})
|
||||
})
|
||||
3
inst/examples/11_timer/ui.R
Normal file
3
inst/examples/11_timer/ui.R
Normal file
@@ -0,0 +1,3 @@
|
||||
shinyUI(fluidPage(
|
||||
textOutput("currentTime")
|
||||
))
|
||||
169
inst/staticdocs/index.r
Normal file
169
inst/staticdocs/index.r
Normal file
@@ -0,0 +1,169 @@
|
||||
sd_section("UI Layout",
|
||||
"Functions for laying out the user interface for your application.",
|
||||
c(
|
||||
"absolutePanel",
|
||||
"bootstrapPage",
|
||||
"column",
|
||||
"conditionalPanel",
|
||||
"fixedPage",
|
||||
"fluidPage",
|
||||
"headerPanel",
|
||||
"helpText",
|
||||
"icon",
|
||||
"mainPanel",
|
||||
"navbarPage",
|
||||
"navlistPanel",
|
||||
"pageWithSidebar",
|
||||
"sidebarLayout",
|
||||
"sidebarPanel",
|
||||
"tabPanel",
|
||||
"tabsetPanel",
|
||||
"titlePanel",
|
||||
"inputPanel",
|
||||
"flowLayout",
|
||||
"splitLayout",
|
||||
"verticalLayout",
|
||||
"wellPanel",
|
||||
"withMathJax"
|
||||
)
|
||||
)
|
||||
sd_section("UI Inputs",
|
||||
"Functions for creating user interface elements that prompt the user for input values or interaction.",
|
||||
c(
|
||||
"actionButton",
|
||||
"checkboxGroupInput",
|
||||
"checkboxInput",
|
||||
"dateInput",
|
||||
"dateRangeInput",
|
||||
"fileInput",
|
||||
"numericInput",
|
||||
"radioButtons",
|
||||
"selectInput",
|
||||
"sliderInput",
|
||||
"submitButton",
|
||||
"textInput",
|
||||
"passwordInput",
|
||||
"updateCheckboxGroupInput",
|
||||
"updateCheckboxInput",
|
||||
"updateDateInput",
|
||||
"updateDateRangeInput",
|
||||
"updateNumericInput",
|
||||
"updateRadioButtons",
|
||||
"updateSelectInput",
|
||||
"updateSliderInput",
|
||||
"updateTabsetPanel",
|
||||
"updateTextInput"
|
||||
)
|
||||
)
|
||||
sd_section("UI Outputs",
|
||||
"Functions for creating user interface elements that, in conjunction with rendering functions, display different kinds of output from your application.",
|
||||
c(
|
||||
"htmlOutput",
|
||||
"imageOutput",
|
||||
"plotOutput",
|
||||
"outputOptions",
|
||||
"tableOutput",
|
||||
"textOutput",
|
||||
"verbatimTextOutput",
|
||||
"downloadButton",
|
||||
"Progress",
|
||||
"withProgress"
|
||||
)
|
||||
)
|
||||
sd_section("Interface builder functions",
|
||||
"A sub-library for writing HTML using R functions. These functions form the foundation on which the higher level user interface functions are built, and can also be used in your Shiny UI to provide custom HTML, CSS, and JavaScript.",
|
||||
c(
|
||||
"builder",
|
||||
"HTML",
|
||||
"include",
|
||||
"singleton",
|
||||
"tag",
|
||||
"validateCssUnit",
|
||||
"withTags"
|
||||
)
|
||||
)
|
||||
sd_section("Rendering functions",
|
||||
"Functions that you use in your application's server side code, assigning them to outputs that appear in your user interface.",
|
||||
c(
|
||||
"renderPlot",
|
||||
"renderText",
|
||||
"renderPrint",
|
||||
"renderDataTable",
|
||||
"renderImage",
|
||||
"renderTable",
|
||||
"renderUI",
|
||||
"downloadHandler",
|
||||
"reactivePlot",
|
||||
"reactivePrint",
|
||||
"reactiveTable",
|
||||
"reactiveText",
|
||||
"reactiveUI"
|
||||
)
|
||||
)
|
||||
sd_section("Reactive constructs",
|
||||
"A sub-library that provides reactive programming facilities for R.",
|
||||
c(
|
||||
"invalidateLater",
|
||||
"is.reactivevalues",
|
||||
"isolate",
|
||||
"makeReactiveBinding",
|
||||
"observe",
|
||||
"observeEvent",
|
||||
"reactive",
|
||||
"reactiveFileReader",
|
||||
"reactivePoll",
|
||||
"reactiveTimer",
|
||||
"reactiveValues",
|
||||
"reactiveValuesToList",
|
||||
"domains",
|
||||
"showReactLog"
|
||||
)
|
||||
)
|
||||
sd_section("Boilerplate",
|
||||
"Functions that are required boilerplate in ui.R and server.R.",
|
||||
c(
|
||||
"shinyUI",
|
||||
"shinyServer"
|
||||
)
|
||||
)
|
||||
sd_section("Running",
|
||||
"Functions that are used to run or stop Shiny applications.",
|
||||
c(
|
||||
"runApp",
|
||||
"runExample",
|
||||
"runUrl",
|
||||
"stopApp"
|
||||
)
|
||||
)
|
||||
sd_section("Extending Shiny",
|
||||
"Functions that are intended to be called by third-party packages that extend Shiny.",
|
||||
c(
|
||||
"createWebDependency",
|
||||
"addResourcePath",
|
||||
"registerInputHandler",
|
||||
"removeInputHandler",
|
||||
"markRenderFunction"
|
||||
)
|
||||
)
|
||||
sd_section("Utility functions",
|
||||
"Miscellaneous utilities that may be useful to advanced users or when extending Shiny.",
|
||||
c(
|
||||
"validate",
|
||||
"session",
|
||||
"exprToFunction",
|
||||
"installExprFunction",
|
||||
"parseQueryString",
|
||||
"plotPNG",
|
||||
"repeatable",
|
||||
"shinyDeprecated",
|
||||
"serverInfo",
|
||||
"shiny-options"
|
||||
)
|
||||
)
|
||||
sd_section("Embedding",
|
||||
"Functions that are intended for third-party packages that embed Shiny applications.",
|
||||
c(
|
||||
"shinyApp",
|
||||
"maskReactiveContext"
|
||||
)
|
||||
)
|
||||
154
inst/tests/test-bootstrap.r
Normal file
154
inst/tests/test-bootstrap.r
Normal file
@@ -0,0 +1,154 @@
|
||||
context("bootstrap")
|
||||
|
||||
test_that("CSS unit validation", {
|
||||
# On error, return NA; on success, return result
|
||||
validateCssUnit_wrap <- function(x) {
|
||||
tryCatch(validateCssUnit(x), error = function(e) { NA_character_ })
|
||||
}
|
||||
|
||||
# Test strings and expected results
|
||||
strings <- c("100x", "10px", "10.4px", ".4px", "1px0", "px", "5", "%", "5%", "auto", "1auto", "")
|
||||
expected <- c(NA, "10px", "10.4px", ".4px", NA, NA, "5px", NA, "5%", "auto", NA, NA)
|
||||
results <- vapply(strings, validateCssUnit_wrap, character(1), USE.NAMES = FALSE)
|
||||
expect_equal(results, expected)
|
||||
|
||||
# Numbers should return string with "px"
|
||||
expect_equal(validateCssUnit(100), "100px")
|
||||
})
|
||||
|
||||
|
||||
test_that("Repeated names for selectInput and radioButtons choices", {
|
||||
# These test might be a bit too closely tied to the exact structure of the
|
||||
# tag object, but they get the job done for now.
|
||||
|
||||
# Select input
|
||||
x <- selectInput('id','label', choices = c(a='x1', a='x2', b='x3'), selectize = FALSE)
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<select id="id" class="form-control"><option value="x1" selected>a</option>\n<option value="x2">a</option>\n<option value="x3">b</option></select>',
|
||||
format(x)
|
||||
))
|
||||
|
||||
|
||||
# Radio buttons
|
||||
x <- radioButtons('id','label', choices = c(a='x1', a='x2', b='x3'))
|
||||
choices <- x$children
|
||||
|
||||
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$value, 'x1')
|
||||
expect_equal(choices[[2]]$children[[1]][[1]]$children[[1]]$children[[1]]$attribs$checked, 'checked')
|
||||
|
||||
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[2]]$children[[1]], 'a')
|
||||
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$value, 'x2')
|
||||
expect_equal(choices[[2]]$children[[1]][[2]]$children[[1]]$children[[1]]$attribs$checked, NULL)
|
||||
|
||||
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[2]]$children[[1]], 'b')
|
||||
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$value, 'x3')
|
||||
expect_equal(choices[[2]]$children[[1]][[3]]$children[[1]]$children[[1]]$attribs$checked, NULL)
|
||||
})
|
||||
|
||||
|
||||
test_that("Choices are correctly assigned names", {
|
||||
# Unnamed vector
|
||||
expect_identical(
|
||||
choicesWithNames(c("a","b","3")),
|
||||
list(a="a", b="b", "3"="3")
|
||||
)
|
||||
# Unnamed list
|
||||
expect_identical(
|
||||
choicesWithNames(list("a","b",3)),
|
||||
list(a="a", b="b", "3"=3)
|
||||
)
|
||||
# Vector, with some named, some not
|
||||
expect_identical(
|
||||
choicesWithNames(c(A="a", "b", C="3", "4")),
|
||||
list(A="a", "b"="b", C="3", "4"="4")
|
||||
)
|
||||
# List, with some named, some not
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", "b", C=3, 4)),
|
||||
list(A="a", "b"="b", C=3, "4"=4)
|
||||
)
|
||||
# List, named, with a sub-vector
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", B="b", C=c("d", "e"))),
|
||||
list(A="a", B="b", C=list(d="d", e="e"))
|
||||
)
|
||||
# List, named, with sublist
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", B="b", C=list("d", "e"))),
|
||||
list(A="a", B="b", C=list(d="d", e="e"))
|
||||
)
|
||||
# List, named, with a named sub-vector of length 1
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", B="b", C=c(D="d"))),
|
||||
list(A="a", B="b", C=list(D="d"))
|
||||
)
|
||||
# List, some named, with sublist
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", "b", C=list("d", E="e"))),
|
||||
list(A="a", b="b", C=list(d="d", E="e"))
|
||||
)
|
||||
# Deeper nesting
|
||||
expect_identical(
|
||||
choicesWithNames(list(A="a", "b", C=list(D=list("e", "f"), G=c(H="h", "i")))),
|
||||
list(A="a", b="b", C=list(D=list(e="e", f="f"), G=list(H="h", i="i")))
|
||||
)
|
||||
# Error when sublist is unnamed
|
||||
expect_error(choicesWithNames(list(A="a", "b", list(1,2))))
|
||||
})
|
||||
|
||||
|
||||
test_that("selectOptions returns correct HTML", {
|
||||
# None selected
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("a", "b")), list()),
|
||||
HTML("<option value=\"a\">a</option>\n<option value=\"b\">b</option>")
|
||||
)
|
||||
# One selected
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("a", "b")), "a"),
|
||||
HTML("<option value=\"a\" selected>a</option>\n<option value=\"b\">b</option>")
|
||||
)
|
||||
# One selected, with named items
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list(A="a", B="b")), "a"),
|
||||
HTML("<option value=\"a\" selected>A</option>\n<option value=\"b\">B</option>")
|
||||
)
|
||||
# Two selected, with optgroup
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("a", B=list("c", D="d"))), c("a", "d")),
|
||||
HTML("<option value=\"a\" selected>a</option>\n<optgroup label=\"B\">\n<option value=\"c\">c</option>\n<option value=\"d\" selected>D</option>\n</optgroup>")
|
||||
)
|
||||
|
||||
# Escape HTML in strings
|
||||
expect_identical(
|
||||
selectOptions(choicesWithNames(list("<A>"="a", B="b")), "a"),
|
||||
HTML("<option value=\"a\" selected><A></option>\n<option value=\"b\">B</option>")
|
||||
)
|
||||
})
|
||||
|
||||
test_that("selectInput selects items by default", {
|
||||
# None specified as selected (defaults to first)
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<option value="a" selected>',
|
||||
selectInput('x', 'x', list("a", "b"))
|
||||
))
|
||||
|
||||
# Nested list (optgroup)
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<option value="a" selected>',
|
||||
selectInput('x', 'x', list(A=list("a", "b"), "c"))
|
||||
))
|
||||
|
||||
# Nothing selected when choices=NULL
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<select id="x" class="form-control"></select>',
|
||||
format(selectInput('x', NULL, NULL, selectize = FALSE))
|
||||
))
|
||||
|
||||
# None specified as selected. With multiple=TRUE, none selected by default.
|
||||
expect_true(grepl(fixed = TRUE,
|
||||
'<option value="a">',
|
||||
selectInput('x', 'x', list("a", "b"), multiple = TRUE)
|
||||
))
|
||||
})
|
||||
69
inst/tests/test-gc.r
Normal file
69
inst/tests/test-gc.r
Normal file
@@ -0,0 +1,69 @@
|
||||
context("garbage collection")
|
||||
|
||||
test_that("unreferenced observers are garbage collected", {
|
||||
vals_removed <- FALSE
|
||||
obs_removed <- FALSE
|
||||
vals <- reactiveValues(A=1)
|
||||
obs <- observe({ vals$A })
|
||||
|
||||
# These are called when the objects are garbage-collected
|
||||
reg.finalizer(.subset2(vals,'impl'), function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(obs, function(e) obs_removed <<- TRUE)
|
||||
|
||||
flushReact()
|
||||
|
||||
# Removing this reference to obs doesn't delete it because vals still has a
|
||||
# reference to it
|
||||
rm(obs)
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
|
||||
|
||||
# Updating vals$A and flushing won't make obs go away because it creates a new
|
||||
# context, and vals$A's context tracks obs's context as a dependent
|
||||
vals$A <- 2
|
||||
flushReact()
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
|
||||
|
||||
# Removing vals will result in vals and obs being garbage collected since
|
||||
# there are no other references to them
|
||||
rm(vals)
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(TRUE, TRUE))
|
||||
})
|
||||
|
||||
|
||||
test_that("suspended observers are garbage collected", {
|
||||
vals_removed <- FALSE
|
||||
obs_removed <- FALSE
|
||||
vals <- reactiveValues(A=1)
|
||||
obs <- observe({ vals$A })
|
||||
|
||||
# These are called when the objects are garbage-collected
|
||||
reg.finalizer(.subset2(vals,'impl'), function(e) vals_removed <<- TRUE)
|
||||
reg.finalizer(obs, function(e) obs_removed <<- TRUE)
|
||||
|
||||
flushReact()
|
||||
|
||||
vals$A <- 2
|
||||
flushReact()
|
||||
invisible(gc())
|
||||
|
||||
# Simply suspending and removing our reference to obs doesn't result in GC,
|
||||
# because vals's context still has a reference to obs's context, as a dependent
|
||||
obs$suspend()
|
||||
rm(obs)
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, FALSE))
|
||||
|
||||
# Next time we update vals$A and flush, there's no more reference to obs
|
||||
vals$A <- 3
|
||||
flushReact()
|
||||
invisible(gc())
|
||||
expect_equal(c(vals_removed, obs_removed), c(FALSE, TRUE))
|
||||
|
||||
# Deleting vals should work immediately now
|
||||
rm(vals)
|
||||
invisible(gc()) # Removes vals object
|
||||
expect_equal(c(vals_removed, obs_removed), c(TRUE, TRUE))
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user