[r-cran-crosstalk] 01/02: New upstream version 1.0.0+dfsg
Andreas Tille
tille at debian.org
Wed Dec 20 13:08:45 UTC 2017
This is an automated email from the git hooks/post-receive script.
tille pushed a commit to branch master
in repository r-cran-crosstalk.
commit 48ef241d0ef4f95b5de60c4eb1927083e560e5cc
Author: Andreas Tille <tille at debian.org>
Date: Wed Dec 20 14:08:13 2017 +0100
New upstream version 1.0.0+dfsg
---
DESCRIPTION | 58 ++
LICENSE | 3 +
LICENSE.note | 341 +++++++++
MD5 | 60 ++
NAMESPACE | 21 +
R/controls.R | 554 ++++++++++++++
R/crosstalk.R | 373 ++++++++++
R/ggplot2.R | 81 +++
README.md | 5 +
inst/www/css/crosstalk.css | 27 +
inst/www/js/crosstalk.js | 1471 ++++++++++++++++++++++++++++++++++++++
inst/www/js/crosstalk.js.map | 37 +
inst/www/js/crosstalk.min.js | 2 +
inst/www/js/crosstalk.min.js.map | 1 +
man/ClientValue.Rd | 62 ++
man/SharedData.Rd | 106 +++
man/bscols.Rd | 56 ++
man/crosstalkLibs.Rd | 13 +
man/filter_select.Rd | 49 ++
man/filter_slider.Rd | 97 +++
man/is.SharedData.Rd | 18 +
man/maintain_selection.Rd | 21 +
man/scale_fill_selection.Rd | 46 ++
23 files changed, 3502 insertions(+)
diff --git a/DESCRIPTION b/DESCRIPTION
new file mode 100644
index 0000000..ee14b8e
--- /dev/null
+++ b/DESCRIPTION
@@ -0,0 +1,58 @@
+Package: crosstalk
+Type: Package
+Title: Inter-Widget Interactivity for HTML Widgets
+Version: 1.0.0
+Authors at R: c(
+ person("Joe", "Cheng", role = c("aut", "cre"), email = "joe at 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("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("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("Sami", "Samhuri", role = c("ctb", "cph"),
+ comment = "Javascript strftime library")
+ )
+Description: Provides building blocks for allowing HTML widgets to communicate
+ with each other, with Shiny or without (i.e. static .html files). Currently
+ supports linked brushing and filtering.
+License: MIT + file LICENSE
+Imports: htmltools (>= 0.3.5), jsonlite, lazyeval, R6, shiny (>= 0.11),
+ ggplot2
+URL: https://rstudio.github.io/crosstalk/
+BugReports: https://github.com/rstudio/crosstalk/issues
+RoxygenNote: 5.0.1
+NeedsCompilation: no
+Packaged: 2016-12-20 20:01:51 UTC; jcheng
+Author: Joe Cheng [aut, cre],
+ RStudio [cph],
+ jQuery Foundation [cph] (jQuery library and jQuery UI library),
+ jQuery contributors [ctb, cph] (jQuery library; authors listed in
+ inst/www/shared/jquery-AUTHORS.txt),
+ Mark Otto [ctb] (Bootstrap library),
+ Jacob Thornton [ctb] (Bootstrap library),
+ Bootstrap contributors [ctb] (Bootstrap library),
+ Twitter, Inc [cph] (Bootstrap library),
+ Brian Reavis [ctb, cph] (selectize.js library),
+ Kristopher Michael Kowal [ctb, cph] (es5-shim library),
+ es5-shim contributors [ctb, cph] (es5-shim library),
+ Denis Ineshin [ctb, cph] (ion.rangeSlider library),
+ Sami Samhuri [ctb, cph] (Javascript strftime library)
+Maintainer: Joe Cheng <joe at rstudio.com>
+Repository: CRAN
+Date/Publication: 2016-12-21 08:30:32
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3c3eda6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,3 @@
+YEAR: 2016
+COPYRIGHT HOLDER: RStudio, Inc.
+
diff --git a/LICENSE.note b/LICENSE.note
new file mode 100644
index 0000000..aa50da8
--- /dev/null
+++ b/LICENSE.note
@@ -0,0 +1,341 @@
+This package includes 3rd party open source software components. The following
+is a list of these components (full copies of the license agreements used by
+these components are included below):
+
+- jQuery, https://github.com/jquery/jquery
+- Bootstrap, https://github.com/twbs/bootstrap
+- selectize.js, https://github.com/brianreavis/selectize.js
+- es5-shim, https://github.com/es-shims/es5-shim
+- ion.rangeSlider, https://github.com/IonDen/ion.rangeSlider
+- strftime for Javascript, https://github.com/samsonjs/strftime
+
+
+jQuery license
+----------------------------------------------------------------------
+
+Copyright jQuery Foundation and other contributors, https://jquery.org/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+Bootstrap License
+----------------------------------------------------------------------
+The MIT License (MIT)
+
+Copyright (c) 2011-2014 Twitter, Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+selectize.js
+----------------------------------------------------------------------
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2013 Brian Reavis
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+es5-shim License
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+
+Copyright (C) 2009-2014 Kristopher Michael Kowal and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+ion.rangeSlider License
+----------------------------------------------------------------------
+
+Copyright (C) 2014 by Denis Ineshin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+strftime for Javascript License
+----------------------------------------------------------------------
+
+The MIT License (MIT)
+Copyright © 2015 Sami Samhuri, http://samhuri.net <sami at samhuri.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the “Software”), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/MD5 b/MD5
new file mode 100644
index 0000000..c8a054c
--- /dev/null
+++ b/MD5
@@ -0,0 +1,60 @@
+a32f4f5aa5fac0d61ab581af3472681d *DESCRIPTION
+ab30396b0573e2cc7a8f2a3053e2ec18 *LICENSE
+3ed75fea1786a3bc880e021562763259 *LICENSE.note
+855be570e3b4409c3e1b91a322325f58 *NAMESPACE
+9fed83f3f6dd2899723adf61888e2ea8 *R/controls.R
+244f1e8649f0fa630b1303a7a8b2647f *R/crosstalk.R
+9de980b2aaeb35d6016625a91c7e4f29 *R/ggplot2.R
+ca536fd4b1294f5bac4661e7c6685c85 *README.md
+e5f6fb08f469dc836cb3609e23694b3a *inst/lib/bootstrap/css/bootstrap-theme.css
+f3d5c533946452124ee6e24d49e59819 *inst/lib/bootstrap/css/bootstrap-theme.css.map
+f0c8fc013c87173a395444fce28cb123 *inst/lib/bootstrap/css/bootstrap-theme.min.css
+be665bb9f0f7fc89f515adb828fa0a9b *inst/lib/bootstrap/css/bootstrap.css
+73200b7d42667c653330310eba7b2779 *inst/lib/bootstrap/css/bootstrap.css.map
+58a49b3689d699cb72ffda7252d99fcb *inst/lib/bootstrap/css/bootstrap.min.css
+f4769f9bdb7466be65088239c12046d1 *inst/lib/bootstrap/fonts/glyphicons-halflings-regular.eot
+89889688147bd7575d6327160d64e760 *inst/lib/bootstrap/fonts/glyphicons-halflings-regular.svg
+e18bbf611f2a2e43afc071aa2f4e1512 *inst/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf
+fa2772327f55d8198301fdb8bcfc8158 *inst/lib/bootstrap/fonts/glyphicons-halflings-regular.woff
+448c34a56d699c29117adc64c43affeb *inst/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2
+6bfd171748f088ad503cb07c080b1f33 *inst/lib/bootstrap/js/bootstrap.js
+046ba2b5f4cff7d2eaaa1af55caa9fd8 *inst/lib/bootstrap/js/bootstrap.min.js
+ccb7f3909e30b1eb8f65a24393c6e12b *inst/lib/bootstrap/js/npm.js
+ae8fceae0e07d55b5cfaa9af6bc43a6d *inst/lib/bootstrap/shim/html5shiv.min.js
+506fe393e9f296d14b63733c0aff6205 *inst/lib/bootstrap/shim/respond.min.js
+2ae68042ac97e2b2213b713deece387f *inst/lib/ionrangeslider/css/ion.rangeSlider.css
+8d631a7cac12ac3c47d8bfe67f896543 *inst/lib/ionrangeslider/css/ion.rangeSlider.skinFlat.css
+7527c2a31899e27ddbc0fcba8dfc3b8d *inst/lib/ionrangeslider/css/ion.rangeSlider.skinHTML5.css
+06a452d69645df91f1903faf13aa7608 *inst/lib/ionrangeslider/css/ion.rangeSlider.skinModern.css
+796c5169d059bb621ebbb380bf013afd *inst/lib/ionrangeslider/css/ion.rangeSlider.skinNice.css
+76f1f0a32f94da941bab0bce6d67d477 *inst/lib/ionrangeslider/css/ion.rangeSlider.skinShiny.css
+b3989d62c6fe8f0506cce37fed71df96 *inst/lib/ionrangeslider/css/ion.rangeSlider.skinSimple.css
+ae65a946b5385bd48861c6ce3895e3e8 *inst/lib/ionrangeslider/css/normalize.css
+bcdb14f38e27b16edeabb62e6e9a829b *inst/lib/ionrangeslider/img/sprite-skin-flat.png
+6035779c2555ab87be45bbc21f9cae47 *inst/lib/ionrangeslider/img/sprite-skin-modern.png
+41732f58be91fcdc79381f239685c0e1 *inst/lib/ionrangeslider/img/sprite-skin-nice.png
+43c6858e46da90d6e201dfed860e8b86 *inst/lib/ionrangeslider/img/sprite-skin-simple.png
+d4b2be381ee15900642f9ab06c9bfc65 *inst/lib/ionrangeslider/js/ion.rangeSlider.js
+6b41096306931c8df11fddf0513e47b2 *inst/lib/ionrangeslider/js/ion.rangeSlider.min.js
+12b40fddbb08ec43e278e7d8a0ab5543 *inst/lib/jquery/jquery-AUTHORS.txt
+7f38dcbfb11aff050652ff3b754adb63 *inst/lib/jquery/jquery.js
+895323ed2f7258af4fae2c738c8aea49 *inst/lib/jquery/jquery.min.js
+c34c7baffbb76616a99568e01f27930e *inst/lib/jquery/jquery.min.map
+d75b17ebe7200b2ff0b8d20d32853c35 *inst/lib/selectize/css/selectize.bootstrap3.css
+bd681a8efd2f45628dddf749426dc633 *inst/lib/selectize/js/es5-shim.min.js
+146435eeda32f0e12bca8519f0da5ad9 *inst/lib/selectize/js/selectize.min.js
+4feedff422885d51bf57601f8a987d70 *inst/lib/strftime/strftime-min.js
+a03b8a3a33eb8a69d37a03746ba92b4f *inst/www/css/crosstalk.css
+39a2f512456b98c824f12c857a118ea6 *inst/www/js/crosstalk.js
+a1de3ba877618aa3d1e03d8245cef18f *inst/www/js/crosstalk.js.map
+116f1b299d39312303a2be2b5a7ac5d8 *inst/www/js/crosstalk.min.js
+b5d03fc86d1c2dff0f6fca2c6f16c3e9 *inst/www/js/crosstalk.min.js.map
+0f3f4edabc458eec98af2d53df9f7d0d *man/ClientValue.Rd
+5691508c2ec4bf80cf4def417bcfc35b *man/SharedData.Rd
+b03043bd832ee6df8a7c49c80fdd7add *man/bscols.Rd
+279bca8896be3f1e1fcb23f488b6c392 *man/crosstalkLibs.Rd
+942b1bc332e70e64958931aec2d43356 *man/filter_select.Rd
+a955de0dd8e05931c08400d8fb2a5428 *man/filter_slider.Rd
+58d166ec5524213a271db776586103b1 *man/is.SharedData.Rd
+eb1fddd8f36e6fe303de7f2da2643c13 *man/maintain_selection.Rd
+85dd5b5628e7d518f816f2d9d1605f30 *man/scale_fill_selection.Rd
diff --git a/NAMESPACE b/NAMESPACE
new file mode 100644
index 0000000..c72d459
--- /dev/null
+++ b/NAMESPACE
@@ -0,0 +1,21 @@
+# Generated by roxygen2: do not edit by hand
+
+export(ClientValue)
+export(SharedData)
+export(animation_options)
+export(bscols)
+export(crosstalkLibs)
+export(filter_checkbox)
+export(filter_select)
+export(filter_slider)
+export(is.SharedData)
+export(maintain_selection)
+export(scale_color_selection)
+export(scale_fill_selection)
+export(selection_factor)
+import(R6)
+import(htmltools)
+import(shiny)
+importFrom(stats,na.omit)
+importFrom(stats,setNames)
+importFrom(utils,packageVersion)
diff --git a/R/controls.R b/R/controls.R
new file mode 100644
index 0000000..8a9ddc0
--- /dev/null
+++ b/R/controls.R
@@ -0,0 +1,554 @@
+bootstrapLib <- function(theme = NULL) {
+ # Intentionally use an older version of bootstrap. The rendering
+ # environment may use a bootstrap version that has a theme, and
+ # we don't want to trump that just for our little controls.
+ # Ideally we should find a better solution for this.
+ htmlDependency("bootstrap", "3.3.2",
+ system.file("lib/bootstrap", package = "crosstalk"),
+ script = c(
+ "js/bootstrap.min.js"
+ ),
+ stylesheet = if (is.null(theme)) "css/bootstrap.min.css",
+ meta = list(viewport = "width=device-width, initial-scale=1")
+ )
+}
+
+selectizeLib <- function(bootstrap = TRUE) {
+ htmlDependency(
+ "selectize", "0.11.2",
+ system.file("lib/selectize", package = "crosstalk"),
+ stylesheet = if (bootstrap) "css/selectize.bootstrap3.css",
+ script = "js/selectize.min.js"
+ )
+}
+
+jqueryLib <- function() {
+ htmlDependency(
+ "jquery", "1.11.3",
+ system.file("lib/jquery", package = "crosstalk"),
+ script = "jquery.min.js"
+ )
+}
+
+ionrangesliderLibs <- function() {
+ list(
+ jqueryLib(),
+ htmlDependency("ionrangeslider", "2.1.2",
+ system.file("lib/ionrangeslider", package = "crosstalk"),
+ script = "js/ion.rangeSlider.min.js",
+ # ion.rangeSlider also needs normalize.css, which is already included in
+ # Bootstrap.
+ stylesheet = c("css/ion.rangeSlider.css",
+ "css/ion.rangeSlider.skinShiny.css")
+ ),
+ htmlDependency("strftime", "0.9.2",
+ system.file("lib/strftime", package = "crosstalk"),
+ script = "strftime-min.js"
+ )
+ )
+}
+
+makeGroupOptions <- function(sharedData, group, allLevels) {
+ df <- sharedData$data(
+ withSelection = FALSE,
+ withFilter = FALSE,
+ withKey = TRUE
+ )
+
+ if (inherits(group, "formula"))
+ group <- lazyeval::f_eval(group, df)
+
+ if (length(group) < 1) {
+ stop("Can't form options with zero-length group vector")
+ }
+
+ lvls <- if (is.factor(group)) {
+ if (allLevels) {
+ levels(group)
+ } else {
+ levels(droplevels(group))
+ }
+ } else {
+ sort(unique(group))
+ }
+ matches <- match(group, lvls)
+ vals <- lapply(1:length(lvls), function(i) {
+ df$key_[which(matches == i)]
+ })
+
+ lvls_str <- as.character(lvls)
+
+ options <- list(
+ items = data.frame(value = lvls_str, label = lvls_str, stringsAsFactors = FALSE),
+ map = setNames(vals, lvls_str),
+ group = sharedData$groupName()
+ )
+
+ options
+}
+
+#' Categorical filter controls
+#'
+#' Creates a select box or list of checkboxes, for filtering a
+#' \code{\link{SharedData}} object based on categorical data.
+#'
+#' @param id An HTML element ID; must be unique within the web page
+#' @param label A human-readable label
+#' @param sharedData \code{SharedData} object with the data to filter
+#' @param group A one-sided formula whose values will populate this select box.
+#' Generally this should be a character or factor column; if not, it will be
+#' coerced to character.
+#' @param allLevels If the vector described by \code{group} is factor-based,
+#' should all the levels be displayed as options, or only ones that are
+#' present in the data?
+#' @param multiple Can multiple values be selected?
+#' @param columns Number of columns the options should be arranged into.
+#'
+#' @examples
+#' ## Only run examples in interactive R sessions
+#' if (interactive()) {
+#'
+#' sd <- SharedData$new(chickwts)
+#' filter_select("feedtype", "Feed type", sd, "feed")
+#'
+#' }
+#'
+#' @export
+filter_select <- function(id, label, sharedData, group, allLevels = FALSE,
+ multiple = TRUE) {
+
+ options <- makeGroupOptions(sharedData, group, allLevels)
+
+ htmltools::browsable(attachDependencies(
+ tags$div(id = id, class = "form-group crosstalk-input-select crosstalk-input",
+ tags$label(class = "control-label", `for` = id, label),
+ tags$div(
+ tags$select(
+ multiple = if (multiple) NA else NULL
+ ),
+ tags$script(type = "application/json",
+ `data-for` = id,
+ jsonlite::toJSON(options, dataframe = "columns", pretty = TRUE)
+ )
+ )
+ ),
+ c(list(jqueryLib(), bootstrapLib(), selectizeLib()), crosstalkLibs())
+ ))
+}
+
+columnize <- function(columnCount, elements) {
+ if (columnCount <= 1 || length(elements) <= 1) {
+ return(elements)
+ }
+
+ columnSize <- ceiling(length(elements) / columnCount)
+ lapply(1:ceiling(length(elements) / columnSize), function(i) {
+ tags$div(class = "crosstalk-options-column",
+ {
+ start <- (i-1) * columnSize + 1
+ end <- i * columnSize
+ elements[start:end]
+ }
+ )
+ })
+}
+
+#' @param inline If \code{TRUE}, render checkbox options horizontally instead of vertically.
+#'
+#' @rdname filter_select
+#' @export
+filter_checkbox <- function(id, label, sharedData, group, allLevels = FALSE, inline = FALSE, columns = 1) {
+ options <- makeGroupOptions(sharedData, group, allLevels)
+
+ labels <- options$items$label
+ values <- options$items$value
+ options$items <- NULL # Doesn't need to be serialized for this type of control
+
+ makeCheckbox <- if (inline) inlineCheckbox else blockCheckbox
+
+ htmltools::browsable(attachDependencies(
+ tags$div(id = id, class = "form-group crosstalk-input-checkboxgroup crosstalk-input",
+ tags$label(class = "control-label", `for` = id, label),
+ tags$div(class = "crosstalk-options-group",
+ columnize(columns,
+ mapply(labels, values, FUN = function(label, value) {
+ makeCheckbox(id, value, label)
+ }, SIMPLIFY = FALSE, USE.NAMES = FALSE)
+ )
+ ),
+ tags$script(type = "application/json",
+ `data-for` = id,
+ jsonlite::toJSON(options, dataframe = "columns", pretty = TRUE)
+ )
+ ),
+ c(list(jqueryLib(), bootstrapLib()), crosstalkLibs())
+ ))
+}
+
+blockCheckbox <- function(id, value, label) {
+ tags$div(class = "checkbox",
+ tags$label(
+ tags$input(type = "checkbox", name = id, value = value),
+ tags$span(label)
+ )
+ )
+}
+
+inlineCheckbox <- function(id, value, label) {
+ tags$label(class = "checkbox-inline",
+ tags$input(type = "checkbox", name = id, value = value),
+ tags$span(label)
+ )
+}
+
+#' Range filter control
+#'
+#' Creates a slider widget that lets users filter observations based on a range
+#' of values.
+#'
+#' @param id An HTML element ID; must be unique within the web page
+#' @param label A human-readable label
+#' @param sharedData \code{SharedData} object with the data to filter
+#' @param column A one-sided formula whose values will be used for this slider.
+#' The column must be of type \code{\link{Date}}, \code{\link{POSIXt}}, or
+#' numeric.
+#' @param step Specifies the interval between each selectable value on the
+#' slider (if \code{NULL}, a heuristic is used to determine the step size). If
+#' the values are dates, \code{step} is in days; if the values are times
+#' (POSIXt), \code{step} is in seconds.
+#' @param round \code{TRUE} to round all values to the nearest integer;
+#' \code{FALSE} if no rounding is desired; or an integer to round to that
+#' number of digits (for example, 1 will round to the nearest 10, and -2 will
+#' round to the nearest .01). Any rounding will be applied after snapping to
+#' the nearest step.
+#' @param ticks \code{FALSE} to hide tick marks, \code{TRUE} to show them
+#' according to some simple heuristics.
+#' @param animate \code{TRUE} to show simple animation controls with default
+#' settings; \code{FALSE} not to; or a custom settings list, such as those
+#' created using \code{\link{animationOptions}}.
+#' @param width The width of the slider control (see
+#' \code{\link[htmltools]{validateCssUnit}} for valid formats)
+#' @param sep Separator between thousands places in numbers.
+#' @param pre A prefix string to put in front of the value.
+#' @param post A suffix string to put after the value.
+#' @param dragRange This option is used only if it is a range slider (with two
+#' values). If \code{TRUE} (the default), the range can be dragged. In other
+#' words, the min and max can be dragged together. If \code{FALSE}, the range
+#' cannot be dragged.
+#' @param timeFormat Only used if the values are Date or POSIXt objects. A time
+#' format string, to be passed to the Javascript strftime library. See
+#' \url{https://github.com/samsonjs/strftime} for more details. The allowed
+#' format specifications are very similar, but not identical, to those for R's
+#' \code{\link{strftime}} function. For Dates, the default is \code{"\%F"}
+#' (like \code{"2015-07-01"}), and for POSIXt, the default is \code{"\%F \%T"}
+#' (like \code{"2015-07-01 15:32:10"}).
+#' @param timezone Only used if the values are POSIXt objects. A string
+#' specifying the time zone offset for the displayed times, in the format
+#' \code{"+HHMM"} or \code{"-HHMM"}. If \code{NULL} (the default), times will
+#' be displayed in the browser's time zone. The value \code{"+0000"} will
+#' result in UTC time.
+#'
+#' @examples
+#' ## Only run examples in interactive R sessions
+#' if (interactive()) {
+#'
+#' sd <- SharedData$new(mtcars)
+#' filter_slider("mpg", "Miles per gallon", sd, "mpg")
+#'
+#' }
+#' @export
+filter_slider <- function(id, label, sharedData, column, step = NULL,
+ round = FALSE, ticks = TRUE, animate = FALSE, width = NULL, sep = ",",
+ pre = NULL, post = NULL, timeFormat = NULL,
+ timezone = NULL, dragRange = TRUE)
+{
+ # TODO: Check that this works well with factors
+ # TODO: Handle empty data frame, NA/NaN/Inf/-Inf values
+
+ if (is.character(column)) {
+ column <- lazyeval::f_new(as.symbol(column))
+ }
+
+ df <- sharedData$data(withKey = TRUE)
+ col <- lazyeval::f_eval(column, df)
+ values <- na.omit(col)
+ min <- min(values)
+ max <- max(values)
+ value <- range(values)
+
+ ord <- order(col)
+ options <- list(
+ values = col[ord],
+ keys = df$key_[ord],
+ group = sharedData$groupName()
+ )
+
+ # If step is NULL, use heuristic to set the step size.
+ findStepSize <- function(min, max, step) {
+ if (!is.null(step)) return(step)
+
+ range <- max - min
+ # If short range or decimals, use continuous decimal with ~100 points
+ if (range < 2 || hasDecimals(min) || hasDecimals(max)) {
+ step <- pretty(c(min, max), n = 100)
+ step[2] - step[1]
+ } else {
+ 1
+ }
+ }
+
+ if (inherits(min, "Date")) {
+ if (!inherits(max, "Date") || !inherits(value, "Date"))
+ stop("`min`, `max`, and `value must all be Date or non-Date objects")
+ dataType <- "date"
+
+ if (is.null(timeFormat))
+ timeFormat <- "%F"
+
+ } else if (inherits(min, "POSIXt")) {
+ if (!inherits(max, "POSIXt") || !inherits(value, "POSIXt"))
+ stop("`min`, `max`, and `value must all be POSIXt or non-POSIXt objects")
+ dataType <- "datetime"
+
+ if (is.null(timeFormat))
+ timeFormat <- "%F %T"
+
+ } else {
+ dataType <- "number"
+ }
+
+ step <- findStepSize(min, max, step)
+ # Avoid ugliness from floating point errors, e.g.
+ # findStepSize(min(quakes$mag), max(quakes$mag), NULL)
+ # was returning 0.01999999999999957 instead of 0.2
+ step <- signif(step, 14)
+
+ if (dataType %in% c("date", "datetime")) {
+ # For Dates, this conversion uses midnight on that date in UTC
+ to_ms <- function(x) 1000 * as.numeric(as.POSIXct(x))
+
+ # Convert values to milliseconds since epoch (this is the value JS uses)
+ # Find step size in ms
+ step <- to_ms(max) - to_ms(max - step)
+ min <- to_ms(min)
+ max <- to_ms(max)
+ value <- to_ms(value)
+ }
+
+ range <- max - min
+
+ # Try to get a sane number of tick marks
+ if (ticks) {
+ n_steps <- range / step
+
+ # Make sure there are <= 10 steps.
+ # n_ticks can be a noninteger, which is good when the range is not an
+ # integer multiple of the step size, e.g., min=1, max=10, step=4
+ scale_factor <- ceiling(n_steps / 10)
+ n_ticks <- n_steps / scale_factor
+
+ } else {
+ n_ticks <- NULL
+ }
+
+ sliderProps <- dropNulls(list(
+ `data-type` = if (length(value) > 1) "double",
+ `data-min` = formatNoSci(min),
+ `data-max` = formatNoSci(max),
+ `data-from` = formatNoSci(value[1]),
+ `data-to` = if (length(value) > 1) formatNoSci(value[2]),
+ `data-step` = formatNoSci(step),
+ `data-grid` = ticks,
+ `data-grid-num` = n_ticks,
+ `data-grid-snap` = FALSE,
+ `data-prettify-separator` = sep,
+ `data-prefix` = pre,
+ `data-postfix` = post,
+ `data-keyboard` = TRUE,
+ `data-keyboard-step` = step / (max - min) * 100,
+ `data-drag-interval` = dragRange,
+ # The following are ignored by the ion.rangeSlider, but are used by Shiny.
+ `data-data-type` = dataType,
+ `data-time-format` = timeFormat,
+ `data-timezone` = timezone
+ ))
+
+ # Replace any TRUE and FALSE with "true" and "false"
+ sliderProps <- lapply(sliderProps, function(x) {
+ if (identical(x, TRUE)) "true"
+ else if (identical(x, FALSE)) "false"
+ else x
+ })
+
+ sliderTag <- div(
+ class = "form-group crosstalk-input",
+ class = "crosstalk-input-slider js-range-slider",
+ id = id,
+
+ style = if (!is.null(width)) paste0("width: ", validateCssUnit(width), ";"),
+ if (!is.null(label)) controlLabel(id, label),
+ do.call(tags$input, sliderProps),
+ tags$script(type = "application/json",
+ `data-for` = id,
+ jsonlite::toJSON(options, dataframe = "columns", pretty = TRUE)
+ )
+ )
+
+ # Add animation buttons
+ if (identical(animate, TRUE))
+ animate <- animationOptions()
+
+ if (!is.null(animate) && !identical(animate, FALSE)) {
+ if (is.null(animate$playButton))
+ animate$playButton <- shiny::icon('play', lib = 'glyphicon')
+ if (is.null(animate$pauseButton))
+ animate$pauseButton <- shiny::icon('pause', lib = 'glyphicon')
+
+ sliderTag <- tagAppendChild(
+ sliderTag,
+ tags$div(class='slider-animate-container',
+ tags$a(href='#',
+ class='slider-animate-button',
+ 'data-target-id'=id,
+ 'data-interval'=animate$interval,
+ 'data-loop'=animate$loop,
+ span(class = 'play', animate$playButton),
+ span(class = 'pause', animate$pauseButton)
+ )
+ )
+ )
+ }
+
+ htmltools::browsable(attachDependencies(
+ sliderTag,
+ c(ionrangesliderLibs(), crosstalkLibs())
+ ))
+}
+
+hasDecimals <- function(value) {
+ truncatedValue <- round(value)
+ return (!identical(value, truncatedValue))
+}
+
+#' @rdname filter_slider
+#'
+#' @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
+animation_options <- function(interval=1000,
+ loop=FALSE,
+ playButton=NULL,
+ pauseButton=NULL) {
+ list(interval=interval,
+ loop=loop,
+ playButton=playButton,
+ pauseButton=pauseButton)
+}
+
+#' Arrange HTML elements or widgets in Bootstrap columns
+#'
+#' This helper function makes it easy to put HTML elements side by side. It can
+#' be called directly from the console but is especially designed to work in an
+#' R Markdown document. Warning: This will bring in all of Bootstrap!
+#'
+#' @param ... \code{htmltools} tag objects, lists, text, HTML widgets, or
+#' NULL. These arguments should be unnamed.
+#' @param widths The number of columns that should be assigned to each of the
+#' \code{...} elements (the total number of columns available is always 12).
+#' The width vector will be recycled if there are more \code{...} arguments.
+#' \code{NA} columns will evenly split the remaining columns that are left
+#' after the widths are recycled and non-\code{NA} values are subtracted.
+#' @param device The class of device which is targeted by these widths; with
+#' smaller screen sizes the layout will collapse to a one-column,
+#' top-to-bottom display instead. xs: never collapse, sm: collapse below
+#' 768px, md: 992px, lg: 1200px.
+#'
+#' @return A \code{\link[htmltools]{browsable}} HTML element.
+#'
+#' @examples
+#' library(htmltools)
+#'
+#' # If width is unspecified, equal widths will be used
+#' bscols(
+#' div(style = css(width="100%", height="400px", background_color="red")),
+#' div(style = css(width="100%", height="400px", background_color="blue"))
+#' )
+#'
+#' # Use NA to absorb remaining width
+#' bscols(widths = c(2, NA, NA),
+#' div(style = css(width="100%", height="400px", background_color="red")),
+#' div(style = css(width="100%", height="400px", background_color="blue")),
+#' div(style = css(width="100%", height="400px", background_color="green"))
+#' )
+#'
+#' # Recycling widths
+#' bscols(widths = c(2, 4),
+#' div(style = css(width="100%", height="400px", background_color="red")),
+#' div(style = css(width="100%", height="400px", background_color="blue")),
+#' div(style = css(width="100%", height="400px", background_color="red")),
+#' div(style = css(width="100%", height="400px", background_color="blue"))
+#' )
+#' @export
+bscols <- function(..., widths = NA, device = c("xs", "sm", "md", "lg")) {
+ device <- match.arg(device)
+
+ if (length(list(...)) == 0) {
+ widths = c()
+ } else {
+ if (length(widths) > length(list(...))) {
+ warning("Too many widths provided to bscols; truncating")
+ }
+ widths <- rep_len(widths, length(list(...)))
+
+ if (any(is.na(widths))) {
+ remaining <- 12 - sum(widths, na.rm = TRUE)
+ stretch_cols <- length(which(is.na(widths)))
+ stretch_width <- max(1, floor(remaining / stretch_cols))
+ widths[is.na(widths)] <- stretch_width
+ }
+
+ if (sum(widths) > 12) {
+ warning("Sum of bscol width units is greater than 12")
+ }
+ }
+
+ ui <- tags$div(class = "container-fluid crosstalk-bscols",
+ # Counteract knitr pre/code output blocks
+ tags$div(class = "fluid-row",
+ unname(mapply(list(...), widths, FUN = function(el, width) {
+ div(class = sprintf("col-%s-%s", device, width),
+ el
+ )
+ }, SIMPLIFY = FALSE))
+ )
+ )
+
+ browsable(attachDependencies(ui, list(jqueryLib(), bootstrapLib())))
+}
+
+controlLabel <- function(controlName, label) {
+ if (is.null(label)) {
+ NULL
+ } else {
+ tags$label(class = "control-label", `for` = controlName, label)
+ }
+}
+
+# Given a vector or list, drop all the NULL items in it
+dropNulls <- function(x) {
+ x[!vapply(x, is.null, FUN.VALUE=logical(1))]
+}
+
+# Format a number without sci notation, and keep as many digits as possible (do
+# we really need to go beyond 15 digits?)
+formatNoSci <- function(x) {
+ if (is.null(x)) return(NULL)
+ format(x, scientific = FALSE, digits = 15)
+}
diff --git a/R/crosstalk.R b/R/crosstalk.R
new file mode 100644
index 0000000..3a35081
--- /dev/null
+++ b/R/crosstalk.R
@@ -0,0 +1,373 @@
+#' @import htmltools
+init <- function() {
+ htmltools::attachDependencies(
+ list(),
+ crosstalkLibs()
+ )
+}
+
+#' Crosstalk dependencies
+#'
+#' List of \code{\link[htmltools]{htmlDependency}} objects necessary for
+#' Crosstalk to function. Intended for widget authors.
+#' @importFrom stats na.omit setNames
+#' @importFrom utils packageVersion
+#' @export
+crosstalkLibs <- function() {
+ list(
+ jqueryLib(),
+ htmltools::htmlDependency("crosstalk", packageVersion("crosstalk"),
+ src = system.file("www", package = "crosstalk"),
+ script = "js/crosstalk.min.js",
+ stylesheet = "css/crosstalk.css"
+ )
+ )
+}
+
+#' ClientValue object
+#'
+#' An object that can be used in a \href{http://shiny.rstudio.com}{Shiny} server
+#' function to get or set a crosstalk variable that exists on the client. The
+#' client copy of the variable is the canonical copy, so there is no direct
+#' "set" method that immediately changes the value; instead, there is a
+#' \code{sendUpdate} method that sends a request to the browser to change the
+#' value, which will then cause the new value to be relayed back to the server.
+#'
+#' @section Methods:
+#' \describe{
+#' \item{\code{initialize(name, group = "default", session = shiny::getDefaultReactiveDomain())}}{
+#' Create a new ClientValue object to reflect the crosstalk variable
+#' specified by \code{group} and \code{name}. The \code{session} indicates
+#' which Shiny session to connect to, and defaults to the current session.
+#' }
+#' \item{\code{get()}}{
+#' Read the value. This is a reactive operation akin to reading a reactive
+#' value, and so can only be done in a reactive context (e.g. in a
+#' \code{\link[shiny]{reactive}}, \code{\link[shiny]{observe}}, or
+#' \code{\link[shiny]{isolate}} block).
+#' }
+#' \item{\code{sendUpdate(value)}}{
+#' Send a message to the browser asking it to update the crosstalk var to
+#' the given value. This update does not happen synchronously, that is, a
+#' call to \code{get()} immediately following \code{sendUpdate(value)} will
+#' not reflect the new value. The value must be serializable as JSON using
+#' jsonlite.
+#' }
+#' }
+#'
+#' @examples
+#' library(shiny)
+#'
+#' server <- function(input, output, session) {
+#' cv <- ClientValue$new("var1", "group1")
+#'
+#' r <- reactive({
+#' # Don't proceed unless cv$get() is a non-NULL value
+#' validate(need(cv$get(), message = FALSE))
+#'
+#' runif(cv$get())
+#' })
+#'
+#' observeEvent(input$click, {
+#' cv$sendUpdate(NULL)
+#' })
+#' }
+#'
+#' @docType class
+#' @import R6
+#' @format An \code{\link{R6Class}} generator object
+#' @export
+ClientValue <- R6Class(
+ "ClientValue",
+ private = list(
+ .session = "ANY",
+ .name = "ANY",
+ .group = "ANY",
+ .qualifiedName = "ANY",
+ .rv = "ANY"
+ ),
+ public = list(
+ initialize = function(name, group = "default", session = shiny::getDefaultReactiveDomain()) {
+ private$.session <- session
+ private$.name <- name
+ private$.group <- group
+ private$.qualifiedName <- paste0(".clientValue-", group, "-", name)
+ },
+ get = function() {
+ private$.session$input[[private$.qualifiedName]]
+ },
+ sendUpdate = function(value) {
+ private$.session$sendCustomMessage("update-client-value", list(
+ name = private$.name,
+ group = private$.group,
+ value = value
+ ))
+ }
+ )
+)
+
+
+createUniqueId <- function (bytes, prefix = "", suffix = "") {
+ paste(prefix, paste(format(as.hexmode(sample(256, bytes,
+ replace = TRUE) - 1), width = 2), collapse = ""),
+ suffix, sep = "")
+}
+
+
+#' An R6 class that represents a shared data frame
+#'
+#' ...or sufficiently data frame-like object. The primary use for
+#' \code{SharedData} is to be passed to Crosstalk-compatible widgets in place
+#' of a data frame. Each \code{SharedData$new(...)} call makes a new "group"
+#' of widgets that link to each other, but not to widgets in other groups.
+#' You can also use a \code{SharedData} object from Shiny code in order to
+#' react to filtering and brushing from non-widget visualizations (like ggplot2
+#' plots).
+#'
+#' @section Constructor:
+#'
+#' \code{SharedData$new(data, key = NULL, group = createUniqueId(4, prefix = "SharedData"))}
+#'
+#' \describe{
+#' \item{\code{data}}{
+#' A data frame-like object, or a Shiny \link[=reactive]{reactive
+#' expression} that returns a data frame-like object.
+#' }
+#' \item{\code{key}}{
+#' Character vector or one-sided formula that indicates the name of the
+#' column that represents the key or ID of the data frame. These \emph{must}
+#' be unique, and ideally will be something intrinsic to the data (a proper
+#' ID) rather than a transient property like row index.
+#'
+#' If \code{NULL}, then \code{row.names(data)} will be used.
+#' }
+#' \item{\code{group}}{
+#' The "identity" of the Crosstalk group that widgets will join when you
+#' pass them this \code{SharedData} object. In some cases, you will want to
+#' have multiple independent \code{SharedData} objects link up to form a
+#' single web of widgets that all share selection and filtering state; in
+#' those cases, you'll give those \code{SharedData} objects the same group
+#' name. (One example: in Shiny, ui.R and server.R might each need their own
+#' \code{SharedData} instance, even though they're intended to represent a
+#' single group.)
+#' }
+#' }
+#'
+#' @section Methods:
+#'
+#' \describe{
+#' \item{\code{data(withSelection = FALSE, withFilter = TRUE, withKey = FALSE)}}{
+#' Return the data (or read and return the data if the data is a Shiny
+#' reactive expression). If \code{withSelection}, add a \code{selection_}
+#' column with logical values indicating which rows are in the current
+#' selection, or \code{NA} if no selection is currently active. If
+#' \code{withFilter} (the default), only return rows that are part of the
+#' current filter settings, if any. If \code{withKey}, add a \code{key_}
+#' column with the key values of each row (normally not needed since the
+#' key is either one of the other columns or else just the row names).
+#'
+#' When running in Shiny, calling \code{data()} is a reactive operation
+#' that will invalidate if the selection or filter change (assuming that
+#' information was requested), or if the original data is a reactive
+#' expression that has invalidated.
+#' }
+#' \item{\code{origData()}}{
+#' Return the data frame that was used to create this \code{SharedData}
+#' instance. If a reactive expression, evaluate the reactive expression.
+#' Equivalent to \code{data(FALSE, FALSE, FALSE)}.
+#' }
+#' \item{\code{groupName()}}{
+#' Returns the value of \code{group} that was used to create this instance.
+#' }
+#' \item{\code{key()}}{
+#' Returns the vector of key values. Filtering is not applied.
+#' }
+#' \item{\code{selection(value, ownerId = "")}}{
+#' If called without arguments, returns a logical vector of rows that are
+#' currently selected (brushed), or \code{NULL} if no selection exists.
+#' Intended to be called from a Shiny reactive context, and invalidates
+#' whenever the selection changes.
+#'
+#' If called with one or two arguments, expects \code{value} to be a logical
+#' vector of \code{nrow(origData())} length, indicating which rows are
+#' currently selected (brushed). This value is propagated to the web browser
+#' (assumes an active Shiny app or Shiny R Markdown document).
+#'
+#' Set the \code{ownerId} argument to the \code{outputId} of a widget if
+#' conceptually that widget "initiated" the selection (prevents that widget
+#' from clearing its visual selection box, which is normally cleared when
+#' the selection changes). For example, if setting the selection based on a
+#' \code{\link[shiny]{plotOutput}} brush, then \code{ownerId} should be the
+#' \code{outputId} of the \code{plotOutput}.
+#' }
+#' \item{\code{clearSelection(ownerId = "")}}{
+#' Clears the selection. For the meaning of \code{ownerId}, see the
+#' \code{selection} method.
+#' }
+#' }
+#'
+#' @import R6 shiny
+#' @export
+SharedData <- R6Class(
+ "SharedData",
+ private = list(
+ .data = "ANY",
+ .key = "ANY",
+ .filterCV = "ANY",
+ .selectionCV = "ANY",
+ .rv = "ANY",
+ .group = "ANY"
+ ),
+ public = list(
+ initialize = function(data, key = NULL, group = createUniqueId(4, prefix = "SharedData")) {
+ private$.data <- data
+ private$.filterCV <- ClientValue$new("filter", group)
+ private$.selectionCV <- ClientValue$new("selection", group)
+ private$.rv <- shiny::reactiveValues()
+ private$.group <- group
+
+ if (inherits(key, "formula")) {
+ private$.key <- key
+ } else if (is.character(key)) {
+ private$.key <- key
+ } else if (is.function(key)) {
+ private$.key <- key
+ } else if (is.null(key)) {
+ private$.key <- key
+ } else {
+ stop("Unknown key type")
+ }
+
+ if (shiny::is.reactive(private$.data)) {
+ observeEvent(private$.data(), {
+ self$clearSelection()
+ })
+ }
+
+ domain <- shiny::getDefaultReactiveDomain()
+ if (!is.null(domain)) {
+ observe({
+ selection <- private$.selectionCV$get()
+ if (!is.null(selection) && length(selection) > 0) {
+ self$.updateSelection(self$key() %in% selection)
+ } else {
+ self$.updateSelection(NULL)
+ }
+ })
+ }
+ },
+ origData = function() {
+ if (shiny::is.reactive(private$.data)) {
+ private$.data()
+ } else {
+ private$.data
+ }
+ },
+ groupName = function() {
+ private$.group
+ },
+ key = function() {
+ df <- if (shiny::is.reactive(private$.data)) {
+ private$.data()
+ } else {
+ private$.data
+ }
+
+ key <- private$.key
+ if (inherits(key, "formula"))
+ lazyeval::f_eval(key, df)
+ else if (is.character(key))
+ key
+ else if (is.function(key))
+ key(df)
+ else if (!is.null(row.names(df)))
+ row.names(df)
+ else if (nrow(df) > 0)
+ as.character(1:nrow(df))
+ else
+ character()
+ },
+ data = function(withSelection = FALSE, withFilter = TRUE, withKey = FALSE) {
+ df <- if (shiny::is.reactive(private$.data)) {
+ private$.data()
+ } else {
+ private$.data
+ }
+
+ op <- options(shiny.suppressMissingContextError = TRUE)
+ on.exit(options(op), add = TRUE)
+
+ if (withSelection) {
+ if (is.null(private$.rv$selected) || length(private$.rv$selected) == 0) {
+ df$selected_ = NA
+ } else {
+ # TODO: Warn if the length of _selected is different?
+ df$selected_ <- private$.rv$selected
+ }
+ }
+
+ if (withKey) {
+ df$key_ <- self$key()
+ }
+
+ if (withFilter) {
+ if (!is.null(private$.filterCV$get())) {
+ df <- df[self$key() %in% private$.filterCV$get(),]
+ }
+ }
+
+ df
+ },
+ # Public API for selection getting/setting. Setting a selection will
+ # cause an event to be propagated to the client.
+ selection = function(value, ownerId = "") {
+ if (missing(value)) {
+ return(private$.rv$selected)
+ } else {
+ # TODO: Should we even update the server at this time? Or do we
+ # force all such events to originate in the client (much like
+ # updateXXXInput)?
+
+ # .updateSelection needs logical array of length nrow(data)
+ # .selectionCV$sendUpdate needs character array of keys
+ isolate({
+ if (is.null(value)) {
+ self$.updateSelection(NULL)
+ private$.selectionCV$sendUpdate(NULL)
+ } else {
+ key <- self$key()
+ if (is.character(value)) {
+ self$.updateSelection(key %in% value)
+ private$.selectionCV$sendUpdate(value)
+ } else if (is.logical(value)) {
+ self$.updateSelection(value)
+ private$.selectionCV$sendUpdate(key[value])
+ } else if (is.numeric(value)) {
+ self$selection(1:nrow(self$data(FALSE)) %in% value)
+ }
+ }
+ })
+ }
+ },
+ clearSelection = function(ownerId = "") {
+ self$selection(list(), ownerId = "")
+ },
+ # Update selection without sending event
+ .updateSelection = function(value) {
+ force(value)
+ `$<-`(private$.rv, "selected", value)
+ }
+ )
+)
+
+#' Check if an object is \code{SharedData}
+#'
+#' Check if an object is an instance of \code{\link{SharedData}} or not.
+#'
+#' @param x The object that may or may not be an instance of \code{SharedData}
+#' @return logical
+#'
+#' @export
+is.SharedData <- function(x) {
+ inherits(x, "SharedData")
+}
diff --git a/R/ggplot2.R b/R/ggplot2.R
new file mode 100644
index 0000000..39f9025
--- /dev/null
+++ b/R/ggplot2.R
@@ -0,0 +1,81 @@
+#' ggplot2 helpers
+#'
+#' Add \code{scale_fill_selection()} or \code{scale_color_selection} to a ggplot
+#' to customize the scale for fill or color, respectively, for linked brushing.
+#' Use \code{selection_factor} to turn logical vectors representing selection,
+#' to a factor with the levels ordered for use with ggplot2 bar stacking.
+#'
+#' @param color_false The color that should be mapped to unselected rows
+#' @param color_true The color that should be mapped to selected rows
+#'
+#' @examples
+#' \dontrun{
+#' sd <- SharedData$new(iris)
+#' renderPlot({
+#' df <- sd$data(withSelection = TRUE, withFilter = TRUE)
+#' ggplot(df, aes(Sepal.Length, Sepal.Width,
+#' color = selection_factor(df))) +
+#' geom_point() +
+#' scale_color_selection("#444444", "skyblue1")
+#' })
+#'
+#' }
+#' @export
+scale_fill_selection <- function(color_false, color_true) {
+ list(
+ ggplot2::scale_fill_manual(values = c("TRUE" = color_true, "FALSE" = color_false)),
+ ggplot2::guides(fill = FALSE)
+ )
+}
+
+#' @rdname scale_fill_selection
+#' @export
+scale_color_selection <- function(color_false, color_true) {
+ list(
+ ggplot2::scale_color_manual(values = c("TRUE" = color_true, "FALSE" = color_false)),
+ ggplot2::guides(colour = FALSE)
+ )
+}
+
+#' @param x Either a data frame with a \code{selected_} column, or, a logical
+#' vector indicating which rows are selected
+#' @param na.replace The value to use to replace \code{NA} values; choose either
+#' \code{FALSE}, \code{NA}, or \code{TRUE} based on how you want values to be
+#' treated when no selection is active
+#' @rdname scale_fill_selection
+#' @export
+selection_factor <- function(x, na.replace = c(FALSE, NA, TRUE)) {
+ if (missing(na.replace))
+ na.replace <- FALSE
+
+ selection <- if (is.logical(x)) {
+ x
+ } else {
+ x$selected_
+ }
+ selection[is.na(selection)] <- na.replace
+ factor(selection, ordered = TRUE, levels = c(TRUE, FALSE))
+}
+
+#' Synchronize Shiny brush selection with shared data
+#'
+#' Waits for a brush to change, and propagates that change to the
+#' \code{sharedData} object.
+#'
+#' @param sharedData The shared data instance
+#' @param brushId Character vector indicating the name of the \code{plotOutput}
+#' brush
+#' @param ownerId (TBD)
+#'
+#' @export
+maintain_selection <- function(sharedData, brushId, ownerId = "") {
+ force(sharedData)
+ force(brushId)
+ session <- shiny::getDefaultReactiveDomain()
+
+ observeEvent(session$input[[brushId]], {
+ df <- sharedData$data(withKey = TRUE, withFilter = TRUE)
+ df <- shiny::brushedPoints(df, session$input[[brushId]])
+ sharedData$selection(df$key_, ownerId)
+ }, ignoreNULL = FALSE)
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b98d0b9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# Crosstalk [](https://travis-ci.org/rstudio/crosstalk)
+
+Crosstalk is a package for R that enhances the [htmlwidgets](http://htmlwidgets.org) package. It extends htmlwidgets with a set of classes, functions, and conventions for implementing cross-widget interactions (currently, linked brushing and filtering).
+
+Find out more at the documentation website: http://rstudio.github.io/crosstalk/
diff --git a/inst/www/css/crosstalk.css b/inst/www/css/crosstalk.css
new file mode 100644
index 0000000..46befd2
--- /dev/null
+++ b/inst/www/css/crosstalk.css
@@ -0,0 +1,27 @@
+/* Adjust margins outwards, so column contents line up with the edges of the
+ parent of container-fluid. */
+.container-fluid.crosstalk-bscols {
+ margin-left: -30px;
+ margin-right: -30px;
+ white-space: normal;
+}
+
+/* But don't adjust the margins outwards if we're directly under the body,
+ i.e. we were the top-level of something at the console. */
+body > .container-fluid.crosstalk-bscols {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column {
+ display: inline-block;
+ padding-right: 12px;
+ vertical-align: top;
+}
+
+ at media only screen and (max-width:480px) {
+ .crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column {
+ display: block;
+ padding-right: inherit;
+ }
+}
diff --git a/inst/www/js/crosstalk.js b/inst/www/js/crosstalk.js
new file mode 100644
index 0000000..8e6ee30
--- /dev/null
+++ b/inst/www/js/crosstalk.js
@@ -0,0 +1,1471 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) [...]
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Events = function () {
+ function Events() {
+ _classCallCheck(this, Events);
+
+ this._types = {};
+ this._seq = 0;
+ }
+
+ _createClass(Events, [{
+ key: "on",
+ value: function on(eventType, listener) {
+ var subs = this._types[eventType];
+ if (!subs) {
+ subs = this._types[eventType] = {};
+ }
+ var sub = "sub" + this._seq++;
+ subs[sub] = listener;
+ return sub;
+ }
+
+ // Returns false if no match, or string for sub name if matched
+
+ }, {
+ key: "off",
+ value: function off(eventType, listener) {
+ var subs = this._types[eventType];
+ if (typeof listener === "function") {
+ for (var key in subs) {
+ if (subs.hasOwnProperty(key)) {
+ if (subs[key] === listener) {
+ delete subs[key];
+ return key;
+ }
+ }
+ }
+ return false;
+ } else if (typeof listener === "string") {
+ if (subs && subs[listener]) {
+ delete subs[listener];
+ return listener;
+ }
+ return false;
+ } else {
+ throw new Error("Unexpected type for listener");
+ }
+ }
+ }, {
+ key: "trigger",
+ value: function trigger(eventType, arg, thisObj) {
+ var subs = this._types[eventType];
+ for (var key in subs) {
+ if (subs.hasOwnProperty(key)) {
+ subs[key].call(thisObj, arg);
+ }
+ }
+ }
+ }]);
+
+ return Events;
+}();
+
+exports.default = Events;
+
+},{}],2:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.FilterHandle = undefined;
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) [...]
+
+var _events = require("./events");
+
+var _events2 = _interopRequireDefault(_events);
+
+var _filterset = require("./filterset");
+
+var _filterset2 = _interopRequireDefault(_filterset);
+
+var _group = require("./group");
+
+var _group2 = _interopRequireDefault(_group);
+
+var _util = require("./util");
+
+var util = _interopRequireWildcard(_util);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function getFilterSet(group) {
+ var fsVar = group.var("filterset");
+ var result = fsVar.get();
+ if (!result) {
+ result = new _filterset2.default();
+ fsVar.set(result);
+ }
+ return result;
+}
+
+var id = 1;
+function nextId() {
+ return id++;
+}
+
+var FilterHandle = exports.FilterHandle = function () {
+ /**
+ * @classdesc
+ * Use this class to contribute to, and listen for changes to, the filter set
+ * for the given group of widgets. Filter input controls should create one
+ * `FilterHandle` and only call {@link FilterHandle#set}. Output widgets that
+ * wish to displayed filtered data should create one `FilterHandle` and use
+ * the {@link FilterHandle#filteredKeys} property and listen for change
+ * events.
+ *
+ * If two (or more) `FilterHandle` instances in the same webpage share the
+ * same group name, they will contribute to a single "filter set". Each
+ * `FilterHandle` starts out with a `null` value, which means they take
+ * nothing away from the set of data that should be shown. To make a
+ * `FilterHandle` actually remove data from the filter set, set its value to
+ * an array of keys which should be displayed. Crosstalk will aggregate the
+ * various key arrays by finding their intersection; only keys that are
+ * present in all non-null filter handles are considered part of the filter
+ * set.
+ *
+ * @param {string} [group] - The name of the Crosstalk group, or if none,
+ * null or undefined (or any other falsy value). This can be changed later
+ * via the @{link FilterHandle#setGroup} method.
+ * @param {Object} [extraInfo] - An object whose properties will be copied to
+ * the event object whenever an event is emitted.
+ */
+ function FilterHandle(group, extraInfo) {
+ _classCallCheck(this, FilterHandle);
+
+ this._eventRelay = new _events2.default();
+ this._emitter = new util.SubscriptionTracker(this._eventRelay);
+
+ // Name of the group we're currently tracking, if any. Can change over time.
+ this._group = null;
+ // The filterSet that we're tracking, if any. Can change over time.
+ this._filterSet = null;
+ // The Var we're currently tracking, if any. Can change over time.
+ this._filterVar = null;
+ // The event handler subscription we currently have on var.on("change").
+ this._varOnChangeSub = null;
+
+ this._extraInfo = util.extend({ sender: this }, extraInfo);
+
+ this._id = "filter" + nextId();
+
+ this.setGroup(group);
+ }
+
+ /**
+ * Changes the Crosstalk group membership of this FilterHandle. If `set()` was
+ * previously called on this handle, switching groups will clear those keys
+ * from the old group's filter set. These keys will not be applied to the new
+ * group's filter set either. In other words, `setGroup()` effectively calls
+ * `clear()` before switching groups.
+ *
+ * @param {string} group - The name of the Crosstalk group, or null (or
+ * undefined) to clear the group.
+ */
+
+
+ _createClass(FilterHandle, [{
+ key: "setGroup",
+ value: function setGroup(group) {
+ var _this = this;
+
+ // If group is unchanged, do nothing
+ if (this._group === group) return;
+ // Treat null, undefined, and other falsy values the same
+ if (!this._group && !group) return;
+
+ if (this._filterVar) {
+ this._filterVar.off("change", this._varOnChangeSub);
+ this.clear();
+ this._varOnChangeSub = null;
+ this._filterVar = null;
+ this._filterSet = null;
+ }
+
+ this._group = group;
+
+ if (group) {
+ group = (0, _group2.default)(group);
+ this._filterSet = getFilterSet(group);
+ this._filterVar = (0, _group2.default)(group).var("filter");
+ var sub = this._filterVar.on("change", function (e) {
+ _this._eventRelay.trigger("change", e, _this);
+ });
+ this._varOnChangeSub = sub;
+ }
+ }
+
+ /**
+ * Combine the given `extraInfo` (if any) with the handle's default
+ * `_extraInfo` (if any).
+ * @private
+ */
+
+ }, {
+ key: "_mergeExtraInfo",
+ value: function _mergeExtraInfo(extraInfo) {
+ return util.extend({}, this._extraInfo ? this._extraInfo : null, extraInfo ? extraInfo : null);
+ }
+
+ /**
+ * Close the handle. This clears this handle's contribution to the filter set,
+ * and unsubscribes all event listeners.
+ */
+
+ }, {
+ key: "close",
+ value: function close() {
+ this._emitter.removeAllListeners();
+ this.clear();
+ this.setGroup(null);
+ }
+
+ /**
+ * Clear this handle's contribution to the filter set.
+ *
+ * @param {Object} [extraInfo] - Extra properties to be included on the event
+ * object that's passed to listeners (in addition to any options that were
+ * passed into the `FilterHandle` constructor).
+ */
+
+ }, {
+ key: "clear",
+ value: function clear(extraInfo) {
+ if (!this._filterSet) return;
+ this._filterSet.clear(this._id);
+ this._onChange(extraInfo);
+ }
+
+ /**
+ * Set this handle's contribution to the filter set. This array should consist
+ * of the keys of the rows that _should_ be displayed; any keys that are not
+ * present in the array will be considered _filtered out_. Note that multiple
+ * `FilterHandle` instances in the group may each contribute an array of keys,
+ * and only those keys that appear in _all_ of the arrays make it through the
+ * filter.
+ *
+ * @param {string[]} keys - Empty array, or array of keys. To clear the
+ * filter, don't pass an empty array; instead, use the
+ * {@link FilterHandle#clear} method.
+ * @param {Object} [extraInfo] - Extra properties to be included on the event
+ * object that's passed to listeners (in addition to any options that were
+ * passed into the `FilterHandle` constructor).
+ */
+
+ }, {
+ key: "set",
+ value: function set(keys, extraInfo) {
+ if (!this._filterSet) return;
+ this._filterSet.update(this._id, keys);
+ this._onChange(extraInfo);
+ }
+
+ /**
+ * @return {string[]|null} - Either: 1) an array of keys that made it through
+ * all of the `FilterHandle` instances, or, 2) `null`, which means no filter
+ * is being applied (all data should be displayed).
+ */
+
+ }, {
+ key: "on",
+
+
+ /**
+ * Subscribe to events on this `FilterHandle`.
+ *
+ * @param {string} eventType - Indicates the type of events to listen to.
+ * Currently, only `"change"` is supported.
+ * @param {FilterHandle~listener} listener - The callback function that
+ * will be invoked when the event occurs.
+ * @return {string} - A token to pass to {@link FilterHandle#off} to cancel
+ * this subscription.
+ */
+ value: function on(eventType, listener) {
+ return this._emitter.on(eventType, listener);
+ }
+
+ /**
+ * Cancel event subscriptions created by {@link FilterHandle#on}.
+ *
+ * @param {string} eventType - The type of event to unsubscribe.
+ * @param {string|FilterHandle~listener} listener - Either the callback
+ * function previously passed into {@link FilterHandle#on}, or the
+ * string that was returned from {@link FilterHandle#on}.
+ */
+
+ }, {
+ key: "off",
+ value: function off(eventType, listener) {
+ return this._emitter.off(eventType, listener);
+ }
+ }, {
+ key: "_onChange",
+ value: function _onChange(extraInfo) {
+ if (!this._filterSet) return;
+ this._filterVar.set(this._filterSet.value, this._mergeExtraInfo(extraInfo));
+ }
+
+ /**
+ * @callback FilterHandle~listener
+ * @param {Object} event - An object containing details of the event. For
+ * `"change"` events, this includes the properties `value` (the new
+ * value of the filter set, or `null` if no filter set is active),
+ * `oldValue` (the previous value of the filter set), and `sender` (the
+ * `FilterHandle` instance that made the change).
+ */
+
+ /**
+ * @event FilterHandle#change
+ * @type {object}
+ * @property {object} value - The new value of the filter set, or `null`
+ * if no filter set is active.
+ * @property {object} oldValue - The previous value of the filter set.
+ * @property {FilterHandle} sender - The `FilterHandle` instance that
+ * changed the value.
+ */
+
+ }, {
+ key: "filteredKeys",
+ get: function get() {
+ return this._filterSet ? this._filterSet.value : null;
+ }
+ }]);
+
+ return FilterHandle;
+}();
+
+},{"./events":1,"./filterset":3,"./group":4,"./util":11}],3:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) [...]
+
+var _util = require("./util");
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function naturalComparator(a, b) {
+ if (a === b) {
+ return 0;
+ } else if (a < b) {
+ return -1;
+ } else if (a > b) {
+ return 1;
+ }
+}
+
+/**
+ * @private
+ */
+
+var FilterSet = function () {
+ function FilterSet() {
+ _classCallCheck(this, FilterSet);
+
+ this.reset();
+ }
+
+ _createClass(FilterSet, [{
+ key: "reset",
+ value: function reset() {
+ // Key: handle ID, Value: array of selected keys, or null
+ this._handles = {};
+ // Key: key string, Value: count of handles that include it
+ this._keys = {};
+ this._value = null;
+ this._activeHandles = 0;
+ }
+ }, {
+ key: "update",
+ value: function update(handleId, keys) {
+ if (keys !== null) {
+ keys = keys.slice(0); // clone before sorting
+ keys.sort(naturalComparator);
+ }
+
+ var _diffSortedLists = (0, _util.diffSortedLists)(this._handles[handleId], keys),
+ added = _diffSortedLists.added,
+ removed = _diffSortedLists.removed;
+
+ this._handles[handleId] = keys;
+
+ for (var i = 0; i < added.length; i++) {
+ this._keys[added[i]] = (this._keys[added[i]] || 0) + 1;
+ }
+ for (var _i = 0; _i < removed.length; _i++) {
+ this._keys[removed[_i]]--;
+ }
+
+ this._updateValue(keys);
+ }
+
+ /**
+ * @param {string[]} keys Sorted array of strings that indicate
+ * a superset of possible keys.
+ * @private
+ */
+
+ }, {
+ key: "_updateValue",
+ value: function _updateValue() {
+ var keys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this._allKeys;
+
+ var handleCount = Object.keys(this._handles).length;
+ if (handleCount === 0) {
+ this._value = null;
+ } else {
+ this._value = [];
+ for (var i = 0; i < keys.length; i++) {
+ var count = this._keys[keys[i]];
+ if (count === handleCount) {
+ this._value.push(keys[i]);
+ }
+ }
+ }
+ }
+ }, {
+ key: "clear",
+ value: function clear(handleId) {
+ if (typeof this._handles[handleId] === "undefined") {
+ return;
+ }
+
+ var keys = this._handles[handleId];
+ if (!keys) {
+ keys = [];
+ }
+
+ for (var i = 0; i < keys.length; i++) {
+ this._keys[keys[i]]--;
+ }
+ delete this._handles[handleId];
+
+ this._updateValue();
+ }
+ }, {
+ key: "value",
+ get: function get() {
+ return this._value;
+ }
+ }, {
+ key: "_allKeys",
+ get: function get() {
+ var allKeys = Object.keys(this._keys);
+ allKeys.sort(naturalComparator);
+ return allKeys;
+ }
+ }]);
+
+ return FilterSet;
+}();
+
+exports.default = FilterSet;
+
+},{"./util":11}],4:[function(require,module,exports){
+(function (global){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) [...]
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+exports.default = group;
+
+var _var2 = require("./var");
+
+var _var3 = _interopRequireDefault(_var2);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+// Use a global so that multiple copies of crosstalk.js can be loaded and still
+// have groups behave as singletons across all copies.
+global.__crosstalk_groups = global.__crosstalk_groups || {};
+var groups = global.__crosstalk_groups;
+
+function group(groupName) {
+ if (groupName && typeof groupName === "string") {
+ if (!groups.hasOwnProperty(groupName)) {
+ groups[groupName] = new Group(groupName);
+ }
+ return groups[groupName];
+ } else if ((typeof groupName === "undefined" ? "undefined" : _typeof(groupName)) === "object" && groupName._vars && groupName.var) {
+ // Appears to already be a group object
+ return groupName;
+ } else if (Array.isArray(groupName) && groupName.length == 1 && typeof groupName[0] === "string") {
+ return group(groupName[0]);
+ } else {
+ throw new Error("Invalid groupName argument");
+ }
+}
+
+var Group = function () {
+ function Group(name) {
+ _classCallCheck(this, Group);
+
+ this.name = name;
+ this._vars = {};
+ }
+
+ _createClass(Group, [{
+ key: "var",
+ value: function _var(name) {
+ if (!name || typeof name !== "string") {
+ throw new Error("Invalid var name");
+ }
+
+ if (!this._vars.hasOwnProperty(name)) this._vars[name] = new _var3.default(this, name);
+ return this._vars[name];
+ }
+ }, {
+ key: "has",
+ value: function has(name) {
+ if (!name || typeof name !== "string") {
+ throw new Error("Invalid var name");
+ }
+
+ return this._vars.hasOwnProperty(name);
+ }
+ }]);
+
+ return Group;
+}();
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./var":12}],5:[function(require,module,exports){
+(function (global){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _group = require("./group");
+
+var _group2 = _interopRequireDefault(_group);
+
+var _selection = require("./selection");
+
+var _filter = require("./filter");
+
+require("./input");
+
+require("./input_selectize");
+
+require("./input_checkboxgroup");
+
+require("./input_slider");
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+var defaultGroup = (0, _group2.default)("default");
+
+function var_(name) {
+ return defaultGroup.var(name);
+}
+
+function has(name) {
+ return defaultGroup.has(name);
+}
+
+if (global.Shiny) {
+ global.Shiny.addCustomMessageHandler("update-client-value", function (message) {
+ if (typeof message.group === "string") {
+ (0, _group2.default)(message.group).var(message.name).set(message.value);
+ } else {
+ var_(message.name).set(message.value);
+ }
+ });
+}
+
+var crosstalk = {
+ group: _group2.default,
+ var: var_,
+ has: has,
+ SelectionHandle: _selection.SelectionHandle,
+ FilterHandle: _filter.FilterHandle
+};
+
+/**
+ * @namespace crosstalk
+ */
+exports.default = crosstalk;
+
+global.crosstalk = crosstalk;
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./filter":2,"./group":4,"./input":6,"./input_checkboxgroup":7,"./input_selectize":8,"./input_slider":9,"./selection":10}],6:[function(require,module,exports){
+(function (global){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.register = register;
+var $ = global.jQuery;
+
+var bindings = {};
+
+function register(reg) {
+ bindings[reg.className] = reg;
+ if (global.document && global.document.readyState !== "complete") {
+ $(function () {
+ bind();
+ });
+ } else if (global.document) {
+ setTimeout(bind, 100);
+ }
+}
+
+function bind() {
+ Object.keys(bindings).forEach(function (className) {
+ var binding = bindings[className];
+ $("." + binding.className).not(".crosstalk-input-bound").each(function (i, el) {
+ bindInstance(binding, el);
+ });
+ });
+}
+
+// Escape jQuery identifier
+function $escape(val) {
+ return val.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
+}
+
+function bindEl(el) {
+ var $el = $(el);
+ Object.keys(bindings).forEach(function (className) {
+ if ($el.hasClass(className) && !$el.hasClass("crosstalk-input-bound")) {
+ var binding = bindings[className];
+ bindInstance(binding, el);
+ }
+ });
+}
+
+function bindInstance(binding, el) {
+ var jsonEl = $(el).find("script[type='application/json'][data-for='" + $escape(el.id) + "']");
+ var data = JSON.parse(jsonEl[0].innerText);
+
+ var instance = binding.factory(el, data);
+ $(el).data("crosstalk-instance", instance);
+ $(el).addClass("crosstalk-input-bound");
+}
+
+if (global.Shiny) {
+ (function () {
+ var inputBinding = new global.Shiny.InputBinding();
+ var $ = global.jQuery;
+ $.extend(inputBinding, {
+ find: function find(scope) {
+ return $(scope).find(".crosstalk-input");
+ },
+ initialize: function initialize(el) {
+ if (!$(el).hasClass("crosstalk-input-bound")) {
+ bindEl(el);
+ }
+ },
+ getId: function getId(el) {
+ return el.id;
+ },
+ getValue: function getValue(el) {},
+ setValue: function setValue(el, value) {},
+ receiveMessage: function receiveMessage(el, data) {},
+ subscribe: function subscribe(el, callback) {
+ $(el).data("crosstalk-instance").resume();
+ },
+ unsubscribe: function unsubscribe(el) {
+ $(el).data("crosstalk-instance").suspend();
+ }
+ });
+ global.Shiny.inputBindings.register(inputBinding, "crosstalk.inputBinding");
+ })();
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{}],7:[function(require,module,exports){
+(function (global){
+"use strict";
+
+var _input = require("./input");
+
+var input = _interopRequireWildcard(_input);
+
+var _filter = require("./filter");
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+var $ = global.jQuery;
+
+input.register({
+ className: "crosstalk-input-checkboxgroup",
+
+ factory: function factory(el, data) {
+ /*
+ * map: {"groupA": ["keyA", "keyB", ...], ...}
+ * group: "ct-groupname"
+ */
+ var ctHandle = new _filter.FilterHandle(data.group);
+
+ var lastKnownKeys = void 0;
+ var $el = $(el);
+ $el.on("change", "input[type='checkbox']", function () {
+ var checked = $el.find("input[type='checkbox']:checked");
+ if (checked.length === 0) {
+ lastKnownKeys = null;
+ ctHandle.clear();
+ } else {
+ (function () {
+ var keys = {};
+ checked.each(function () {
+ data.map[this.value].forEach(function (key) {
+ keys[key] = true;
+ });
+ });
+ var keyArray = Object.keys(keys);
+ keyArray.sort();
+ lastKnownKeys = keyArray;
+ ctHandle.set(keyArray);
+ })();
+ }
+ });
+
+ return {
+ suspend: function suspend() {
+ ctHandle.clear();
+ },
+ resume: function resume() {
+ if (lastKnownKeys) ctHandle.set(lastKnownKeys);
+ }
+ };
+ }
+});
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./filter":2,"./input":6}],8:[function(require,module,exports){
+(function (global){
+"use strict";
+
+var _input = require("./input");
+
+var input = _interopRequireWildcard(_input);
+
+var _util = require("./util");
+
+var util = _interopRequireWildcard(_util);
+
+var _filter = require("./filter");
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+var $ = global.jQuery;
+
+input.register({
+ className: "crosstalk-input-select",
+
+ factory: function factory(el, data) {
+ /*
+ * items: {value: [...], label: [...]}
+ * map: {"groupA": ["keyA", "keyB", ...], ...}
+ * group: "ct-groupname"
+ */
+
+ var first = [{ value: "", label: "(All)" }];
+ var items = util.dataframeToD3(data.items);
+ var opts = {
+ options: first.concat(items),
+ valueField: "value",
+ labelField: "label",
+ searchField: "label"
+ };
+
+ var select = $(el).find("select")[0];
+
+ var selectize = $(select).selectize(opts)[0].selectize;
+
+ var ctHandle = new _filter.FilterHandle(data.group);
+
+ var lastKnownKeys = void 0;
+ selectize.on("change", function () {
+ if (selectize.items.length === 0) {
+ lastKnownKeys = null;
+ ctHandle.clear();
+ } else {
+ (function () {
+ var keys = {};
+ selectize.items.forEach(function (group) {
+ data.map[group].forEach(function (key) {
+ keys[key] = true;
+ });
+ });
+ var keyArray = Object.keys(keys);
+ keyArray.sort();
+ lastKnownKeys = keyArray;
+ ctHandle.set(keyArray);
+ })();
+ }
+ });
+
+ return {
+ suspend: function suspend() {
+ ctHandle.clear();
+ },
+ resume: function resume() {
+ if (lastKnownKeys) ctHandle.set(lastKnownKeys);
+ }
+ };
+ }
+});
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./filter":2,"./input":6,"./util":11}],9:[function(require,module,exports){
+(function (global){
+"use strict";
+
+var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr [...]
+
+var _input = require("./input");
+
+var input = _interopRequireWildcard(_input);
+
+var _filter = require("./filter");
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+var $ = global.jQuery;
+var strftime = global.strftime;
+
+input.register({
+ className: "crosstalk-input-slider",
+
+ factory: function factory(el, data) {
+ /*
+ * map: {"groupA": ["keyA", "keyB", ...], ...}
+ * group: "ct-groupname"
+ */
+ var ctHandle = new _filter.FilterHandle(data.group);
+
+ var opts = {};
+ var $el = $(el).find("input");
+ var dataType = $el.data("data-type");
+ var timeFormat = $el.data("time-format");
+ var timeFormatter = void 0;
+
+ // Set up formatting functions
+ if (dataType === "date") {
+ timeFormatter = strftime.utc();
+ opts.prettify = function (num) {
+ return timeFormatter(timeFormat, new Date(num));
+ };
+ } else if (dataType === "datetime") {
+ var timezone = $el.data("timezone");
+ if (timezone) timeFormatter = strftime.timezone(timezone);else timeFormatter = strftime;
+
+ opts.prettify = function (num) {
+ return timeFormatter(timeFormat, new Date(num));
+ };
+ }
+
+ $el.ionRangeSlider(opts);
+
+ function getValue() {
+ var result = $el.data("ionRangeSlider").result;
+
+ // Function for converting numeric value from slider to appropriate type.
+ var convert = void 0;
+ var dataType = $el.data("data-type");
+ if (dataType === "date") {
+ convert = function convert(val) {
+ return formatDateUTC(new Date(+val));
+ };
+ } else if (dataType === "datetime") {
+ convert = function convert(val) {
+ // Convert ms to s
+ return +val / 1000;
+ };
+ } else {
+ convert = function convert(val) {
+ return +val;
+ };
+ }
+
+ if ($el.data("ionRangeSlider").options.type === "double") {
+ return [convert(result.from), convert(result.to)];
+ } else {
+ return convert(result.from);
+ }
+ }
+
+ var lastKnownKeys = null;
+
+ $el.on("change.crosstalkSliderInput", function (event) {
+ if (!$el.data("updating") && !$el.data("animating")) {
+ var _getValue = getValue(),
+ _getValue2 = _slicedToArray(_getValue, 2),
+ from = _getValue2[0],
+ to = _getValue2[1];
+
+ var keys = [];
+ for (var i = 0; i < data.values.length; i++) {
+ var val = data.values[i];
+ if (val >= from && val <= to) {
+ keys.push(data.keys[i]);
+ }
+ }
+ keys.sort();
+ ctHandle.set(keys);
+ lastKnownKeys = keys;
+ }
+ });
+
+ // let $el = $(el);
+ // $el.on("change", "input[type="checkbox"]", function() {
+ // let checked = $el.find("input[type="checkbox"]:checked");
+ // if (checked.length === 0) {
+ // ctHandle.clear();
+ // } else {
+ // let keys = {};
+ // checked.each(function() {
+ // data.map[this.value].forEach(function(key) {
+ // keys[key] = true;
+ // });
+ // });
+ // let keyArray = Object.keys(keys);
+ // keyArray.sort();
+ // ctHandle.set(keyArray);
+ // }
+ // });
+
+ return {
+ suspend: function suspend() {
+ ctHandle.clear();
+ },
+ resume: function resume() {
+ if (lastKnownKeys) ctHandle.set(lastKnownKeys);
+ }
+ };
+ }
+});
+
+// Convert a number to a string with leading zeros
+function padZeros(n, digits) {
+ var str = n.toString();
+ while (str.length < digits) {
+ str = "0" + str;
+ }return str;
+}
+
+// Given a Date object, return a string in yyyy-mm-dd format, using the
+// UTC date. This may be a day off from the date in the local time zone.
+function formatDateUTC(date) {
+ if (date instanceof Date) {
+ return date.getUTCFullYear() + "-" + padZeros(date.getUTCMonth() + 1, 2) + "-" + padZeros(date.getUTCDate(), 2);
+ } else {
+ return null;
+ }
+}
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./filter":2,"./input":6}],10:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.SelectionHandle = undefined;
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) [...]
+
+var _events = require("./events");
+
+var _events2 = _interopRequireDefault(_events);
+
+var _group = require("./group");
+
+var _group2 = _interopRequireDefault(_group);
+
+var _util = require("./util");
+
+var util = _interopRequireWildcard(_util);
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var SelectionHandle = exports.SelectionHandle = function () {
+
+ /**
+ * @classdesc
+ * Use this class to read and write (and listen for changes to) the selection
+ * for a Crosstalk group. This is intended to be used for linked brushing.
+ *
+ * If two (or more) `SelectionHandle` instances in the same webpage share the
+ * same group name, they will share the same state. Setting the selection using
+ * one `SelectionHandle` instance will result in the `value` property instantly
+ * changing across the others, and `"change"` event listeners on all instances
+ * (including the one that initiated the sending) will fire.
+ *
+ * @param {string} [group] - The name of the Crosstalk group, or if none,
+ * null or undefined (or any other falsy value). This can be changed later
+ * via the [SelectionHandle#setGroup](#setGroup) method.
+ * @param {Object} [extraInfo] - An object whose properties will be copied to
+ * the event object whenever an event is emitted.
+ */
+ function SelectionHandle() {
+ var group = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+ var extraInfo = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+
+ _classCallCheck(this, SelectionHandle);
+
+ this._eventRelay = new _events2.default();
+ this._emitter = new util.SubscriptionTracker(this._eventRelay);
+
+ // Name of the group we're currently tracking, if any. Can change over time.
+ this._group = null;
+ // The Var we're currently tracking, if any. Can change over time.
+ this._var = null;
+ // The event handler subscription we currently have on var.on("change").
+ this._varOnChangeSub = null;
+
+ this._extraInfo = util.extend({ sender: this }, extraInfo);
+
+ this.setGroup(group);
+ }
+
+ /**
+ * Changes the Crosstalk group membership of this SelectionHandle. The group
+ * being switched away from (if any) will not have its selection value
+ * modified as a result of calling `setGroup`, even if this handle was the
+ * most recent handle to set the selection of the group.
+ *
+ * The group being switched to (if any) will also not have its selection value
+ * modified as a result of calling `setGroup`. If you want to set the
+ * selection value of the new group, call `set` explicitly.
+ *
+ * @param {string} group - The name of the Crosstalk group, or null (or
+ * undefined) to clear the group.
+ */
+
+
+ _createClass(SelectionHandle, [{
+ key: "setGroup",
+ value: function setGroup(group) {
+ var _this = this;
+
+ // If group is unchanged, do nothing
+ if (this._group === group) return;
+ // Treat null, undefined, and other falsy values the same
+ if (!this._group && !group) return;
+
+ if (this._var) {
+ this._var.off("change", this._varOnChangeSub);
+ this._var = null;
+ this._varOnChangeSub = null;
+ }
+
+ this._group = group;
+
+ if (group) {
+ this._var = (0, _group2.default)(group).var("selection");
+ var sub = this._var.on("change", function (e) {
+ _this._eventRelay.trigger("change", e, _this);
+ });
+ this._varOnChangeSub = sub;
+ }
+ }
+
+ /**
+ * Retrieves the current selection for the group represented by this
+ * `SelectionHandle`.
+ *
+ * - If no selection is active, then this value will be falsy.
+ * - If a selection is active, but no data points are selected, then this
+ * value will be an empty array.
+ * - If a selection is active, and data points are selected, then the keys
+ * of the selected data points will be present in the array.
+ */
+
+ }, {
+ key: "_mergeExtraInfo",
+
+
+ /**
+ * Combines the given `extraInfo` (if any) with the handle's default
+ * `_extraInfo` (if any).
+ * @private
+ */
+ value: function _mergeExtraInfo(extraInfo) {
+ // Important incidental effect: shallow clone is returned
+ return util.extend({}, this._extraInfo ? this._extraInfo : null, extraInfo ? extraInfo : null);
+ }
+
+ /**
+ * Overwrites the current selection for the group, and raises the `"change"`
+ * event among all of the group's '`SelectionHandle` instances (including
+ * this one).
+ *
+ * @fires SelectionHandle#change
+ * @param {string[]} selectedKeys - Falsy, empty array, or array of keys (see
+ * {@link SelectionHandle#value}).
+ * @param {Object} [extraInfo] - Extra properties to be included on the event
+ * object that's passed to listeners (in addition to any options that were
+ * passed into the `SelectionHandle` constructor).
+ */
+
+ }, {
+ key: "set",
+ value: function set(selectedKeys, extraInfo) {
+ if (this._var) this._var.set(selectedKeys, this._mergeExtraInfo(extraInfo));
+ }
+
+ /**
+ * Overwrites the current selection for the group, and raises the `"change"`
+ * event among all of the group's '`SelectionHandle` instances (including
+ * this one).
+ *
+ * @fires SelectionHandle#change
+ * @param {Object} [extraInfo] - Extra properties to be included on the event
+ * object that's passed to listeners (in addition to any that were passed
+ * into the `SelectionHandle` constructor).
+ */
+
+ }, {
+ key: "clear",
+ value: function clear(extraInfo) {
+ if (this._var) this.set(void 0, this._mergeExtraInfo(extraInfo));
+ }
+
+ /**
+ * Subscribes to events on this `SelectionHandle`.
+ *
+ * @param {string} eventType - Indicates the type of events to listen to.
+ * Currently, only `"change"` is supported.
+ * @param {SelectionHandle~listener} listener - The callback function that
+ * will be invoked when the event occurs.
+ * @return {string} - A token to pass to {@link SelectionHandle#off} to cancel
+ * this subscription.
+ */
+
+ }, {
+ key: "on",
+ value: function on(eventType, listener) {
+ return this._emitter.on(eventType, listener);
+ }
+
+ /**
+ * Cancels event subscriptions created by {@link SelectionHandle#on}.
+ *
+ * @param {string} eventType - The type of event to unsubscribe.
+ * @param {string|SelectionHandle~listener} listener - Either the callback
+ * function previously passed into {@link SelectionHandle#on}, or the
+ * string that was returned from {@link SelectionHandle#on}.
+ */
+
+ }, {
+ key: "off",
+ value: function off(eventType, listener) {
+ return this._emitter.off(eventType, listener);
+ }
+
+ /**
+ * Shuts down the `SelectionHandle` object.
+ *
+ * Removes all event listeners that were added through this handle.
+ */
+
+ }, {
+ key: "close",
+ value: function close() {
+ this._emitter.removeAllListeners();
+ this.setGroup(null);
+ }
+
+ /**
+ * @callback SelectionHandle~listener
+ * @param {Object} event - An object containing details of the event. For
+ * `"change"` events, this includes the properties `value` (the new
+ * value of the selection, or `undefined` if no selection is active),
+ * `oldValue` (the previous value of the selection), and `sender` (the
+ * `SelectionHandle` instance that made the change).
+ */
+
+ /**
+ * @event SelectionHandle#change
+ * @type {object}
+ * @property {object} value - The new value of the selection, or `undefined`
+ * if no selection is active.
+ * @property {object} oldValue - The previous value of the selection.
+ * @property {SelectionHandle} sender - The `SelectionHandle` instance that
+ * changed the value.
+ */
+
+ }, {
+ key: "value",
+ get: function get() {
+ return this._var ? this._var.get() : null;
+ }
+ }]);
+
+ return SelectionHandle;
+}();
+
+},{"./events":1,"./group":4,"./util":11}],11:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) [...]
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+exports.extend = extend;
+exports.checkSorted = checkSorted;
+exports.diffSortedLists = diffSortedLists;
+exports.dataframeToD3 = dataframeToD3;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function extend(target) {
+ for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+ sources[_key - 1] = arguments[_key];
+ }
+
+ for (var i = 0; i < sources.length; i++) {
+ var src = sources[i];
+ if (typeof src === "undefined" || src === null) continue;
+
+ for (var key in src) {
+ if (src.hasOwnProperty(key)) {
+ target[key] = src[key];
+ }
+ }
+ }
+ return target;
+}
+
+function checkSorted(list) {
+ for (var i = 1; i < list.length; i++) {
+ if (list[i] <= list[i - 1]) {
+ throw new Error("List is not sorted or contains duplicate");
+ }
+ }
+}
+
+function diffSortedLists(a, b) {
+ var i_a = 0;
+ var i_b = 0;
+
+ if (!a) a = [];
+ if (!b) b = [];
+
+ var a_only = [];
+ var b_only = [];
+
+ checkSorted(a);
+ checkSorted(b);
+
+ while (i_a < a.length && i_b < b.length) {
+ if (a[i_a] === b[i_b]) {
+ i_a++;
+ i_b++;
+ } else if (a[i_a] < b[i_b]) {
+ a_only.push(a[i_a++]);
+ } else {
+ b_only.push(b[i_b++]);
+ }
+ }
+
+ if (i_a < a.length) a_only = a_only.concat(a.slice(i_a));
+ if (i_b < b.length) b_only = b_only.concat(b.slice(i_b));
+ return {
+ removed: a_only,
+ added: b_only
+ };
+}
+
+// Convert from wide: { colA: [1,2,3], colB: [4,5,6], ... }
+// to long: [ {colA: 1, colB: 4}, {colA: 2, colB: 5}, ... ]
+function dataframeToD3(df) {
+ var names = [];
+ var length = void 0;
+ for (var name in df) {
+ if (df.hasOwnProperty(name)) names.push(name);
+ if (_typeof(df[name]) !== "object" || typeof df[name].length === "undefined") {
+ throw new Error("All fields must be arrays");
+ } else if (typeof length !== "undefined" && length !== df[name].length) {
+ throw new Error("All fields must be arrays of the same length");
+ }
+ length = df[name].length;
+ }
+ var results = [];
+ var item = void 0;
+ for (var row = 0; row < length; row++) {
+ item = {};
+ for (var col = 0; col < names.length; col++) {
+ item[names[col]] = df[names[col]][row];
+ }
+ results.push(item);
+ }
+ return results;
+}
+
+/**
+ * Keeps track of all event listener additions/removals and lets all active
+ * listeners be removed with a single operation.
+ *
+ * @private
+ */
+
+var SubscriptionTracker = exports.SubscriptionTracker = function () {
+ function SubscriptionTracker(emitter) {
+ _classCallCheck(this, SubscriptionTracker);
+
+ this._emitter = emitter;
+ this._subs = {};
+ }
+
+ _createClass(SubscriptionTracker, [{
+ key: "on",
+ value: function on(eventType, listener) {
+ var sub = this._emitter.on(eventType, listener);
+ this._subs[sub] = eventType;
+ return sub;
+ }
+ }, {
+ key: "off",
+ value: function off(eventType, listener) {
+ var sub = this._emitter.off(eventType, listener);
+ if (sub) {
+ delete this._subs[sub];
+ }
+ return sub;
+ }
+ }, {
+ key: "removeAllListeners",
+ value: function removeAllListeners() {
+ var _this = this;
+
+ var current_subs = this._subs;
+ this._subs = {};
+ Object.keys(current_subs).forEach(function (sub) {
+ _this._emitter.off(current_subs[sub], sub);
+ });
+ }
+ }]);
+
+ return SubscriptionTracker;
+}();
+
+},{}],12:[function(require,module,exports){
+(function (global){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) [...]
+
+var _events = require("./events");
+
+var _events2 = _interopRequireDefault(_events);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var Var = function () {
+ function Var(group, name, /*optional*/value) {
+ _classCallCheck(this, Var);
+
+ this._group = group;
+ this._name = name;
+ this._value = value;
+ this._events = new _events2.default();
+ }
+
+ _createClass(Var, [{
+ key: "get",
+ value: function get() {
+ return this._value;
+ }
+ }, {
+ key: "set",
+ value: function set(value, /*optional*/event) {
+ if (this._value === value) {
+ // Do nothing; the value hasn't changed
+ return;
+ }
+ var oldValue = this._value;
+ this._value = value;
+ // Alert JavaScript listeners that the value has changed
+ var evt = {};
+ if (event && (typeof event === "undefined" ? "undefined" : _typeof(event)) === "object") {
+ for (var k in event) {
+ if (event.hasOwnProperty(k)) evt[k] = event[k];
+ }
+ }
+ evt.oldValue = oldValue;
+ evt.value = value;
+ this._events.trigger("change", evt, this);
+
+ // TODO: Make this extensible, to let arbitrary back-ends know that
+ // something has changed
+ if (global.Shiny && global.Shiny.onInputChange) {
+ global.Shiny.onInputChange(".clientValue-" + (this._group.name !== null ? this._group.name + "-" : "") + this._name, typeof value === "undefined" ? null : value);
+ }
+ }
+ }, {
+ key: "on",
+ value: function on(eventType, listener) {
+ return this._events.on(eventType, listener);
+ }
+ }, {
+ key: "off",
+ value: function off(eventType, listener) {
+ return this._events.off(eventType, listener);
+ }
+ }]);
+
+ return Var;
+}();
+
+exports.default = Var;
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./events":1}]},{},[5])
+//# sourceMappingURL=crosstalk.js.map
diff --git a/inst/www/js/crosstalk.js.map b/inst/www/js/crosstalk.js.map
new file mode 100644
index 0000000..508b24f
--- /dev/null
+++ b/inst/www/js/crosstalk.js.map
@@ -0,0 +1,37 @@
+{
+ "version": 3,
+ "sources": [
+ "node_modules/browser-pack/_prelude.js",
+ "javascript/src/events.js",
+ "javascript/src/filter.js",
+ "javascript/src/filterset.js",
+ "javascript/src/group.js",
+ "javascript/src/index.js",
+ "javascript/src/input.js",
+ "javascript/src/input_checkboxgroup.js",
+ "javascript/src/input_selectize.js",
+ "javascript/src/input_slider.js",
+ "javascript/src/selection.js",
+ "javascript/src/util.js",
+ "javascript/src/var.js"
+ ],
+ "names": [],
+ "mappings": "AAAA;;;;;;;;;;;ICAqB,M;AACnB,oBAAc;AAAA;;AACZ,SAAK,MAAL,GAAc,EAAd;AACA,SAAK,IAAL,GAAY,CAAZ;AACD;;;;uBAEE,S,EAAW,Q,EAAU;AACtB,UAAI,OAAO,KAAK,MAAL,CAAY,SAAZ,CAAX;AACA,UAAI,CAAC,IAAL,EAAW;AACT,eAAO,KAAK,MAAL,CAAY,SAAZ,IAAyB,EAAhC;AACD;AACD,UAAI,MAAM,QAAS,KAAK,IAAL,EAAnB;AACA,WAAK,GAAL,IAAY,QAAZ;AACA,aAAO,GAAP;AACD;;AAED;;;;wBACI,S,EAAW,Q,EAAU;AACvB,UAAI,OAAO,KAAK,MAAL,CAAY,SAAZ,CAAX;AACA,UAAI,OAAO,QAAP,KAAqB,UAAzB,EAAqC;AACnC,aAAK,IAAI,GAAT,IAAgB,IAAhB,EAAsB;AACpB,cAAI,KAAK,c [...]
+ "file": "generated.js",
+ "sourceRoot": "",
+ "sourcesContent": [
+ "(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})",
+ "export default class Events {\n constructor() {\n this._types = {};\n this._seq = 0;\n }\n\n on(eventType, listener) {\n let subs = this._types[eventType];\n if (!subs) {\n subs = this._types[eventType] = {};\n }\n let sub = \"sub\" + (this._seq++);\n subs[sub] = listener;\n return sub;\n }\n\n // Returns false if no match, or string for sub name if matched\n off(eventType, listener) {\n let subs = this._types[eventType];\n if (typeof(listene [...]
+ "import Events from \"./events\";\nimport FilterSet from \"./filterset\";\nimport grp from \"./group\";\nimport * as util from \"./util\";\n\nfunction getFilterSet(group) {\n let fsVar = group.var(\"filterset\");\n let result = fsVar.get();\n if (!result) {\n result = new FilterSet();\n fsVar.set(result);\n }\n return result;\n}\n\nlet id = 1;\nfunction nextId() {\n return id++;\n}\n\nexport class FilterHandle {\n /**\n * @classdesc\n * Use this class to contribute t [...]
+ "import { diffSortedLists } from \"./util\";\n\nfunction naturalComparator(a, b) {\n if (a === b) {\n return 0;\n } else if (a < b) {\n return -1;\n } else if (a > b) {\n return 1;\n }\n}\n\n/**\n * @private\n */\nexport default class FilterSet {\n constructor() {\n this.reset();\n }\n\n reset() {\n // Key: handle ID, Value: array of selected keys, or null\n this._handles = {};\n // Key: key string, Value: count of handles that include it\n this._keys = [...]
+ "import Var from \"./var\";\n\n// Use a global so that multiple copies of crosstalk.js can be loaded and still\n// have groups behave as singletons across all copies.\nglobal.__crosstalk_groups = global.__crosstalk_groups || {};\nlet groups = global.__crosstalk_groups;\n\nexport default function group(groupName) {\n if (groupName && typeof(groupName) === \"string\") {\n if (!groups.hasOwnProperty(groupName)) {\n groups[groupName] = new Group(groupName);\n }\n return gr [...]
+ "import group from \"./group\";\nimport { SelectionHandle } from \"./selection\";\nimport { FilterHandle } from \"./filter\";\nimport \"./input\";\nimport \"./input_selectize\";\nimport \"./input_checkboxgroup\";\nimport \"./input_slider\";\n\nconst defaultGroup = group(\"default\");\n\nfunction var_(name) {\n return defaultGroup.var(name);\n}\n\nfunction has(name) {\n return defaultGroup.has(name);\n}\n\nif (global.Shiny) {\n global.Shiny.addCustomMessageHandler(\"update-client-v [...]
+ "let $ = global.jQuery;\n\nlet bindings = {};\n\nexport function register(reg) {\n bindings[reg.className] = reg;\n if (global.document && global.document.readyState !== \"complete\") {\n $(() => {\n bind();\n });\n } else if (global.document) {\n setTimeout(bind, 100);\n }\n}\n\nfunction bind() {\n Object.keys(bindings).forEach(function(className) {\n let binding = bindings[className];\n $(\".\" + binding.className).not(\".crosstalk-input-bound\").each(funct [...]
+ "import * as input from \"./input\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\n\ninput.register({\n className: \"crosstalk-input-checkboxgroup\",\n\n factory: function(el, data) {\n /*\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n let ctHandle = new FilterHandle(data.group);\n\n let lastKnownKeys;\n let $el = $(el);\n $el.on(\"change\", \"input[type='checkbox']\", function() {\n [...]
+ "import * as input from \"./input\";\nimport * as util from \"./util\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\n\ninput.register({\n className: \"crosstalk-input-select\",\n\n factory: function(el, data) {\n /*\n * items: {value: [...], label: [...]}\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n\n let first = [{value: \"\", label: \"(All)\"}];\n let items = util.dataframeToD3(data.ite [...]
+ "import * as input from \"./input\";\nimport { FilterHandle } from \"./filter\";\n\nlet $ = global.jQuery;\nlet strftime = global.strftime;\n\ninput.register({\n className: \"crosstalk-input-slider\",\n\n factory: function(el, data) {\n /*\n * map: {\"groupA\": [\"keyA\", \"keyB\", ...], ...}\n * group: \"ct-groupname\"\n */\n let ctHandle = new FilterHandle(data.group);\n\n let opts = {};\n let $el = $(el).find(\"input\");\n let dataType = $el.data(\"dat [...]
+ "import Events from \"./events\";\nimport grp from \"./group\";\nimport * as util from \"./util\";\n\nexport class SelectionHandle {\n\n /**\n * @classdesc\n * Use this class to read and write (and listen for changes to) the selection\n * for a Crosstalk group. This is intended to be used for linked brushing.\n *\n * If two (or more) `SelectionHandle` instances in the same webpage share the\n * same group name, they will share the same state. Setting the selection using\ [...]
+ "export function extend(target, ...sources) {\n for (let i = 0; i < sources.length; i++) {\n let src = sources[i];\n if (typeof(src) === \"undefined\" || src === null)\n continue;\n\n for (let key in src) {\n if (src.hasOwnProperty(key)) {\n target[key] = src[key];\n }\n }\n }\n return target;\n}\n\nexport function checkSorted(list) {\n for (let i = 1; i < list.length; i++) {\n if (list[i] <= list[i-1]) {\n throw new Error(\"List is not s [...]
+ "import Events from \"./events\";\n\nexport default class Var {\n constructor(group, name, /*optional*/ value) {\n this._group = group;\n this._name = name;\n this._value = value;\n this._events = new Events();\n }\n\n get() {\n return this._value;\n }\n\n set(value, /*optional*/ event) {\n if (this._value === value) {\n // Do nothing; the value hasn't changed\n return;\n }\n let oldValue = this._value;\n this._value = value;\n // Alert Ja [...]
+ ]
+}
\ No newline at end of file
diff --git a/inst/www/js/crosstalk.min.js b/inst/www/js/crosstalk.min.js
new file mode 100644
index 0000000..55262e8
--- /dev/null
+++ b/inst/www/js/crosstalk.min.js
@@ -0,0 +1,2 @@
+!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){"use strict";function d(a,b){if(!( [...]
+//# sourceMappingURL=crosstalk.min.js.map
\ No newline at end of file
diff --git a/inst/www/js/crosstalk.min.js.map b/inst/www/js/crosstalk.min.js.map
new file mode 100644
index 0000000..7b3f2e9
--- /dev/null
+++ b/inst/www/js/crosstalk.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["node_modules/browser-pack/_prelude.js","javascript/src/events.js","javascript/src/filter.js","javascript/src/filterset.js","javascript/src/group.js","javascript/src/index.js","javascript/src/input.js","javascript/src/input_checkboxgroup.js","javascript/src/input_selectize.js","javascript/src/input_slider.js","javascript/src/selection.js","javascript/src/util.js","javascript/src/var.js"],"names":["e","t","n","r","s","o","u","a","require","i","f","Error","code","l" [...]
\ No newline at end of file
diff --git a/man/ClientValue.Rd b/man/ClientValue.Rd
new file mode 100644
index 0000000..d780bac
--- /dev/null
+++ b/man/ClientValue.Rd
@@ -0,0 +1,62 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/crosstalk.R
+\docType{class}
+\name{ClientValue}
+\alias{ClientValue}
+\title{ClientValue object}
+\format{An \code{\link{R6Class}} generator object}
+\usage{
+ClientValue
+}
+\description{
+An object that can be used in a \href{http://shiny.rstudio.com}{Shiny} server
+function to get or set a crosstalk variable that exists on the client. The
+client copy of the variable is the canonical copy, so there is no direct
+"set" method that immediately changes the value; instead, there is a
+\code{sendUpdate} method that sends a request to the browser to change the
+value, which will then cause the new value to be relayed back to the server.
+}
+\section{Methods}{
+
+\describe{
+ \item{\code{initialize(name, group = "default", session = shiny::getDefaultReactiveDomain())}}{
+ Create a new ClientValue object to reflect the crosstalk variable
+ specified by \code{group} and \code{name}. The \code{session} indicates
+ which Shiny session to connect to, and defaults to the current session.
+ }
+ \item{\code{get()}}{
+Read the value. This is a reactive operation akin to reading a reactive
+value, and so can only be done in a reactive context (e.g. in a
+\code{\link[shiny]{reactive}}, \code{\link[shiny]{observe}}, or
+\code{\link[shiny]{isolate}} block).
+ }
+ \item{\code{sendUpdate(value)}}{
+ Send a message to the browser asking it to update the crosstalk var to
+ the given value. This update does not happen synchronously, that is, a
+ call to \code{get()} immediately following \code{sendUpdate(value)} will
+ not reflect the new value. The value must be serializable as JSON using
+ jsonlite.
+ }
+}
+}
+\examples{
+library(shiny)
+
+server <- function(input, output, session) {
+ cv <- ClientValue$new("var1", "group1")
+
+ r <- reactive({
+ # Don't proceed unless cv$get() is a non-NULL value
+ validate(need(cv$get(), message = FALSE))
+
+ runif(cv$get())
+ })
+
+ observeEvent(input$click, {
+ cv$sendUpdate(NULL)
+ })
+}
+
+}
+\keyword{datasets}
+
diff --git a/man/SharedData.Rd b/man/SharedData.Rd
new file mode 100644
index 0000000..fc43888
--- /dev/null
+++ b/man/SharedData.Rd
@@ -0,0 +1,106 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/crosstalk.R
+\docType{data}
+\name{SharedData}
+\alias{SharedData}
+\title{An R6 class that represents a shared data frame}
+\format{An object of class \code{R6ClassGenerator} of length 24.}
+\usage{
+SharedData
+}
+\description{
+...or sufficiently data frame-like object. The primary use for
+\code{SharedData} is to be passed to Crosstalk-compatible widgets in place
+of a data frame. Each \code{SharedData$new(...)} call makes a new "group"
+of widgets that link to each other, but not to widgets in other groups.
+You can also use a \code{SharedData} object from Shiny code in order to
+react to filtering and brushing from non-widget visualizations (like ggplot2
+plots).
+}
+\section{Constructor}{
+
+
+\code{SharedData$new(data, key = NULL, group = createUniqueId(4, prefix = "SharedData"))}
+
+\describe{
+ \item{\code{data}}{
+ A data frame-like object, or a Shiny \link[=reactive]{reactive
+ expression} that returns a data frame-like object.
+ }
+ \item{\code{key}}{
+ Character vector or one-sided formula that indicates the name of the
+ column that represents the key or ID of the data frame. These \emph{must}
+ be unique, and ideally will be something intrinsic to the data (a proper
+ ID) rather than a transient property like row index.
+
+ If \code{NULL}, then \code{row.names(data)} will be used.
+ }
+ \item{\code{group}}{
+ The "identity" of the Crosstalk group that widgets will join when you
+ pass them this \code{SharedData} object. In some cases, you will want to
+ have multiple independent \code{SharedData} objects link up to form a
+ single web of widgets that all share selection and filtering state; in
+ those cases, you'll give those \code{SharedData} objects the same group
+ name. (One example: in Shiny, ui.R and server.R might each need their own
+ \code{SharedData} instance, even though they're intended to represent a
+ single group.)
+ }
+}
+}
+
+\section{Methods}{
+
+
+\describe{
+ \item{\code{data(withSelection = FALSE, withFilter = TRUE, withKey = FALSE)}}{
+ Return the data (or read and return the data if the data is a Shiny
+ reactive expression). If \code{withSelection}, add a \code{selection_}
+ column with logical values indicating which rows are in the current
+ selection, or \code{NA} if no selection is currently active. If
+ \code{withFilter} (the default), only return rows that are part of the
+ current filter settings, if any. If \code{withKey}, add a \code{key_}
+ column with the key values of each row (normally not needed since the
+ key is either one of the other columns or else just the row names).
+
+ When running in Shiny, calling \code{data()} is a reactive operation
+ that will invalidate if the selection or filter change (assuming that
+ information was requested), or if the original data is a reactive
+ expression that has invalidated.
+ }
+ \item{\code{origData()}}{
+ Return the data frame that was used to create this \code{SharedData}
+ instance. If a reactive expression, evaluate the reactive expression.
+ Equivalent to \code{data(FALSE, FALSE, FALSE)}.
+ }
+ \item{\code{groupName()}}{
+ Returns the value of \code{group} that was used to create this instance.
+ }
+ \item{\code{key()}}{
+ Returns the vector of key values. Filtering is not applied.
+ }
+ \item{\code{selection(value, ownerId = "")}}{
+ If called without arguments, returns a logical vector of rows that are
+ currently selected (brushed), or \code{NULL} if no selection exists.
+ Intended to be called from a Shiny reactive context, and invalidates
+ whenever the selection changes.
+
+ If called with one or two arguments, expects \code{value} to be a logical
+ vector of \code{nrow(origData())} length, indicating which rows are
+ currently selected (brushed). This value is propagated to the web browser
+ (assumes an active Shiny app or Shiny R Markdown document).
+
+ Set the \code{ownerId} argument to the \code{outputId} of a widget if
+ conceptually that widget "initiated" the selection (prevents that widget
+ from clearing its visual selection box, which is normally cleared when
+ the selection changes). For example, if setting the selection based on a
+ \code{\link[shiny]{plotOutput}} brush, then \code{ownerId} should be the
+ \code{outputId} of the \code{plotOutput}.
+ }
+ \item{\code{clearSelection(ownerId = "")}}{
+ Clears the selection. For the meaning of \code{ownerId}, see the
+ \code{selection} method.
+ }
+}
+}
+\keyword{datasets}
+
diff --git a/man/bscols.Rd b/man/bscols.Rd
new file mode 100644
index 0000000..e4b2801
--- /dev/null
+++ b/man/bscols.Rd
@@ -0,0 +1,56 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/controls.R
+\name{bscols}
+\alias{bscols}
+\title{Arrange HTML elements or widgets in Bootstrap columns}
+\usage{
+bscols(..., widths = NA, device = c("xs", "sm", "md", "lg"))
+}
+\arguments{
+\item{...}{\code{htmltools} tag objects, lists, text, HTML widgets, or
+NULL. These arguments should be unnamed.}
+
+\item{widths}{The number of columns that should be assigned to each of the
+\code{...} elements (the total number of columns available is always 12).
+The width vector will be recycled if there are more \code{...} arguments.
+\code{NA} columns will evenly split the remaining columns that are left
+after the widths are recycled and non-\code{NA} values are subtracted.}
+
+\item{device}{The class of device which is targeted by these widths; with
+smaller screen sizes the layout will collapse to a one-column,
+top-to-bottom display instead. xs: never collapse, sm: collapse below
+768px, md: 992px, lg: 1200px.}
+}
+\value{
+A \code{\link[htmltools]{browsable}} HTML element.
+}
+\description{
+This helper function makes it easy to put HTML elements side by side. It can
+be called directly from the console but is especially designed to work in an
+R Markdown document. Warning: This will bring in all of Bootstrap!
+}
+\examples{
+library(htmltools)
+
+# If width is unspecified, equal widths will be used
+bscols(
+ div(style = css(width="100\%", height="400px", background_color="red")),
+ div(style = css(width="100\%", height="400px", background_color="blue"))
+)
+
+# Use NA to absorb remaining width
+bscols(widths = c(2, NA, NA),
+ div(style = css(width="100\%", height="400px", background_color="red")),
+ div(style = css(width="100\%", height="400px", background_color="blue")),
+ div(style = css(width="100\%", height="400px", background_color="green"))
+)
+
+# Recycling widths
+bscols(widths = c(2, 4),
+ div(style = css(width="100\%", height="400px", background_color="red")),
+ div(style = css(width="100\%", height="400px", background_color="blue")),
+ div(style = css(width="100\%", height="400px", background_color="red")),
+ div(style = css(width="100\%", height="400px", background_color="blue"))
+)
+}
+
diff --git a/man/crosstalkLibs.Rd b/man/crosstalkLibs.Rd
new file mode 100644
index 0000000..1e106a8
--- /dev/null
+++ b/man/crosstalkLibs.Rd
@@ -0,0 +1,13 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/crosstalk.R
+\name{crosstalkLibs}
+\alias{crosstalkLibs}
+\title{Crosstalk dependencies}
+\usage{
+crosstalkLibs()
+}
+\description{
+List of \code{\link[htmltools]{htmlDependency}} objects necessary for
+Crosstalk to function. Intended for widget authors.
+}
+
diff --git a/man/filter_select.Rd b/man/filter_select.Rd
new file mode 100644
index 0000000..215778c
--- /dev/null
+++ b/man/filter_select.Rd
@@ -0,0 +1,49 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/controls.R
+\name{filter_select}
+\alias{filter_checkbox}
+\alias{filter_select}
+\title{Categorical filter controls}
+\usage{
+filter_select(id, label, sharedData, group, allLevels = FALSE,
+ multiple = TRUE)
+
+filter_checkbox(id, label, sharedData, group, allLevels = FALSE,
+ inline = FALSE, columns = 1)
+}
+\arguments{
+\item{id}{An HTML element ID; must be unique within the web page}
+
+\item{label}{A human-readable label}
+
+\item{sharedData}{\code{SharedData} object with the data to filter}
+
+\item{group}{A one-sided formula whose values will populate this select box.
+Generally this should be a character or factor column; if not, it will be
+coerced to character.}
+
+\item{allLevels}{If the vector described by \code{group} is factor-based,
+should all the levels be displayed as options, or only ones that are
+present in the data?}
+
+\item{multiple}{Can multiple values be selected?}
+
+\item{inline}{If \code{TRUE}, render checkbox options horizontally instead of vertically.}
+
+\item{columns}{Number of columns the options should be arranged into.}
+}
+\description{
+Creates a select box or list of checkboxes, for filtering a
+\code{\link{SharedData}} object based on categorical data.
+}
+\examples{
+## Only run examples in interactive R sessions
+if (interactive()) {
+
+sd <- SharedData$new(chickwts)
+filter_select("feedtype", "Feed type", sd, "feed")
+
+}
+
+}
+
diff --git a/man/filter_slider.Rd b/man/filter_slider.Rd
new file mode 100644
index 0000000..040ee12
--- /dev/null
+++ b/man/filter_slider.Rd
@@ -0,0 +1,97 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/controls.R
+\name{filter_slider}
+\alias{animation_options}
+\alias{filter_slider}
+\title{Range filter control}
+\usage{
+filter_slider(id, label, sharedData, column, step = NULL, round = FALSE,
+ ticks = TRUE, animate = FALSE, width = NULL, sep = ",", pre = NULL,
+ post = NULL, timeFormat = NULL, timezone = NULL, dragRange = TRUE)
+
+animation_options(interval = 1000, loop = FALSE, playButton = NULL,
+ pauseButton = NULL)
+}
+\arguments{
+\item{id}{An HTML element ID; must be unique within the web page}
+
+\item{label}{A human-readable label}
+
+\item{sharedData}{\code{SharedData} object with the data to filter}
+
+\item{column}{A one-sided formula whose values will be used for this slider.
+The column must be of type \code{\link{Date}}, \code{\link{POSIXt}}, or
+numeric.}
+
+\item{step}{Specifies the interval between each selectable value on the
+slider (if \code{NULL}, a heuristic is used to determine the step size). If
+the values are dates, \code{step} is in days; if the values are times
+(POSIXt), \code{step} is in seconds.}
+
+\item{round}{\code{TRUE} to round all values to the nearest integer;
+\code{FALSE} if no rounding is desired; or an integer to round to that
+number of digits (for example, 1 will round to the nearest 10, and -2 will
+round to the nearest .01). Any rounding will be applied after snapping to
+the nearest step.}
+
+\item{ticks}{\code{FALSE} to hide tick marks, \code{TRUE} to show them
+according to some simple heuristics.}
+
+\item{animate}{\code{TRUE} to show simple animation controls with default
+settings; \code{FALSE} not to; or a custom settings list, such as those
+created using \code{\link{animationOptions}}.}
+
+\item{width}{The width of the slider control (see
+\code{\link[htmltools]{validateCssUnit}} for valid formats)}
+
+\item{sep}{Separator between thousands places in numbers.}
+
+\item{pre}{A prefix string to put in front of the value.}
+
+\item{post}{A suffix string to put after the value.}
+
+\item{timeFormat}{Only used if the values are Date or POSIXt objects. A time
+format string, to be passed to the Javascript strftime library. See
+\url{https://github.com/samsonjs/strftime} for more details. The allowed
+format specifications are very similar, but not identical, to those for R's
+\code{\link{strftime}} function. For Dates, the default is \code{"\%F"}
+(like \code{"2015-07-01"}), and for POSIXt, the default is \code{"\%F \%T"}
+(like \code{"2015-07-01 15:32:10"}).}
+
+\item{timezone}{Only used if the values are POSIXt objects. A string
+specifying the time zone offset for the displayed times, in the format
+\code{"+HHMM"} or \code{"-HHMM"}. If \code{NULL} (the default), times will
+be displayed in the browser's time zone. The value \code{"+0000"} will
+result in UTC time.}
+
+\item{dragRange}{This option is used only if it is a range slider (with two
+values). If \code{TRUE} (the default), the range can be dragged. In other
+words, the min and max can be dragged together. If \code{FALSE}, the range
+cannot be dragged.}
+
+\item{interval}{The interval, in milliseconds, between each animation step.}
+
+\item{loop}{\code{TRUE} to automatically restart the animation when it
+reaches the end.}
+
+\item{playButton}{Specifies the appearance of the play button. Valid values
+are a one-element character vector (for a simple text label), an HTML tag
+or list of tags (using \code{\link{tag}} and friends), or raw HTML (using
+\code{\link{HTML}}).}
+
+\item{pauseButton}{Similar to \code{playButton}, but for the pause button.}
+}
+\description{
+Creates a slider widget that lets users filter observations based on a range
+of values.
+}
+\examples{
+## Only run examples in interactive R sessions
+if (interactive()) {
+
+sd <- SharedData$new(mtcars)
+filter_slider("mpg", "Miles per gallon", sd, "mpg")
+
+}
+}
+
diff --git a/man/is.SharedData.Rd b/man/is.SharedData.Rd
new file mode 100644
index 0000000..a2ebb99
--- /dev/null
+++ b/man/is.SharedData.Rd
@@ -0,0 +1,18 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/crosstalk.R
+\name{is.SharedData}
+\alias{is.SharedData}
+\title{Check if an object is \code{SharedData}}
+\usage{
+is.SharedData(x)
+}
+\arguments{
+\item{x}{The object that may or may not be an instance of \code{SharedData}}
+}
+\value{
+logical
+}
+\description{
+Check if an object is an instance of \code{\link{SharedData}} or not.
+}
+
diff --git a/man/maintain_selection.Rd b/man/maintain_selection.Rd
new file mode 100644
index 0000000..a6dcfef
--- /dev/null
+++ b/man/maintain_selection.Rd
@@ -0,0 +1,21 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/ggplot2.R
+\name{maintain_selection}
+\alias{maintain_selection}
+\title{Synchronize Shiny brush selection with shared data}
+\usage{
+maintain_selection(sharedData, brushId, ownerId = "")
+}
+\arguments{
+\item{sharedData}{The shared data instance}
+
+\item{brushId}{Character vector indicating the name of the \code{plotOutput}
+brush}
+
+\item{ownerId}{(TBD)}
+}
+\description{
+Waits for a brush to change, and propagates that change to the
+\code{sharedData} object.
+}
+
diff --git a/man/scale_fill_selection.Rd b/man/scale_fill_selection.Rd
new file mode 100644
index 0000000..607ce81
--- /dev/null
+++ b/man/scale_fill_selection.Rd
@@ -0,0 +1,46 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/ggplot2.R
+\name{scale_fill_selection}
+\alias{scale_color_selection}
+\alias{scale_fill_selection}
+\alias{selection_factor}
+\title{ggplot2 helpers}
+\usage{
+scale_fill_selection(color_false, color_true)
+
+scale_color_selection(color_false, color_true)
+
+selection_factor(x, na.replace = c(FALSE, NA, TRUE))
+}
+\arguments{
+\item{color_false}{The color that should be mapped to unselected rows}
+
+\item{color_true}{The color that should be mapped to selected rows}
+
+\item{x}{Either a data frame with a \code{selected_} column, or, a logical
+vector indicating which rows are selected}
+
+\item{na.replace}{The value to use to replace \code{NA} values; choose either
+\code{FALSE}, \code{NA}, or \code{TRUE} based on how you want values to be
+treated when no selection is active}
+}
+\description{
+Add \code{scale_fill_selection()} or \code{scale_color_selection} to a ggplot
+to customize the scale for fill or color, respectively, for linked brushing.
+Use \code{selection_factor} to turn logical vectors representing selection,
+to a factor with the levels ordered for use with ggplot2 bar stacking.
+}
+\examples{
+\dontrun{
+sd <- SharedData$new(iris)
+renderPlot({
+ df <- sd$data(withSelection = TRUE, withFilter = TRUE)
+ ggplot(df, aes(Sepal.Length, Sepal.Width,
+ color = selection_factor(df))) +
+ geom_point() +
+ scale_color_selection("#444444", "skyblue1")
+})
+
+}
+}
+
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/r-cran-crosstalk.git
More information about the debian-science-commits
mailing list