Repository: tinystats/teacups-giraffes-and-statistics
Branch: master
Commit: b859c30e57fd
Files: 298
Total size: 28.4 MB
Directory structure:
gitextract_se6ek936/
├── .gitignore
├── .nojekyll
├── 00_narrative.Rmd
├── 01_introToR.Rmd
├── 02_bellCurve.Rmd
├── 03_mean.Rmd
├── 04_variance.Rmd
├── 05_correlation.Rmd
├── 06_standardError.Rmd
├── 07_ttest.Rmd
├── Giraffe.Rproj
├── README.md
├── _bookdown_files/
│ ├── 02_bellCurve_cache/
│ │ └── html/
│ │ ├── __packages
│ │ ├── unnamed-chunk-10_f069c4cb7b2a37e345b54d932ab0c3fd.RData
│ │ ├── unnamed-chunk-10_f069c4cb7b2a37e345b54d932ab0c3fd.rdb
│ │ └── unnamed-chunk-10_f069c4cb7b2a37e345b54d932ab0c3fd.rdx
│ ├── 03_mean_cache/
│ │ └── html/
│ │ ├── __packages
│ │ ├── unnamed-chunk-3_10110fd506496d65a16c9f8d338a2668.RData
│ │ ├── unnamed-chunk-3_10110fd506496d65a16c9f8d338a2668.rdb
│ │ ├── unnamed-chunk-3_10110fd506496d65a16c9f8d338a2668.rdx
│ │ ├── unnamed-chunk-8_38b4ab5145979124668b8637fc2e735d.RData
│ │ ├── unnamed-chunk-8_38b4ab5145979124668b8637fc2e735d.rdb
│ │ └── unnamed-chunk-8_38b4ab5145979124668b8637fc2e735d.rdx
│ ├── 04_variance_cache/
│ │ └── html/
│ │ ├── __packages
│ │ ├── unnamed-chunk-1_ada9cb0ce5c4061a0c5ff1623d5f7f89.RData
│ │ ├── unnamed-chunk-1_ada9cb0ce5c4061a0c5ff1623d5f7f89.rdb
│ │ ├── unnamed-chunk-1_ada9cb0ce5c4061a0c5ff1623d5f7f89.rdx
│ │ ├── unnamed-chunk-6_73229a47f445dee6916147a2314d2a2e.RData
│ │ ├── unnamed-chunk-6_73229a47f445dee6916147a2314d2a2e.rdb
│ │ ├── unnamed-chunk-6_73229a47f445dee6916147a2314d2a2e.rdx
│ │ ├── unnamed-chunk-7_d6e227b9f890babb0c3fafa99f25cb53.RData
│ │ ├── unnamed-chunk-7_d6e227b9f890babb0c3fafa99f25cb53.rdb
│ │ ├── unnamed-chunk-7_d6e227b9f890babb0c3fafa99f25cb53.rdx
│ │ ├── unnamed-chunk-8_2be90eac193e2686e087f7209c6f8f82.RData
│ │ ├── unnamed-chunk-8_2be90eac193e2686e087f7209c6f8f82.rdb
│ │ └── unnamed-chunk-8_2be90eac193e2686e087f7209c6f8f82.rdx
│ ├── 05_correlation_cache/
│ │ └── html/
│ │ ├── __packages
│ │ ├── unnamed-chunk-3_1eebcf2610d87e4af3a12ab993fa91e9.RData
│ │ ├── unnamed-chunk-3_1eebcf2610d87e4af3a12ab993fa91e9.rdb
│ │ ├── unnamed-chunk-3_1eebcf2610d87e4af3a12ab993fa91e9.rdx
│ │ ├── unnamed-chunk-4_b717a42ace8f3afc385b7ca2d3e25357.RData
│ │ ├── unnamed-chunk-4_b717a42ace8f3afc385b7ca2d3e25357.rdb
│ │ └── unnamed-chunk-4_b717a42ace8f3afc385b7ca2d3e25357.rdx
│ └── 06_standardError_cache/
│ └── html/
│ ├── __packages
│ ├── unnamed-chunk-3_9cfde893520860c5b44c8622f1d3d7ab.RData
│ ├── unnamed-chunk-3_9cfde893520860c5b44c8622f1d3d7ab.rdb
│ ├── unnamed-chunk-3_9cfde893520860c5b44c8622f1d3d7ab.rdx
│ ├── unnamed-chunk-4_e5136729fcb5781a823ef76d4423bd9b.RData
│ ├── unnamed-chunk-4_e5136729fcb5781a823ef76d4423bd9b.rdb
│ ├── unnamed-chunk-4_e5136729fcb5781a823ef76d4423bd9b.rdx
│ ├── unnamed-chunk-5_6ba20d5ad665975d5a2f7608fb786bf6.RData
│ ├── unnamed-chunk-5_6ba20d5ad665975d5a2f7608fb786bf6.rdb
│ ├── unnamed-chunk-5_6ba20d5ad665975d5a2f7608fb786bf6.rdx
│ ├── unnamed-chunk-6_34d73e069af7b7720f4b88e28e921cb3.RData
│ ├── unnamed-chunk-6_34d73e069af7b7720f4b88e28e921cb3.rdb
│ └── unnamed-chunk-6_34d73e069af7b7720f4b88e28e921cb3.rdx
├── _site.yml
├── aboutTheAuthors.Rmd
├── assets/
│ ├── 00_narrative.html
│ ├── 01_introToR_image.html
│ ├── 02_bellCurve_image.html
│ ├── 03_mean_image.html
│ ├── 04_variance_image.html
│ ├── 05_correlation_image.html
│ ├── 06_standardError_image.html
│ ├── 07_tTest_image.html
│ ├── Landing_page.html
│ ├── aboutTheAuthors_image.html
│ ├── button.css
│ ├── foot.html
│ ├── landing.css
│ ├── landing_styles.css
│ ├── mobile_landing.css
│ ├── mobile_narrative.css
│ └── style.css
├── docs/
│ ├── 00_narrative.html
│ ├── 00_narrative.utf8.html
│ ├── 01_introToR.html
│ ├── 02_bellCurve.html
│ ├── 03_mean.html
│ ├── 04_variance.html
│ ├── 05_correlation.html
│ ├── 06_standardError.html
│ ├── 07_ttest.html
│ ├── aboutTheAuthors.html
│ ├── assets/
│ │ ├── 00_narrative.html
│ │ ├── 01_introToR_image.html
│ │ ├── 02_bellCurve_image.html
│ │ ├── 03_mean_image.html
│ │ ├── 04_variance_image.html
│ │ ├── 05_correlation_image.html
│ │ ├── 06_standardError_image.html
│ │ ├── 07_tTest_image.html
│ │ ├── Landing_page.html
│ │ ├── aboutTheAuthors_image.html
│ │ ├── button.css
│ │ ├── foot.html
│ │ ├── landing.css
│ │ ├── landing_styles.css
│ │ ├── mobile_landing.css
│ │ ├── mobile_narrative.css
│ │ └── style.css
│ ├── images/
│ │ ├── 02_bellCurve/
│ │ │ ├── lib/
│ │ │ │ ├── crosstalk-1.0.0/
│ │ │ │ │ ├── css/
│ │ │ │ │ │ └── crosstalk.css
│ │ │ │ │ └── js/
│ │ │ │ │ └── crosstalk.js
│ │ │ │ ├── crosstalk-1.2.0/
│ │ │ │ │ ├── js/
│ │ │ │ │ │ └── crosstalk.js
│ │ │ │ │ └── scss/
│ │ │ │ │ └── crosstalk.scss
│ │ │ │ ├── htmlwidgets-1.3/
│ │ │ │ │ └── htmlwidgets.js
│ │ │ │ ├── htmlwidgets-1.5.1/
│ │ │ │ │ └── htmlwidgets.js
│ │ │ │ ├── htmlwidgets-1.5.4/
│ │ │ │ │ └── htmlwidgets.js
│ │ │ │ ├── jquery-1.11.3/
│ │ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ │ └── jquery.js
│ │ │ │ ├── jquery-3.5.1/
│ │ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ │ └── jquery.js
│ │ │ │ ├── plotly-binding-4.10.0/
│ │ │ │ │ └── plotly.js
│ │ │ │ ├── plotly-binding-4.9.0/
│ │ │ │ │ └── plotly.js
│ │ │ │ ├── plotly-binding-4.9.1/
│ │ │ │ │ └── plotly.js
│ │ │ │ ├── plotly-htmlwidgets-css-1.46.1/
│ │ │ │ │ └── plotly-htmlwidgets.css
│ │ │ │ ├── plotly-htmlwidgets-css-1.49.4/
│ │ │ │ │ └── plotly-htmlwidgets.css
│ │ │ │ └── plotly-htmlwidgets-css-2.5.1/
│ │ │ │ └── plotly-htmlwidgets.css
│ │ │ └── two_animated_hist.html
│ │ ├── 03_mean/
│ │ │ ├── Law_of_large_numbers.html
│ │ │ └── lib/
│ │ │ ├── crosstalk-1.0.0/
│ │ │ │ ├── css/
│ │ │ │ │ └── crosstalk.css
│ │ │ │ └── js/
│ │ │ │ └── crosstalk.js
│ │ │ ├── crosstalk-1.2.0/
│ │ │ │ ├── js/
│ │ │ │ │ └── crosstalk.js
│ │ │ │ └── scss/
│ │ │ │ └── crosstalk.scss
│ │ │ ├── htmlwidgets-1.3/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── htmlwidgets-1.5.1/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── htmlwidgets-1.5.4/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── jquery-1.11.3/
│ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ └── jquery.js
│ │ │ ├── jquery-3.5.1/
│ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ └── jquery.js
│ │ │ ├── plotly-binding-4.10.0/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-binding-4.9.0/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-binding-4.9.1/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-htmlwidgets-css-1.46.1/
│ │ │ │ └── plotly-htmlwidgets.css
│ │ │ ├── plotly-htmlwidgets-css-1.49.4/
│ │ │ │ └── plotly-htmlwidgets.css
│ │ │ └── plotly-htmlwidgets-css-2.5.1/
│ │ │ └── plotly-htmlwidgets.css
│ │ ├── 04_variance/
│ │ │ ├── lib/
│ │ │ │ ├── crosstalk-1.0.0/
│ │ │ │ │ ├── css/
│ │ │ │ │ │ └── crosstalk.css
│ │ │ │ │ └── js/
│ │ │ │ │ └── crosstalk.js
│ │ │ │ ├── crosstalk-1.2.0/
│ │ │ │ │ ├── js/
│ │ │ │ │ │ └── crosstalk.js
│ │ │ │ │ └── scss/
│ │ │ │ │ └── crosstalk.scss
│ │ │ │ ├── htmlwidgets-1.3/
│ │ │ │ │ └── htmlwidgets.js
│ │ │ │ ├── htmlwidgets-1.5.1/
│ │ │ │ │ └── htmlwidgets.js
│ │ │ │ ├── htmlwidgets-1.5.4/
│ │ │ │ │ └── htmlwidgets.js
│ │ │ │ ├── jquery-1.11.3/
│ │ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ │ └── jquery.js
│ │ │ │ ├── jquery-3.5.1/
│ │ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ │ └── jquery.js
│ │ │ │ ├── plotly-binding-4.10.0/
│ │ │ │ │ └── plotly.js
│ │ │ │ ├── plotly-binding-4.9.0/
│ │ │ │ │ └── plotly.js
│ │ │ │ ├── plotly-binding-4.9.1/
│ │ │ │ │ └── plotly.js
│ │ │ │ ├── plotly-htmlwidgets-css-1.46.1/
│ │ │ │ │ └── plotly-htmlwidgets.css
│ │ │ │ ├── plotly-htmlwidgets-css-1.49.4/
│ │ │ │ │ └── plotly-htmlwidgets.css
│ │ │ │ └── plotly-htmlwidgets-css-2.5.1/
│ │ │ │ └── plotly-htmlwidgets.css
│ │ │ ├── mega_dots.html
│ │ │ └── static_bar.html
│ │ └── 05_correlation/
│ │ ├── cov_vs_sxsy.html
│ │ ├── factory.webm
│ │ ├── lib/
│ │ │ ├── crosstalk-1.0.0/
│ │ │ │ ├── css/
│ │ │ │ │ └── crosstalk.css
│ │ │ │ └── js/
│ │ │ │ └── crosstalk.js
│ │ │ ├── crosstalk-1.2.0/
│ │ │ │ ├── js/
│ │ │ │ │ └── crosstalk.js
│ │ │ │ └── scss/
│ │ │ │ └── crosstalk.scss
│ │ │ ├── htmlwidgets-1.3/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── htmlwidgets-1.5.4/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── jquery-1.11.3/
│ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ └── jquery.js
│ │ │ ├── jquery-3.5.1/
│ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ └── jquery.js
│ │ │ ├── plotly-binding-4.10.0/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-binding-4.9.0/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-htmlwidgets-css-1.46.1/
│ │ │ │ └── plotly-htmlwidgets.css
│ │ │ └── plotly-htmlwidgets-css-2.5.1/
│ │ │ └── plotly-htmlwidgets.css
│ │ └── lines.webm
│ ├── index.html
│ ├── lib/
│ │ ├── crosstalk-1.0.0/
│ │ │ ├── css/
│ │ │ │ └── crosstalk.css
│ │ │ └── js/
│ │ │ └── crosstalk.js
│ │ ├── htmlwidgets-1.2/
│ │ │ └── htmlwidgets.js
│ │ ├── htmlwidgets-1.3/
│ │ │ └── htmlwidgets.js
│ │ ├── jquery-1.11.3/
│ │ │ ├── jquery-AUTHORS.txt
│ │ │ └── jquery.js
│ │ ├── plotly-binding-4.8.0/
│ │ │ └── plotly.js
│ │ ├── plotly-binding-4.9.0/
│ │ │ └── plotly.js
│ │ ├── plotly-htmlwidgets-css-1.39.2/
│ │ │ └── plotly-htmlwidgets.css
│ │ └── plotly-htmlwidgets-css-1.46.1/
│ │ └── plotly-htmlwidgets.css
│ └── site_libs/
│ ├── bootstrap-3.3.5/
│ │ ├── css/
│ │ │ ├── bootstrap-theme.css
│ │ │ └── bootstrap.css
│ │ └── js/
│ │ ├── bootstrap.js
│ │ └── npm.js
│ ├── crosstalk-1.2.0/
│ │ ├── js/
│ │ │ └── crosstalk.js
│ │ └── scss/
│ │ └── crosstalk.scss
│ ├── font-awesome-5.1.0/
│ │ └── css/
│ │ ├── all.css
│ │ └── v4-shims.css
│ ├── highlightjs-9.12.0/
│ │ ├── default.css
│ │ ├── highlight.js
│ │ └── textmate.css
│ ├── htmlwidgets-1.5.4/
│ │ └── htmlwidgets.js
│ ├── jquery-3.6.0/
│ │ └── jquery-3.6.0.js
│ ├── jqueryui-1.11.4/
│ │ ├── README
│ │ ├── index.html
│ │ ├── jquery-ui.css
│ │ ├── jquery-ui.js
│ │ ├── jquery-ui.structure.css
│ │ └── jquery-ui.theme.css
│ ├── navigation-1.1/
│ │ ├── codefolding.js
│ │ ├── sourceembed.js
│ │ └── tabsets.js
│ ├── plotly-binding-4.10.0/
│ │ └── plotly.js
│ ├── plotly-htmlwidgets-css-2.5.1/
│ │ └── plotly-htmlwidgets.css
│ └── tocify-1.9.1/
│ ├── jquery.tocify.css
│ └── jquery.tocify.js
├── five_points_scatter.R
├── images/
│ ├── 02_bellCurve/
│ │ ├── lib/
│ │ │ ├── crosstalk-1.0.0/
│ │ │ │ ├── css/
│ │ │ │ │ └── crosstalk.css
│ │ │ │ └── js/
│ │ │ │ └── crosstalk.js
│ │ │ ├── crosstalk-1.2.0/
│ │ │ │ ├── js/
│ │ │ │ │ └── crosstalk.js
│ │ │ │ └── scss/
│ │ │ │ └── crosstalk.scss
│ │ │ ├── htmlwidgets-1.3/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── htmlwidgets-1.5.1/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── htmlwidgets-1.5.4/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── jquery-1.11.3/
│ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ └── jquery.js
│ │ │ ├── jquery-3.5.1/
│ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ └── jquery.js
│ │ │ ├── plotly-binding-4.10.0/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-binding-4.9.0/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-binding-4.9.1/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-htmlwidgets-css-1.46.1/
│ │ │ │ └── plotly-htmlwidgets.css
│ │ │ ├── plotly-htmlwidgets-css-1.49.4/
│ │ │ │ └── plotly-htmlwidgets.css
│ │ │ └── plotly-htmlwidgets-css-2.5.1/
│ │ │ └── plotly-htmlwidgets.css
│ │ └── two_animated_hist.html
│ ├── 03_mean/
│ │ ├── Law_of_large_numbers.html
│ │ └── lib/
│ │ ├── crosstalk-1.0.0/
│ │ │ ├── css/
│ │ │ │ └── crosstalk.css
│ │ │ └── js/
│ │ │ └── crosstalk.js
│ │ ├── crosstalk-1.2.0/
│ │ │ ├── js/
│ │ │ │ └── crosstalk.js
│ │ │ └── scss/
│ │ │ └── crosstalk.scss
│ │ ├── htmlwidgets-1.3/
│ │ │ └── htmlwidgets.js
│ │ ├── htmlwidgets-1.5.1/
│ │ │ └── htmlwidgets.js
│ │ ├── htmlwidgets-1.5.4/
│ │ │ └── htmlwidgets.js
│ │ ├── jquery-1.11.3/
│ │ │ ├── jquery-AUTHORS.txt
│ │ │ └── jquery.js
│ │ ├── jquery-3.5.1/
│ │ │ ├── jquery-AUTHORS.txt
│ │ │ └── jquery.js
│ │ ├── plotly-binding-4.10.0/
│ │ │ └── plotly.js
│ │ ├── plotly-binding-4.9.0/
│ │ │ └── plotly.js
│ │ ├── plotly-binding-4.9.1/
│ │ │ └── plotly.js
│ │ ├── plotly-htmlwidgets-css-1.46.1/
│ │ │ └── plotly-htmlwidgets.css
│ │ ├── plotly-htmlwidgets-css-1.49.4/
│ │ │ └── plotly-htmlwidgets.css
│ │ └── plotly-htmlwidgets-css-2.5.1/
│ │ └── plotly-htmlwidgets.css
│ ├── 04_variance/
│ │ ├── lib/
│ │ │ ├── crosstalk-1.0.0/
│ │ │ │ ├── css/
│ │ │ │ │ └── crosstalk.css
│ │ │ │ └── js/
│ │ │ │ └── crosstalk.js
│ │ │ ├── crosstalk-1.2.0/
│ │ │ │ ├── js/
│ │ │ │ │ └── crosstalk.js
│ │ │ │ └── scss/
│ │ │ │ └── crosstalk.scss
│ │ │ ├── htmlwidgets-1.3/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── htmlwidgets-1.5.1/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── htmlwidgets-1.5.4/
│ │ │ │ └── htmlwidgets.js
│ │ │ ├── jquery-1.11.3/
│ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ └── jquery.js
│ │ │ ├── jquery-3.5.1/
│ │ │ │ ├── jquery-AUTHORS.txt
│ │ │ │ └── jquery.js
│ │ │ ├── plotly-binding-4.10.0/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-binding-4.9.0/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-binding-4.9.1/
│ │ │ │ └── plotly.js
│ │ │ ├── plotly-htmlwidgets-css-1.46.1/
│ │ │ │ └── plotly-htmlwidgets.css
│ │ │ ├── plotly-htmlwidgets-css-1.49.4/
│ │ │ │ └── plotly-htmlwidgets.css
│ │ │ └── plotly-htmlwidgets-css-2.5.1/
│ │ │ └── plotly-htmlwidgets.css
│ │ ├── mega_dots.html
│ │ └── static_bar.html
│ └── 05_correlation/
│ ├── cov_vs_sxsy.html
│ ├── factory.webm
│ ├── lib/
│ │ ├── crosstalk-1.0.0/
│ │ │ ├── css/
│ │ │ │ └── crosstalk.css
│ │ │ └── js/
│ │ │ └── crosstalk.js
│ │ ├── crosstalk-1.2.0/
│ │ │ ├── js/
│ │ │ │ └── crosstalk.js
│ │ │ └── scss/
│ │ │ └── crosstalk.scss
│ │ ├── htmlwidgets-1.3/
│ │ │ └── htmlwidgets.js
│ │ ├── htmlwidgets-1.5.4/
│ │ │ └── htmlwidgets.js
│ │ ├── jquery-1.11.3/
│ │ │ ├── jquery-AUTHORS.txt
│ │ │ └── jquery.js
│ │ ├── jquery-3.5.1/
│ │ │ ├── jquery-AUTHORS.txt
│ │ │ └── jquery.js
│ │ ├── plotly-binding-4.10.0/
│ │ │ └── plotly.js
│ │ ├── plotly-binding-4.9.0/
│ │ │ └── plotly.js
│ │ ├── plotly-htmlwidgets-css-1.46.1/
│ │ │ └── plotly-htmlwidgets.css
│ │ └── plotly-htmlwidgets-css-2.5.1/
│ │ └── plotly-htmlwidgets.css
│ └── lines.webm
├── index.Rmd
├── lib/
│ ├── crosstalk-1.0.0/
│ │ ├── css/
│ │ │ └── crosstalk.css
│ │ └── js/
│ │ └── crosstalk.js
│ ├── htmlwidgets-1.2/
│ │ └── htmlwidgets.js
│ ├── htmlwidgets-1.3/
│ │ └── htmlwidgets.js
│ ├── jquery-1.11.3/
│ │ ├── jquery-AUTHORS.txt
│ │ └── jquery.js
│ ├── plotly-binding-4.8.0/
│ │ └── plotly.js
│ ├── plotly-binding-4.9.0/
│ │ └── plotly.js
│ ├── plotly-htmlwidgets-css-1.39.2/
│ │ └── plotly-htmlwidgets.css
│ └── plotly-htmlwidgets-css-1.46.1/
│ └── plotly-htmlwidgets.css
└── renv/
├── .gitignore
├── activate.R
└── settings.dcf
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.DS_Store
# History files
.Rhistory
.Rapp.history
# Session Data files
.RData
# Example code in package build process
*-Ex.R
# Output files from R CMD build
/*.tar.gz
# Output files from R CMD check
/*.Rcheck/
# RStudio files
.Rproj.user/
# produced vignettes
vignettes/*.html
vignettes/*.pdf
# OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3
.httr-oauth
# knitr and R markdown default cache directories
/*_cache/
/cache/
# Temporary files created by R markdown
*.utf8.md
*.knit.md
# Shiny token, see https://shiny.rstudio.com/articles/shinyapps.html
rsconnect/
.Rproj.user
# Don't need renv in docs/
docs/renv.lock
================================================
FILE: .nojekyll
================================================
================================================
FILE: 00_narrative.Rmd
================================================
---
title: "History of Teacup Giraffes"
output:
bookdown::html_document2:
includes:
in_header: assets/00_narrative.html
toc: false
---
================================================
FILE: 01_introToR.Rmd
================================================
---
title: "Introduction to the R programming language"
output:
bookdown::html_document2:
includes:
in_header: assets/01_introToR_image.html
after_body: assets/foot.html
---
# Ready to begin?
You’re about to start an adventure to learn R and statistics. If this is your first time working with R, then you should begin on this page. If you’re comfortable with R basics and you’d like to start with the statistical content, please proceed onto the islands with this [link](02_bellCurve.html).
# Using interactive windows
Throughout this material we will use an interactive version of R provided by [learnR](https://rstudio.github.io/learnr/). Although this way of using R comes with some limitations regarding functionality, it will give us the benefit of being able to run R code without switching from a web browser to a standalone application such as [RStudio](https://www.rstudio.com/).
Below you can see an example of an interactive R window. In the white window (under the button with the text *Start Over*) is where you will write code to be executed. To run code you have written, click the green *Run Code* button and observe how information pops up under the window. If no errors were made, this is where the answer will be returned. Error messages are shown in a red box, also under the learnR window.
Now spend a few minutes using the window below as a calculator and run simple calculations by clicking *Run Code*.
# Objects in R
R is an object oriented programming language, meaning when working in R we will create different types of objects and use operators and functions to manipulate these objects.
To create a new object in R we first pick a name for the object and tell R what to assign to it using **assignment operators**; either `=` or a small arrow `<-` made from the combination of `<` and `-`.
* Use the window below to assign numbers to objects and output the content by typing out the name of the object.
* After creating two numerical objects, use the mathematical operator `+` to add these objects together and see what happens when you run the code.
# Common functions
Most of the time when we work in R, we will use functions; either pre-written functions or ones we write ourselves. **Functions** make it easy to use sets of code instructions repeatedly (without filling our scripts with the code underlying the function) and help us carry out multiple tasks in a single step without having to go through the details of how the steps are executed.
To use functions in R, we type the name of the function followed by parentheses (e.g. `print( )`). Within the parentheses is where we will specify the function input and options, separated by commas `,`. One function you will use a lot is the **combine function** `c( )`, which as the name suggests lets you concatenate different types of values.
* In the window below, create an object combining a set of numerical values using `c( )`, separating the different values with commas.
* Then sum up the content of this object using `sum( )`.
# Writing your own function
R makes it easy to create user defined functions by using `function( )`. Here is how it works:
* Give your function an object name and assign the function to it, e.g. `my_function_name <- function( )`.
* Within the parentheses you specify inputs and options just like how pre-written functions work, e.g. `function(input_data)`
* Next, put all the code you want your function to execute inside curly brackets like this: `function(input_data){code to run}`
* Use `return( )` to specify what you want to your function to output once it is done running the code.
Use the following instructions to complete the function in the window below:
* We've started writing a function for you that will sum up values and take the square root of the sum. To take the square root, we use the `sqrt( )` function.
* Complete this function by filling in `input_data` for the sum( ), and then filling in the remaining empty parentheses with the appropriate object names.
* Now create an object containing a set of numerical values and call it `my_combined_values`. Then try out your new function on this object, which will compute the square root of the object's sum.
# Functions within functions
It is also possible (and sometimes very useful) to put a function within another function. For example, we could combine multiple steps into one like this: `sqrt(prod(c(1,2)))`, resulting in one line of code that both generates the values and directly calculates the square root of the product of those values.
# Vectors
The `c( )` function will combine values and create a specific type of data structure called an **atomic vector**. A vector is a simple one-dimensional structure that can contain only one type of value. This means that storing multiple numeric values in a vector, as we have already done, works just fine. But there are other types of values that can be used in R, for example **character** values. These values are created by putting text or numbers within quotes such as `"Giraffe"`. If we try to combine numeric and character values in the same vector, R will convert both values to the character type.
```{r}
c("Giraffe", 123)
```
This behavior is not always desirable, so it is a good idea to try to only combine values of the same type.
# Boolean values and logical operators
Another type of variable in R is the boolean (TRUE/FALSE) type. **Boolean** or logical vectors can only take two different values; TRUE or FALSE (case sensitive). You will see these types of values mostly when logical operators are used to test how different objects relate to each other. For example, the **logical operator** `==` can be used to test if two different objects are exactly the same and `TRUE` will be returned only if the identity is fulfilled.
```{r}
"Giraffe" == "Teacup"
sqrt(100) == 10
```
# Data frames
Vectors are one of multiple data structures in R. We will not cover all types of structures here, but perhaps the most common one encountered when analyzing data in R is the **data frame**. Data frames are two dimensional, which basically means that data frames let you store multiple vectors in one object. Oftentimes, each vector will be a new column in the data frame. One constraint though is that all vectors/columns need to be of the same length.
* In the window below, use the `data.frame()` function to create a data frame with two columns called *x* and *y*. When creating data frames we specify a column by giving the column a name and assigning values or vectors to it, e.g. `data.frame(x= c("Giraffe", "Teacup"))`.
* Also observe what happens when you try to combine an x and y vector of different lengths.
As you could see above, running a line of code with just the name of a data structure (in this case, the letter *d*) will print the full data frame in the console output (if no errors were made!). If you instead are interested in only one of the columns, this can be specified by using the `$` operator followed by the column's name, e.g `d$x`. Try it out below!
# You're ready to go!
This was a brief introduction into R, but now you know what you need to get started with the [first module](02_bellCurve.html)!
================================================
FILE: 02_bellCurve.Rmd
================================================
---
title: "The Normal Distribution"
output:
bookdown::html_document2:
includes:
in_header: assets/02_bellCurve_image.html
after_body: assets/foot.html
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, fig.align = "center")
library(here)
library(plotly)
library(tweenr)
library(here)
library(ggplot2)
```
:::obj
**Module learning objectives**
1. Define what a distribution is
1. Describe the characteristics of a normal distribution
1. Create a histogram using `ggplot` and modify its appearance
1. Define sample and population
1. Explain why distributions of sample data that come from populations with normal distibutions don't always look normal
:::
# Welcome to the island
Two weeks after starting grad school, you're assigned to go to a small island east of Madagascar to study a mysterious mammalian species that has caught your advisor's interest. After a long trip on various ships and smaller boats, you arrive on the island, excited to collect data on this new species.
The only information your advisor has given you is that they are small giraffe-like creatures. He called them teacup giraffes. You waste no time to suit up in your field gear, and your guide leads you deep into the dense island brush. Filled with anticipation, you start searching for your first subject.
After a one hour hike, you reach a clearing where tall cypress trees encircle low growth vegetation. Suddenly, you experience your first encounter with a little giraffe, whose cool drink from a puddle you seem to have interrupted. Smaller than you had imagined--its slender body does not even clear the height of a dandelion. You toss a celery stick in its direction, and you're pleasantly surprised that it immediately trots over to you and starts vigorously munching, creating a celery confetti cloud. After observing this behavior for a while and taking some notes on how quickly it devours your celery supply, you bring out your measuring tape and record the giraffe's height.
\
```{r, out.width="600px", echo= FALSE}
knitr::include_graphics("images/02_bellCurve/Cool-Drink.jpg")
```
\
A few minuters later, you leave the clearing and forge a path through thick ferns and palm leaves. You pause for a drink from your water bottle long enough to pinpoint a fast-paced crunching noise. In the shade of a fallen tree, there's another teacup giraffe annihilating a small patch of wild celery for its afternoon snack. You're surprised how much smaller this one is--about as tall as your Swiss army knife. Throughout the day you encounter several teacup giraffes, and you realize that although all petite, they are remarkably variable in stature.
The next week is spent trekking and measuring every giraffe you can manage to get near enough. You take advantage of the fact that they come running everytime you pull out a celery stalk from your lunch bag, and you're relieved that it takes them long enough to finish snacking for you to measure their height. With the help of your guide, you manage to measure 50 giraffes in the first week.
```{r, out.width="800px", echo= FALSE}
knitr::include_graphics("images/02_bellCurve/giraffe_lineup4.jpg")
```
There is a second island not too far away, where your guide has indicated there may be more giraffes. You wonder how the population of giraffes on the second island may be different, and so you make arrangements to go to Island #2 the following week. It is not too long until you have added another 50 measurements of these tiny giraffes from this second excursion.
```{r, out.width="800px", echo= FALSE}
knitr::include_graphics("images/02_bellCurve/giraffe_islands2.jpg")
```
# Visualize the data
After collecting 100 measurements, you decide to take a first look at the data. What's the best way to look at data when you know nothing about it...? You take a long walk on the beach to ponder this.
You can start by scanning the values for the shortest and tallest heights. You see the range is between 6 and 20cm. So you draw a ruler in the sand with the extreme heights on either end. You'd like to see how many times each height occurs in your data set, and so you grab a small colorful stone from the shore to represent each individual giraffe's height and place it just above your ruler mark. You put out a new stone for each height and continue doing this for each individual to see which heights "stack up" along your ruler. To keep track of which heights came from different islands, you pick differently colored stones for each group. Look below for a sped-up version of this process! The y-axis is frequency. The x-axis is the height.
```{r, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
set.seed(12)
x <- round(rnorm(50, 10, 2))
x2 <- round(rnorm(50, 18, 1.2))
x <- c(x, x2)
df <- data.frame(x = x, y = 23, type = rep(c("Island #1", "Island #2"),
each = 50))
dfs <- list(df)
for (i in seq_len(nrow(df)))
{
dftemp <- tail(dfs, 1)
dftemp[[1]]$y[i] <- sum(dftemp[[1]]$x[seq_len(i)] == dftemp[[1]]$x[i])
dfs <- append(dfs, dftemp)
}
dfs <- append(dfs, dfs[rep(length(dfs), 3)])
dft <- tween_states(dfs, 10, 1, "cubic-in", 500)
dft$y <- dft$y - 0.5
dft <- dft[dft$y != 23, ]
m <- list(l = 50, r = 50, b = 10, t = 10, pad = 4)
p <-
dft %>%
plot_ly(x = ~x, y = ~y, frame = ~.frame, color = ~type,
colors = c("green3", "turquoise3"),
marker = list(size = 16), width = 630, height = 390) %>%
config(displayModeBar = F) %>%
layout(xaxis = list(range = c(4, 23), title = "Teacup Giraffe heights", zeroline = F),
yaxis = list(range = c(-0.5, 21), title = "Frequency", zeroline = F),
legend = list(x = 0.075, y = 0.91),
autosize = F, margin = m) %>%
animation_opts(frame = 50, transition = 0, redraw = FALSE) %>%
animation_slider(hide = T) %>%
animation_button(x = 1, xanchor = "right", y = 0, yanchor = "bottom")
p
htmltools::save_html(p, here(file = "images/02_bellCurve/two_animated_hist.html"))
```
As you put the last stone in place, your local guide saunters by and glances at your markings, "What a nice histogram!"
# Distributions
The histogram above shows the **distribution**, or shape, of your data. The distribution of a variable in a data set gives you information about:
* All the values the variable takes on in your data set, when the data are split into reasonably sized groups
* How often each value occurs
* The shape, center, and amount of variability in the data
Checking the distribution of the data is always one of the first steps of data anlaysis. By knowing the shape of the data, you gain insights into some of the data's statistical properties (which become useful down the line, for example, when you need to decide whether a particular statistical test would be appropriate).
# The normal distribution
Although the data can be distributed in many shapes, there are some general shapes that occur so frequently in nature that these distributions are given their own names. The most well-known distribution has a shape similar to a bell and is called the *normal distribution* (or sometimes "the bell curve" or just "normal curve").
There are a few characteristics of the normal distribution:
* There is a single peak
* The mass of the distribution is at its center
* There is symmetry about the center line
```{r, out.width="100%", echo= FALSE}
knitr::include_graphics("images/02_bellCurve/normal_hist.png")
```
Taking a look at the stones in the sand, you see two bell-shaped distributions. One for each island. It looks like giraffe heights on each island follow a normal distribution--- and that's a good thing because you remember your stats textbook always talking about how normally distributed data behaves well! Phew!
Happy with your progress thus far, you are excited to send your histogram results to your PhD mentor back in the homeland. Instead of taking a picture of your stone histogram, you turn to R to create the perfect figure.
# Our dataframe
Time to apply your [Intro to R knowledge](01_introToR.html). The heights from your logbook have been stored in a data frame called `d`. Below we show the last few observations from this vector, using the `tail()` function, which all happen to be from Island #2.
```{r, echo=FALSE}
set.seed(12)
x <- rnorm(50, 10, 2)
x2 <- rnorm(50, 18, 1.2)
x <- data.frame(x = x, type = "Island #1")
x2 <- data.frame(x = x2, type = "Island #2")
d <- rbind(x, x2)
colnames(d) <- c("Height", "Location")
```
```{r, include = TRUE}
tail(d)
```
# Making a histogram with `ggplot2`
We will use the *ggplot2* package for all our graphing. Check out [this page](https://ggplot2.tidyverse.org/reference) as a reference.
We need some basic components as a bare minimum to get started. We can customize components later to make the graph more to our liking. The steps we will go through are:
* First we need to tell R that we want to create a ggplot. This is done by **using the `ggplot( )` function**. Within the parentheses, we can specify the data frame that contains what we want to plot, using the option `data = d`. We also have to tell ggplot *what* columns of the data frame to actually plot-- we do this with the argument that stands for aesthetics: `aes( )`. In our case, only the x-axis variable `Height` needs to be specified.
* Next, **add a `geom` layer**, which will determine the type of visual representation that will be used for the data. Different ggplot layers and options are added using a plus sign `+`. In our case, we will write `+` and then `geom_histogram( )`. To make your plot look similar to your sand drawing, you want to add an optional argument within the parentheses of `geom_histogram`, which will set the bin width to 1cm: `geom_histogram( binwidth = 1 )`.
Here we are using `geom_histogram`, but there are many other `geom_` layers that you could use instead for different plot types. Check some of them out [here](https://ggplot2.tidyverse.org/reference/#section-layer-geoms).
A note about the `+`: You can keep adding new specifications on one long continuous line of code, separating each one with a `+`. However, if you'd like to make the code easier to read by adding each specification to a new line, make sure the `+` is added to the end of the first line and not the new one.
It's a good idea to save any ggplot you make as an object. It's a helpful practice for when you'll do more complicated graphing later (e.g. combining plots).
Run the code below to see what this basic histogram in ggplot looks like:
# Customize your ggplot
Let's go over some quick ways we can customize any ggplot. First, we can tell ggplot that we want the data from the two islands to be different colors. And second, we can to specify the colors we want to use.
**Different color for each group:** Within `aes( )`, we add a `fill = ` argument. Here is where you put the name of the variable that contains the categories that you want to distinguish with different colors.
You might be wondering why we don't use the `color= ` here instead (which is a valid argument for `aes( )`), and this is because we want to change the color of the *fill* of the bars, while `color = ` would change only the bar outline color (see below).
To choose colors ggplot should use, we need to add a new option ` + scale_fill_manual( )` and then specify the colors with the argument `values = `. To read more about how to create your own color scale, see this [page](https://ggplot2.tidyverse.org/reference/scale_manual.html). If you have more than one color you need to specify, make sure you combine them within the `c( )` function.
Colors in R can be specified in different ways. For example, you can use a string of the color name (see possible colors [here](http://sape.inf.usi.ch/quick-reference/ggplot2/colour)) or with [hex color codes](https://htmlcolorcodes.com/).
**Outline Color:** To change the color of the outline, specify `color = ` within the parentheses of the `geom_` (i.e. `geom_histogram`).
In the window below, we have added some options that you can play around with. Use the descriptions above to:
* Specify the variable that `fill` should be set to, as well as the colors for the fill and outline.
* Try out some color specifications on your own, and then check out the solution to see what we picked.
\
```{r, out.width="600px", echo= FALSE}
knitr::include_graphics("images/02_bellCurve/Painter.png")
```
**Playing around with "complete themes": ** ggplot has a nice way of changing many non-data display parameters at once though what is referred to as "complete themes". Check this [page](https://ggplot2.tidyverse.org/reference/ggtheme.html) for the available options.
* Have fun testing out a few different complete themes by adding the argument with a `+` sign.
* Try 3 different complete themes and take note of how the plot changes.
\
After trying different themes, you pick `theme_light()` and feel pretty good about your ggplot accomplishment. You send your plot to your PhD advisor, and within what feels like only minutes, you have a new attachment in your email inbox:
\
```{r, out.width="600px", echo= FALSE}
knitr::include_graphics("images/02_bellCurve/advisor_email.png")
```
You've got some more changes to make to your plot. Let's start with something easy:
(1) **Remove the space between the bars and the x-axis**: Use the `scale_y_continuous()` argument, and inside the parentheses specify `expand = ` followed by two numbers within the `c( )` command. These two numbers represent how much above or below the data's range you would like to extend the y-axis by.
The function `scale_y_continuous` can be used for other purposes. See some examples and more documentation can be found [here](https://ggplot2.tidyverse.org/reference/scale_continuous.html).
* Set axis limits
* Set axis breaks
* Transformations
\
(2) **Change axes labels**: Add `labs( )` to the existing ggplot layers and specify each axis you'd like to label as arguments, e.g. `x = `, followed by the string for your label. If you'd like to learn more about manipulations you can do with `labs( )`, see [this reference](https://ggplot2.tidyverse.org/reference/labs.html).
(3) In addition, `labs( )` can be used to **remove labels**. In this case, we can also include `fill = NULL` to remove the legend label (recall that the categories for our legend were determined by the `fill` argument in `aes()` previously).
Use the window below to:
* Remove the space and legend label
* Change the x-axis label to "Teacup Giraffe heights" and the y-axis label to "Frequency".
\
(4) **Remove panel border** and 5.**Remove minor grid lines**: To make detailed changes to the layout, we can add a `theme( )` argument. Nested within `theme( )` we can use additional arguments, such as `panel.border= ` and `panel.grid.minor= `. Many `theme( )` arguments can be set to `element_blank( )` to remove the element in question. To read more about what can be modifed with `theme( )`, check out this [resource](https://ggplot2.tidyverse.org/reference/theme.html).
* In the window below, use what you just learned to remove the panel border and the minor grid lines of the plot.
\
(6) **Move the Legend**: We will add two more arguments to `theme( )` to move the legend and make its background transparent. To change its position, use `legend.position = ` followed by the `c( )` command, in which you will specify the x- and y- positions. These values must be between 0 and 1. Specifying `c(0,0)`, for example, would place the legend at the bottom left of the plot, while `c(1,1)` would place it at the top right.
To change the legend background to be transparent, we essentially remove it. Add the argument `legend.background =`. Take a look at previous steps to determine how you remove an element.
\
Hopefully your PhD advisor in the homeland will be satisfied with your new ggplot!
\
# Things to think about {#bell-ttta}
Since we could not take the height of every giraffe on each of the two islands, and it is unclear how many giraffes live on the islands, we had to rely on taking the heights of randomly selected groups of giraffes.
A **sample**, in our case the 50 giraffes from each island, is a subset of a population. The **population** is defined as all available observations in a defined geographic area at a given point in time -- in this case, all existing giraffes on one of the islands while you are there.
If we pick our sample in a random way, then our hope is that our sample data will be representative of the population. The larger our sample is, the more of the population it will include, and thus, the more closely the sample will resemble the population in its statistical attributes (e.g the distribution). We then must acknowledge that the smaller our sample is, the less likely it is that it will be representative of the population.
The animation below illustrates how small samples can depart from the characteristics of the population.
* The panels below show samples that all come from the same population.
* Each frame of a panel, is a new sample drawn of the specified size.
* Observe that the smaller samples tend to:
+ Have oddly shaped distributions
+ Jump around a lot
**Take heed that with inadequate sample sizes, your sample data may barely resemble the population you're interested in!**
You decided that you had the resources to collect data on 50 giraffes on each of the islands. Will a sample of 50 be good enough to get a sense for the true values of the giraffe populations?
\
```{r fig.show="animate", animation.hook = 'gifski', fig.width=10, fig.height=2, echo=FALSE, message=FALSE, warning=FALSE, results = 'hide', cache=TRUE}
lims <- data.frame(min = c(0, 0, 0, 0), max = c(4, 16, 120, 1200))
cols <- c("brown1", "darkturquoise", "royalblue1", "darkorchid1")
ns <- c("10", "100", "1,000", "10,000")
plot1 <- function(x) {
d <- lapply(c(10, 100, 1000, 10000), function(x) {
d <- data.frame(x = rnorm(x), frame = x)
return(d)
})
p <- lapply(1:4, function(y) ggplot(data = d[[y]], aes(x)) + geom_histogram(binwidth = 0.25,
color = "white", fill = cols[y]) + theme_light() + theme(panel.border = element_blank(),
panel.grid.minor = element_blank(), panel.grid.major = element_line(size = 0.2),
axis.ticks = element_blank(), strip.background = element_blank(),
strip.text.x = element_text(color = "black"), axis.text.x = element_blank(),
plot.title = element_text(hjust = 0.5, size = 12)) + guides(fill = FALSE) +
labs(x = NULL, y = NULL) + scale_y_continuous(expand = c(0, 0),
limits = c(lims[y, 1], lims[y, 2])) + xlim(-5, 5) + ggtitle(paste0("N=",
ns[y])))
p <- cowplot::plot_grid(p[[1]], p[[2]], p[[3]], p[[4]], ncol = 4, align = "hv")
p
}
lapply(1:8, function(x) plot1())
```
================================================
FILE: 03_mean.Rmd
================================================
---
title: "Mean, Median, and Mode"
output:
bookdown::html_document2:
includes:
in_header: assets/03_mean_image.html
after_body: assets/foot.html
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, fig.align="center")
library(ggplot2)
#library(fGarch)
#library(gifski)
library(emdbook)
library(plotly)
library(here)
```
:::obj
**Module learning objectives**
1. Describe 3 measures of centrality
1. Explain the mathematical notation used for calculating the mean
1. Write a function which calculates the mean for any vector
1. Write a function which calculates the median for any vector
1. Describe how the reliability of a sample mean will scale with increasing sample size
:::
# What are measures of centrality?
\
```{r, echo=FALSE}
set.seed(12)
x <- rnorm(50, 10, 2)
x2 <- rnorm(50, 18, 1.2)
x <- data.frame(x = x, type = "Island #1")
x2 <- data.frame(x = x2, type = "Island #2")
d <- rbind(x, x2)
# p <- ggplot(data = d, aes(d$x, fill = d$type)) + geom_histogram(binwidth = 1,
# color = "white") + theme_light() + scale_fill_manual(values = c("green3",
# "turquoise3")) + labs(x = "Teacup Giraffe heights", y = "Frequency",
# fill = NULL) + scale_y_continuous(expand = c(0, 0)) + theme(panel.border = element_blank(),
# panel.grid.minor = element_blank(), axis.ticks.y = element_blank(),
# legend.position = c(0.165, 0.92), legend.background = element_blank())
#
# ggsave(filename = "/Users/Desiree/Documents/New R Projects/Cars/p.png",
# width = 5, height = 3, p)
```
```{r, out.width="500px", echo= FALSE}
knitr::include_graphics("images/03_mean/mean_hist.png")
```
\
You've just collected a lot data and graphed heights. Although informative, a graphical display of these data is difficult to summarize -- we need to describe these heights with a single number that will be meaningful and allow us to do statistics.
We can do this with a **measure of centrality**, the concept that one number in the "center" of the data set is a good summary of all the values. Below are examples of different measures of centrality.
* The **mean** is the average and the measure of centrality that you are probably most familiar with. This is a good measure to use when the data are normally distributed. We describe it in detail below.
* The **median** is the value in the middle of the data set. Half of the observations lie above the median and half below. When the data are normally distributed, the median and the mean will be very close to each other. When your data are not normally distributed (skewed to the left or right) the median is a more appropriate measure of centrality (see the animation below).
\
```{r, fig.show='animate', animation.hook='gifski', fig.width=6, fig.height=2, echo=FALSE, message=FALSE, warning=FALSE, results='hide', interval=0.5, cache=TRUE}
# skew <- seq(0.5, 1, 0.05)
# skew2 <- seq(1.1, 2, 0.1)
# skew3 <- seq(1.9, 1, -0.1)
# skew4 <- seq(0.95, 0.55, -0.05)
# skew <- c(skew, skew2, skew3, skew4)
# plot1 <- function(x) {
# d <- lapply(1:40, function(x) {
# d <- data.frame(x = rsnorm(1e+05, mean = 0, sd = 2, xi = skew[x]),
# frame = x)
# return(d)
# })
# medians <- c(seq(0.31, -0.31, -0.031), seq(-0.279, 0.279, 0.031))
# # medians <<- lapply(1:40, function(x) median(d[[x]]$x))
# p <- lapply(1:40, function(y) ggplot(data = d[[y]], aes(x)) + geom_histogram(binwidth = 0.25,
# color = "white", fill = "skyblue2") + theme_light() + theme(panel.border = element_blank(),
# panel.grid.minor = element_blank(), axis.ticks = element_blank(),
# axis.text = element_blank()) + guides(fill = FALSE) + labs(x = NULL,
# y = NULL) + scale_y_continuous(expand = c(0, 0), limits = c(0,
# 5600), breaks = c(0, 2000, 4000)) + geom_vline(xintercept = 0,
# size = 0.5, linetype = "dashed") + geom_vline(xintercept = medians[y],
# size = 0.5) + xlim(-5, 5) + annotate("text", label = "Mean", size = 3.4,
# x = -4.1, y = 5300, hjust = 0) + annotate("text", label = "Median",
# size = 3.4, x = -4.1, y = 4600, hjust = 0) + geom_segment(aes(x = -4.8,
# xend = -4.3, y = 5300, yend = 5300), linetype = "dashed") + geom_segment(aes(x = -4.8,
# xend = -4.3, y = 4600, yend = 4600)))
# print(p)
# }
#
# gif_file <- file.path(getwd(), "median.gif")
# save_gif(plot1(), gif_file = gif_file, progress = FALSE, loop = TRUE, delay = 0.5,
# width = 400, height = 133, res = 100)
#
# utils::browseURL(gif_file)
```

\
* The **mode** is the value (height, in our case) that occurs most frequently in the data set. It's not typically used in statistics, and we won't cover it further here.
\
# Taking the mean
The mean of a variable is the sum of its values, divided by the number of values.
This concept can be represented with equation below. In our case, each "x" represents a giraffe height (i.e. a single observation), and the numerical subscript indicates its order in the sample. We'll use ${\bar{x}}$ (read "x-bar") to represent the mean of the height variable.
\begin{equation}
(\#eq:equation1)
\Large{\bar{x}} = \frac{x_1 + x_2 + ... + x_n}{n}
\end{equation}
To make this more efficient, instead of writing "${x_1 + x_2 + ... + x_n}$", we can use the uppercase sigma symbol $\sum{}$ to represent summation of all the observations.
\
\begin{equation}
(\#eq:equation2)
\Large{\bar{x}} = \frac{\sum_{i=1}^{n}{x_i}}{n}
\end{equation}
\
This might look intimidating, but equation \@ref(eq:equation2) is really showing the same thing as \@ref(eq:equation1). Let's go through the steps again, breaking the symbols apart a bit (see annotated equation \@ref(eq:equation3) below). The sigma means 'add up'. What are we adding up? All the heights "x". The "i = " part indicates which term to begin with. For our purposes, this will always be the first observation, hence $i$ = 1. The character on top of the sigma is the last observation we include in our summation. In this case it's n -- because we're adding all n = 50 observations in each group of giraffes. In both equations, we still divide by the total number of observations in each group we have: again, n.
\
# Notation for sample vs population
Recall our discussion about a [sample versus a population](02_bellCurve.html#bell-ttta). Different symbols are used to represent the mean for each of these. We've already discussed $\bar{x}$ for the sample mean. The analogous symbol for the population mean is ${\mu}$ (read "mu"). Additionally, when referring to the size of the population, we will use a capital ${N}$ instead of a lowercase one.
\
# Code it up
Using \@ref(eq:equation2), it's easy to translate this equation into code in R. The heights recorded from island 1 have been stored in a vector called `heights_island1`. Below we show the first few observations from this vector, using the `head()` function.
```{r, echo=FALSE}
set.seed(12)
heights_island1 <- rnorm(50, 10, 2)
```
```{r, echo=TRUE}
head(heights_island1)
```
\
Use the interactive window below to calculate the mean "by hand".
# Create your own function
Now it's your turn to write your own function. Call it "my_mean" and have it calculate the mean of any given vector. You're going to use the rules for writing a function in R that you've used previously. As a reminder, you'll use `function()` and embed your code (that you completed in the window above) within curly brackets`{}`. The advantage of making a "homemade" function is that you can string together all the steps from the previous exercise into a single command.
You can also complete the exercise above in RStudio on your local computer. This way you will be able to save your `my_mean()` function and script for future use.
\
# Take a tea break!
```{r, out.width="600px", echo= FALSE}
knitr::include_graphics("images/03_mean/Teacup.png")
```
# Taking the median
To calculate the median go through the following steps:
* Assess whether there is an odd or even number of observations
* Order all observations from smallest to largest
* If an odd number, then the median is the middle value at position: (n + 1) / 2
* If an even number, then:
+ Find the value at the position: n / 2
+ Find the value at the position: (n / 2) + 1
+ The median will be the mean of the values at these two positions.
Before you write your own median function, two concepts need to be introduced: 1) the modulus operator `%%` and 2) `if...else` statements.
The **modulus operation** gives the remainder after division of one number by another. For example, in R `11 %% 5` returns the `1`, which is the remainer of `11` divided by `5`. If the modulus operation returns `0`, then there is no remainder. It is useful to apply the modulus operation `x %% 2` to determine whether a number `x` is even or odd by testing if the result is exactly equal to 0. See example code below.
* The animation above shows the values of means calculated from increasingly larger samples: small samples on the left and larger samples to the right (on the x-axis).
* Each point on the zig-zag line is the mean calculated from a random sample. The true mean of the population is 0.
* The y-axis shows what the mean is for a sample of that particular size. Though the y-values vary here, remember that if the sample were a good estimate of the population, the y-values should be very close to 0.
* You can see that when the samples are small the sample mean isn't necessarily a good representation of the population that it was sampled from--and that is not a good thing.
For further reading see the [Law of Large Numbers](https://en.wikipedia.org/wiki/Law_of_large_numbers).
================================================
FILE: 04_variance.Rmd
================================================
---
title: "The Spread of the Data"
output:
bookdown::html_document2:
includes:
in_header: assets/04_variance_image.html
after_body: assets/foot.html
---
:::obj
**Module learning objectives**
1. Describe the steps for constructing the sum of squares
1. Describe how the standard deviations can allow us to determine which data values are common or rare
1. Write a function for the variance and standard deviation
1. Explain why the sample variance would be downwardly biased if we did not correct it by diving by (n-1)
:::
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE, fig.align = "center")
library(ggplot2)
library(plotly)
library(here)
```
# Measures of spread
After successfully computing the mean, you return to the memory of the first day you had collected data. There was one teacup giraffe that was your favorite-- it was relatively small with purple spots and perky tail! You begin to wonder how rare it would be to encounter a giraffe smaller than this one. To answer this question, you need to be able to calculate a **measure of spread**.
{width=600px}
You might start by quantifying the simplest measure of spread, the **range**. This at least tells us the boundaries within which all the sample heights fall, but the range ignores important contextual information. For example, two data sets can have very different spreads but still have the same range.
{width=600px}
If we want to avoid undue influence of outliers for the measure of spread, the range is not good enough to provide us with a wholistic, robust measure.
What is a more stable measurement? The answer is the **variance**.
# Variance in plain language
You need a solid understanding of variance in order to grasp the mechanics of any statistical test. But what does the concept variance really capture?
* Recall the normal distribution: when we inspect the distributions below visually, we see that they all have the same mean, but some distributions are more spread out. Bell curves that are more "squished together" are composed of observations that are more similar to one another, while bell curves that are more "spread out" are composed of observations that have greater variability. Wider bell-curves mean greater variance! In plain language, the variance gives us an idea of how similar observations are to one another, and to the average value.

# How to calculate variance
Let's begin by going through the steps and equations for calculating the variance of a *population*. We'll explain how to modify this for calculating the *sample* variance later on.
First, the idea is to capture how far away individual observations lie from the mean. In other words, we could subtract the mean from each observation. This is called the **deviation** from the mean. And since we're calculating a *population* variance, we will use $\mu$ for the mean instead of ${\bar{x}}$.
Calculating the deviations is a great start, but we're back to the problem of needing to summarize multiple values. Since these newly calculated values are both negative and positive, we quickly realize that adding them up (like the first step when calculating the mean) would not be a productive idea since the negatives cancel the positives.
What's the easiest thing to do when you want to retain how far away a point is from the mean irrespective of whether it's above or below the mean? How about taking the absolute value?
Though the absolute value of the deviation would be a valid measure of distance from the mean, it turns out that it has some mathematical properties that don't make it the best choice, especially for more complex statistial analyses involving the variance later down the line.
# Why we square the deviations
There is an alternative with simpler, "better behaved" mathematical properties: **squaring the deviations**. Squaring will always give us positive values, so the values can never cancel each other out. It's worth pointing out, however, that a consequence of squaring deviations will tend to amplify the influence of values at extreme distances from the mean. You can read [this thread](https://stats.stackexchange.com/questions/118/why-square-the-difference-instead-of-taking-the-absolute-value-in-standard-devia) for a more detailed discussion about absolute values versus squared deviations.
# Sum of squares
Now we have positive, squared deviation values that can be summed to a single total. We call this total **the sum of squares**, and the equation is shown below.
The sum of squares is an important calculation that we will see again for other statistical operations. The animation below illustrates how these sums of squares are "constructed" starting with the sample observations and then squaring each one's distance away from the mean.
```{r fig.show="animate", animation.hook = 'gifski', fig.width=7.2, fig.height=4.8, echo=FALSE, message=FALSE, warning=FALSE, results = 'hide', interval=0.01666667, fig.align='center', cache = TRUE}
s <- data.frame(x=c(113, 146.5, 132, 70.5, 121, 55), y=c(8.75,1.25,3.75,3.75,6.25,1.25))
s <- s[order(s$x),]
s <- s[c(1,2,3,6,5,4),]
s2 <- s
s <- s[c(1,2,6,5,4,3),]
m <- mean(s[,1])
m2 <- 85
lim <- c(0, 60)
d <- data.frame(x1=s[,1], x2=rep(m, 6), y1=s$y, y2=(abs(s[,1]-m))+s$y)
co <- c("#6FB4CE", "#D97FDA", "#DC5F24", "#C46A79", "#f93188", "#F88336")
v <- do.call(cbind, lapply(1:6, function(x) seq(d[x,]$x1, d[x,]$x2, by = (d[x,]$x2-d[x,]$x1)/29)))
vv <- lapply(1:30, function(y) data.frame(x1=v[y,], x2=d$x1, y1=s$y, y2=(abs(s[,1]-m))+s$y))
pp <- function(x){
p1 <- ggplot()+geom_point(data=s, aes(x=x, y=y),color=co, size=4)+
theme_light()+ylim(lim)+geom_segment(aes(x = x2, y = y1, xend = x1, yend = y1), data = x, color=co, size=2)+labs(x="Teacup giraffe height", y=NULL)+theme(panel.border=element_blank(),panel.grid.minor=element_blank(), axis.ticks=element_blank())+
geom_segment(aes(x=m, xend=m, y=0, yend=lim[2]), linetype="dashed", color="black", size=2)+
annotate('text', x = 111.33, y = 53, label = "bar(x)",parse = TRUE,size=15)
p1
}
lapply(vv, function(x) pp(x))
h <- do.call(cbind, lapply(1:6, function(x) seq(d[x,]$y1, d[x,]$y2, by = (d[x,]$y2-d[x,]$y1)/29)))
hh <- lapply(1:30, function(y) data.frame(x1=s[,1], x2=rep(m, 6), y1=s$y, y2=h[y,]))
pp2 <- function(x){
p2 <- ggplot()+geom_point(data=s, aes(x=x, y=y),color=co, size=4)+
theme_light()+ylim(lim)+geom_segment(aes(x = x1, y = y1, xend = x2, yend = y1), data = d, color=co, size=2)+
geom_segment(aes(x = x1, y = y2, xend = x1, yend = y1), data = x, color=co, size=2)+labs(x="Teacup giraffe height", y=NULL)+theme(panel.border=element_blank(),panel.grid.minor=element_blank(), axis.ticks=element_blank())+
geom_segment(aes(x=m, xend=m, y=0, yend=lim[2]), linetype="dashed", color="black", size=2)+
annotate('text', x = 111.33, y = 53, label = "bar(x)",parse = TRUE,size=15)
p2
}
lapply(hh, function(x) pp2(x))
pp22 <- function(x){
p2 <- ggplot()+theme_light()+geom_rect(data=d, mapping=aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), color=alpha(co, x),fill=co, alpha=x/5, size=2)+ylim(lim)+geom_segment(aes(x = x1, y = y1, xend = x2, yend = y1), data = d, color=co, size=2)+
geom_segment(aes(x = x1, y = y2, xend = x1, yend = y1), data = d, color=co, size=2)+
geom_point(data=s, aes(x=x, y=y),color=co, size=4)+
labs(x="Teacup giraffe height", y=NULL)+theme(panel.border=element_blank(),panel.grid.minor=element_blank(), axis.ticks=element_blank())+
geom_segment(aes(x=m, xend=m, y=0, yend=lim[2]), linetype="dashed", color="black", size=2)+
annotate('text', x = 111.33, y = 53, label = "bar(x)",parse = TRUE,size=15)
p2
}
lapply(seq(0,1,0.1), function(x) pp22(x))
pp3 <- function(x){
p3 <- ggplot()+theme_light()+geom_rect(data=d, mapping=aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), color=co,fill=co, alpha=0.2, size=2)+
geom_point(data=s, aes(x=x, y=y), color=co, size=4)+
ylim(lim)+labs(x="Teacup giraffe height", y=NULL)+theme(panel.border=element_blank(),panel.grid.minor=element_blank(), axis.ticks=element_blank())+
geom_segment(aes(x=m, xend=m, y=0, yend=lim[2]), linetype="dashed", color="black", size=2)+
annotate('text', x = 111.33, y = 53, label = "bar(x)",parse = TRUE,size=15)
p3
}
lapply(1:15, function(x) pp3())
```
Once the squares have been "constructed", we sum their squares, producing a single value.

# Variance, $\sigma^2$
We need to take into account how many observations contributed to these sum of squares. So, we divide the sum of squares by N. This step essentially takes the average of the squared differences from the mean. This is the variance.
# Standard Deviation, $\sigma$
The problem with variance is that its value is not easily interpretable, the units will be squared and therefore not on the same scale as the mean. It would not be very intuitive to interpret giraffe heights written in *millimeters squared*! The **standard deviation** fixes that. We "un-square" the variance, and now we return to the data's original units (millimeters). The standard deviation equation is below:
\begin{equation}
(\#eq:equation3)
\Large \sigma = \sqrt{\frac{\sum_{i=1}^N (x_i - \mu)^2}{N}}
\end{equation}
# Population vs sample equations
One more thing: the equations above are for calculating the variance and standard deviation of a population. In real life applications, the population equations will almost never be used during data analysis. To calculate the variance and standard deviation for a sample instead, we will need to divide by n-1 instead of N, which we explain at the end of this module. Note that we also change to the corresponding symbols for the sample mean ($\bar{x}$), sample size (lowercase $n$), and use a lowercase $s$ in place of $\sigma$.
When we apply this change, our equation for the **sample variance, $s^2$ ** is:
\begin{equation}
(\#eq:equation4)
\Large s^2 = \frac{\sum_{i=1}^n (x_i - \bar{x})^2}{n-1}
\end{equation}
And for **sample standard deviation, $s$**:
\begin{equation}
(\#eq:equation5)
\Large s = \sqrt{\frac{\sum_{i=1}^n (x_i - \bar{x})^2}{n-1}}
\end{equation}
# Meaning of the standard deviation
Since we're now focusing on samples, let's think about how we can apply the standard deviation in a useful way to normal distributions to predict how "rare" or "common" particular observations in a data set may be. For the normal distribution, almost all of the data will fall within ± 3 standard deviations from the mean. This rule of thumb, called the **empirical rule**, is illustrated below and you can [read more about it here](https://newonlinecourses.science.psu.edu/stat200/lesson/2/2.2/2.2.7).

* The entire normal distribution includes 100% of the data. The empirical rule states that the interval created by **1 standard deviation above and below the mean includes 68% of all the data**. Observations within these bounds would be fairly common, but it would not be exceedingly rare to observe data that fall *outside* of these bounds.
* **2 standard deviations above and below the mean** encompasses approximately **95%** of the data. Observations that fall within these bounds include the common and also infrequent observations. Observations that fall *outside* of 2 standard deviations would be uncommon.
* **3 standard deviations above and below the mean** encompass **99.7%** of the data, capturing almost all possible observations in the set. Observations that fall oustide of these bounds into the extremes of distribution's tails would be exceedingly rare to observe (but still possible if you sample large enough groups to detect these rare events!).
# Example
Let's calculate the variance and standard deviation using 6 observations of giraffe heights from a subset of our data, including your favorite small one with the purple spots.
(1) **Calculate the sample mean**, $\bar{x}$:
```{r}
h <- c(113, 146.5, 132, 70.5, 121, 55)
mean(h)
```
We'll plot the mean $\bar{x}$ below with a gray line.

(2) **Find the deviation** from the mean, the difference between each giraffe's height and $\bar{x}$.
```{r}
deviation <- h - mean(h)
deviation
```

(3) **Calculate Variance**: Square the deviations, add them all up to get the sum of squares, and then take the average of the sum of squares (adjusted to "n-1" because we're using a sample).
```{r}
SS <- sum(deviation^2)
variance <- SS/(length(h)-1) # Divides by N-1
variance
```
(4) **Standard Deviation**: Take the square root of the variance.
```{r}
sqrt(variance)
```
Because the standard devation is a standardized score-- we can now focus on particular giraffes and see whether or not they lie within 1 standard deviation of the mean.

We see the little blue spotted giraffe is more than 1 standard deviation below the mean-- and so we can conclude that a little guy of his height is rather short-- even smaller than your favorite! Similarly, the giraffe with bright pink spots is taller than 1 standard deviation above the mean-- quite tall!
# Standard deviation application example
Using the standard deviation and the empirical rule described earlier, we now finally have the tools to answer our original question from the start of the module: how probable it is to find a giraffe smaller than our favorite purple-spotted one?
* Our giraffe of interest happens to be almost exactly 1 standard deviation below the mean, so this makes it easy to assess the probability of encountering a giraffe shorter than him.
* If we assume our sample comes from a normally distributed population, then **what percentage of giraffes will be shorter than the one with purple spots?**

We can apply the knowledge that the full percentage area under the curve is 100%, and what we know from the empirical rule, to conclude that there is approximately 16% of giraffes will be shorter than the one with purple spots. So, it would be common to find giraffes taller than our favorite but somewhat of a treat to find ones smaller--like the blue one!
Maybe this explains why the little blue spotted giraffe is so cute--- it is not so common to find ones so small!
# Code it up
Using \@ref(eq:equation4) and \@ref(eq:equation5), it's easy to translate the equations for the variance and standard deviation into code in R.
* In the window below, you will write two separate functions, one to calculate the sample variance and another to calculate the sample standard deviation. Name your functions `my_variance` and `my_sd`.
* Test your functions on the vector `heights_island1` and compare the output of your "handwritten" functions with the base R function of `var( )` and `sd( )`.
# Population vs Sample ($N$ vs $n-1$)
We have to correct the calculated variance by dividing by $n-1$. Let's explain why:
* Let's recall that when we calculate the sum of squares, we only have the sample mean $\bar{x}$ to go off of as our center point.
* We must first acknowledge that while the population $\mu$ is unknowable, the chance that the sample $\bar{x}$ and the population $\mu$ are the same is unlikely.
+ It's also worth pointing out that the risk that $\bar{x}$ and $\mu$ are not even values close to each other is much increased when $\bar{x}$ has been calculated from a small sample.
* Recognizing that the true population mean value is probably some *other* value than $\bar{x}$, let's recalculate the sum of squares. This time we will use an imaginary true population $\mu$ as our center point, which in the animation below will be represented with a line at an arbitrary distance away from $\bar{x}$.
```{r fig.show="animate", animation.hook = 'gifski', fig.width=5.04, fig.height=7, echo=FALSE, message=FALSE, warning=FALSE, results = 'hide', interval=0.01666667, fig.align = 'center', cache=TRUE}
s <- data.frame(x=c(113, 146.5, 132, 70.5, 121, 55), y=c(9,1.5,4,4,6.5,1.5))
s <- s[order(s$x),]
s <- s[c(1,2,3,6,5,4),]
s2 <- s
s <- s[c(1,2,6,5,4,3),]
m <- mean(s[,1])
m2 <- 85
lim <- c(-73, 60)
d <- data.frame(x1=s[,1], x2=rep(m, 6), y1=s$y, y2=(abs(s[,1]-m))+s$y)
co <- c("#6FB4CE", "#D97FDA", "#DC5F24", "#C46A79", "#f93188", "#F88336")
co2 <- c("grey30", "grey40", "grey80", "grey70", "grey60", "grey50")
s2$y <- (s2$y)*-1
s2[3,2] <- -9
s2 <- s2[c(1,2,4:6,3),]
d2 <- data.frame(x1=s2[,1], x2=rep(m2, 6), y1=s2$y, y2=(-abs(s2[,1]-m2))+s2$y)
v <- do.call(cbind, lapply(1:6, function(x) seq(d[x,]$x1, d[x,]$x2, by = (d[x,]$x2-d[x,]$x1)/29)))
vv <- lapply(1:30, function(y) data.frame(x1=v[y,], x2=d$x1, y1=s$y, y2=(abs(s[,1]-m))+s$y))
v2 <- do.call(cbind, lapply(1:6, function(x) seq(d2[x,]$x1, d2[x,]$x2, by = (d2[x,]$x2-d2[x,]$x1)/29)))
vv2 <- lapply(1:30, function(y) data.frame(x1.2=v2[y,], x2.2=d2$x1, y1.2=s2$y, y2.2=(abs(s2[,1]-m2))+s2$y))
vv <- lapply(1:30, function(x) cbind(vv[[x]], vv2[[x]]))
pp <- function(x){
p1 <- ggplot()+geom_point(data=s, aes(x=x, y=y),color=co, size=3)+geom_point(data=s2, aes(x=x, y=y),color=co2, size=3)+theme_light()+ylim(lim)+geom_segment(aes(x = x2, y = y1, xend = x1, yend = y1), data = x, color=co, size=1.5)+geom_segment(aes(x = x2.2, y = y1.2, xend = x1.2, yend = y1.2), data = x, color=co2, size=1.5)+labs(x="Teacup giraffe heights", y=NULL)+theme(panel.border=element_blank(),panel.grid.minor=element_blank(), axis.ticks=element_blank())+
geom_segment(aes(x=m, xend=m, y=0, yend=lim[2]), linetype="dashed", color="black", size=1.5)+
geom_segment(aes(x=m2, xend=m2, y=0, yend=lim[1]), linetype="solid", color="black", size=1.5)+
annotate('text', x = 80, y = -60, label = "mu",parse = TRUE,size=12)+
annotate('text', x = 111.33, y = 53, label = "bar(x)",parse = TRUE,size=12)
p1
}
lapply(vv, function(x) pp(x))
h <- do.call(cbind, lapply(1:6, function(x) seq(d[x,]$y1, d[x,]$y2, by = (d[x,]$y2-d[x,]$y1)/29)))
hh <- lapply(1:30, function(y) data.frame(x1=s[,1], x2=rep(m, 6), y1=s$y, y2=h[y,]))
h2 <- do.call(cbind, lapply(1:6, function(x) seq(d2[x,]$y1, d2[x,]$y2, by = (d2[x,]$y2-d2[x,]$y1)/29)))
hh2 <- lapply(1:30, function(y) data.frame(x1.2=s2[,1], x2.2=rep(m2, 6), y1.2=s2$y, y2.2=h2[y,]))
hh <- lapply(1:30, function(x) cbind(hh[[x]], hh2[[x]]))
pp2 <- function(x){
p2 <- ggplot()+geom_point(data=s, aes(x=x, y=y),color=co, size=3)+geom_point(data=s2, aes(x=x, y=y),color=co2, size=3)+theme_light()+ylim(lim)+geom_segment(aes(x = x1, y = y1, xend = x2, yend = y1), data = d, color=co, size=1.5)+geom_segment(aes(x = x1, y = y1, xend = x2, yend = y1), data = d2, color=co2, size=1.5)+
geom_segment(aes(x = x1.2, y = y2.2, xend = x1.2, yend = y1.2), data = x, color=co2, size=1.5)+geom_segment(aes(x = x1, y = y2, xend = x1, yend = y1), data = x, color=co, size=1.5)+labs(x="Teacup giraffe heights", y=NULL)+theme(panel.border=element_blank(),panel.grid.minor=element_blank(), axis.ticks=element_blank())+
geom_segment(aes(x=m, xend=m, y=0, yend=lim[2]), linetype="dashed", color="black", size=1.5)+
geom_segment(aes(x=m2, xend=m2, y=0, yend=lim[1]), linetype="solid", color="black", size=1.5)+
annotate('text', x = 80, y = -60, label = "mu",parse = TRUE,size=12)+
annotate('text', x = 111.33, y = 53, label = "bar(x)",parse = TRUE,size=12)
p2
}
lapply(hh, function(x) pp2(x))
pp22 <- function(x){
p2 <- ggplot()+theme_light()+geom_rect(data=d, mapping=aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), color=alpha(co, x),fill=co, alpha=x/5, size=1.5)+geom_rect(data=d2, mapping=aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), color=alpha(co2, x),fill=co2, alpha=x/5, size=1.5)+ylim(lim)+geom_segment(aes(x = x1, y = y1, xend = x2, yend = y1), data = d, color=co, size=1.5)+geom_segment(aes(x = x1, y = y1, xend = x2, yend = y1), data = d2, color=co2, size=1.5)+
geom_segment(aes(x = x1, y = y2, xend = x1, yend = y1), data = d, color=co, size=1.5)+geom_segment(aes(x = x1, y = y2, xend = x1, yend = y1), data = d2, color=co2, size=2)+geom_point(data=s, aes(x=x, y=y),color=co, size=3)+geom_point(data=s2, aes(x=x, y=y),color=co2, size=3)+
labs(x="Teacup giraffe heights", y=NULL)+theme(panel.border=element_blank(),panel.grid.minor=element_blank(), axis.ticks=element_blank())+
geom_segment(aes(x=m, xend=m, y=0, yend=lim[2]), linetype="dashed", color="black", size=1.5)+
geom_segment(aes(x=m2, xend=m2, y=0, yend=lim[1]), linetype="solid", color="black", size=1.5)+
annotate('text', x = 80, y = -60, label = "mu",parse = TRUE,size=12)+
annotate('text', x = 111.33, y = 53, label = "bar(x)",parse = TRUE,size=12)
p2
}
lapply(seq(0,1,0.1), function(x) pp22(x))
pp3 <- function(x){
p3 <- ggplot()+theme_light()+geom_rect(data=d, mapping=aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), color=co,fill=co, alpha=0.2, size=1.5)+geom_rect(data=d2, mapping=aes(xmin=x1, xmax=x2, ymin=y1, ymax=y2), color=co2,fill=co2, alpha=0.2, size=1.5)+geom_point(data=s, aes(x=x, y=y), color=co, size=3)+geom_point(data=s2, aes(x=x, y=y), color=co2, size=3)+ylim(lim)+labs(x="Teacup giraffe heights", y=NULL)+theme(panel.border=element_blank(),panel.grid.minor=element_blank(), axis.ticks=element_blank())+
geom_segment(aes(x=m, xend=m, y=0, yend=lim[2]), linetype="dashed", color="black", size=1.5)+
geom_segment(aes(x=m2, xend=m2, y=0, yend=lim[1]), linetype="solid", color="black", size=1.5)+
annotate('text', x = 80, y = -60, label = "mu",parse = TRUE,size=12)+
annotate('text', x = 111.33, y = 53, label = "bar(x)",parse = TRUE,size=12)
p3
}
lapply(1:15, function(x) pp3())
```
* When we compare the sum of squares in both of these scenarios: 1) using $\bar{x}$ or 2) using our imaginary $\mu$, we see that the sum of squares from $\mu$ will *always* be greater than the $\bar{x}$ sum of squares. This is true because by definition of being the sample mean, the line at $\bar{x}$ will always be the "center" of the values in our sample. Its location already minimizes the total distance of all the observations to the center. A line at any other location (i.e. $\mu$) would be a line that is not mimimizing the distance for observations in our sample.
{width=750px}
* Therefore, when we calculate the sum of squares (and consequently, the variance and the standard deviation) using the sample mean $\bar{x}$, we are most likely arriving at a value that is downwardly biased compared to what the true variance or standard deviation would be if we were able to know and use the population mean $\mu$.
* This is why we need to adjust our sample variance by diving by $n-1$ instead of just $N$. By diving by a smaller value (i.e. $n-1$ instead of N), we ensure that the overall value of the variance and standard deviation will be a little larger, correcting for the downward bias we just described.
# Things to think about
**How badly might the sample variance be downwardly biased?**: Well, it depends on how far away $\bar{x}$ is from the true $\mu$. The further away it is, the worse the downward bias will be!
* Of course, we want to avoid having a very downwardly biased variance. What controls how far away $\bar{x}$ is from $\mu$? The sample size! As pointed out previously, the larger the sample, the greater the likelihood that your sample mean will resemble the population mean.
* Press Play on the animation below. The plot shows the relationship between bias in the variance, the sample size, and the distance between $\bar{x}$ and $\mu$. Each dot represents one out of a thousand random samples all from the same population. The vertical dotted line represents $\mu$, and the horizontal dotted line represents the true population variance (animation inspired by Khan Academy [video](https://www.khanacademy.org/math/ap-statistics/summarizing-quantitative-data-ap/more-standard-deviation/v/simulation-showing-bias-in-sample-variance).)
```{r, tut=FALSE, echo=FALSE, message= FALSE, warning=FALSE, cache=TRUE}
set.seed(12)
d <- rnorm(50, 10, 2)
d1 <- rnorm(50, 18, 1.2)
d <- c(d,d1)
mm <- mean(d)
v <- var(d)
gen_data <- function(x){
d2 <- sample(size=sample(size=1, seq(from = 2, to = 10, by = 1)), d)
dd <- data.frame(mean=mean(d2), unb_var=var(d2), b_var=var(d2)* (length(d2) - 1) / length(d2), N=length(d2), perc=(var(d2)* (length(d2) - 1) / length(d2))/var(d))
}
data <- do.call(rbind, lapply(1:1003, function(x) gen_data()))
ff <- function(x){
d <- data[1:x,]
d$frame <- x+1
return(d)
}
pd <- do.call(rbind, lapply(seq(9, 1000, 10), function(x) ff(x)))
p <- data.frame(mean=Inf, unb_var=Inf, b_var=Inf, N=2, perc=Inf, frame=0)
pd <- rbind(pd, p)
vline <- function(x = 0, color = "black") {
list(
type = "line",
y0 = 0,
y1 = 1,
yref = "paper",
x0 = x,
x1 = x,
line = list(color = color, dash="dash", width=3)
)
}
hline <- function(y = 0, color = "black") {
list(
type = "line",
x0 = 0,
x1 = 1,
xref = "paper",
y0 = y,
y1 = y,
line = list(color = color, dash="dash", width=3)
)
}
m <- list(
l = 100,
r = 100,
b = 10,
t = 10,
pad = 4
)
p <- pd %>%
plot_ly(
width=700,
height=450,
type = 'scatter',
mode='markers',
x = ~mean,
y = ~b_var,
frame = ~frame,
color = ~N,
colors = c("midnightblue", "skyblue2"),
marker = list(size = 20, opacity=0.75),
hoverinfo="text",
text=~paste("Mean:", round(mean, 2), "\nVariance:",
round(b_var, 2))
)%>%
animation_opts(
frame = 1,
transition = 0,
redraw = FALSE
)%>%
config(displayModeBar = F) %>%
layout(margin=m,autosize=F,
xaxis = list(range=c(7,20.5), zeroline=FALSE, title="Mean"),
yaxis = list(range=c(-2,38), zeroline=FALSE, title="Biased variance"),
shapes = list(hline(v),vline(mm)))%>%
animation_slider(currentvalue = list(prefix = "Number of Samples: ", font = list(color="grey70", size=14)))
htmltools::save_html(p,here("images/04_variance/mega_dots.html"))
```
* When the samples whose means $\bar{x}$ are far off from the true population mean, they tend to have downwardly biased variance.
* Take a look at the points that are furthest away from the true population mean-- the samples represented by these points primarily came from small sample sizes (dark blue dots).
# How the correction works
The plot below shows the percentage of the true population variance that an uncorrected sample variance achieves on average. These data were generated by sampling from the same population as the animation above. This time the data have been grouped into bars by how many observations each random sample had. (Animation inspired by Khan Academy [video](https://www.khanacademy.org/math/ap-statistics/summarizing-quantitative-data-ap/more-standard-deviation/v/simulation-showing-bias-in-sample-variance))
```{r, tut=FALSE, echo=FALSE, message= FALSE, warning=FALSE, cache=TRUE}
data <- do.call(rbind, lapply(1:20000, function(x) gen_data()))
mm <- aggregate(b_var~N, data=data, function(y) (mean(y)/var(d))*100)
p2 <- mm %>%
plot_ly(
width=650,
height=350,
type = 'bar',
x = ~N,
y = ~b_var,
color = ~N,
colors = c("midnightblue", "skyblue2"),
marker = list(size = 2, opacity=1),
hoverinfo="y"
)%>%
config(displayModeBar = F) %>%
layout(margin=m,autosize=F,
yaxis = list(range=c(0,90), zeroline=FALSE, title="Biased Sample variance / Pop. variance (%)"),
xaxis = list(title="Sample Size"))%>%
hide_colorbar()
htmltools::save_html(p2, here("images/04_variance/static_bar.html"))
```
* Notice that the variances from smaller samples do the worst job of approaching 100% of the true variance. In fact, without correction the sample variance is downwardly biased by a factor of $n/(n-1)$.
* You can hover over the bars above to see what the average percentage of the true variance actually is for the different samples sizes. If we multiply this percentage by the correction, we fix the discrepancy between sample and population variance. We demonstrate this below for samples of size n = 3.
```{r, tut= FALSE}
n = 3
correction = n/(n-1)
hover_value = 67.22902 # % value when hovering over bar for n = 3
# Apply correction
percent_of_true_variance <- hover_value * correction
percent_of_true_variance
```
As we can see, the correction works by adjusting the downwardly biased sample variance to close to 100% of the true variance.
Try hovering over a few other bars and see yourself that correction works independent of the sample size. You can use the window below as a calculator to change the N and the hover values and then run the code.
================================================
FILE: 05_correlation.Rmd
================================================
---
title: "Covariance and Correlation"
output:
bookdown::html_document2:
includes:
in_header: assets/05_correlation_image.html
after_body: assets/foot.html
---
```{r setup, include=FALSE}
library(MASS)
library(plotly)
library(here)
library(ggplot2)
```
:::obj
**Module learning objectives**
1. Create a scatterplot using ggplot
1. Identify the similarities and differences between calculating the variance and covariance
1. Write a function for the covariance and Pearson's correlation coefficient
1. Interpret the meaning behind Pearson's correlation correlation
1. Describe the purpose of dividing by the product of the standard deviations when calculating the correlation.
:::
# Gathering data on another variable
Over the course of your time on the islands, you notice that the teacup giraffes seem to have an affinity for celery, which you have already used to entice so they come close enough for a height measurement. Suprisingly, one of the small ones had eaten so much of it! You decide to quantify how much celery each of the giraffes consumed to see if there is any relationship to height.
You systematically measure the amount of celery eaten and add it to your log of data, which is stored as a data frame called `giraffe_data`.
We can check out the first entries of the data frame by using the `head( )` command:
```{r, echo= FALSE}
set.seed(12)
x1 <- rnorm(50, 10, 2)
x2 <- scale(matrix(rnorm(50), ncol=1))
x12 <- cbind(scale(x1),x2)
c1 <- var(x12)
chol1 <- solve(chol(c1))
newx <- x12 %*% chol1
newc <- matrix(c(1,-0.52, -0.52, 1), ncol=2)
chol2 <- chol(newc)
finalx <- newx %*% chol2 * sd(x1) + mean(x1)
finalx[,2] <- (2*finalx[,2]-5)
giraffe_data <- finalx
colnames(giraffe_data) <- c("Heights", "Celery_Eaten")
giraffe_data <- as.data.frame(giraffe_data)
points <- giraffe_data[c(12, 50, 14, 43, 32),]
```
```{r}
head(giraffe_data)
```
# Make a scatter plot
It's difficult to get an idea if there's any relationship by just looking at the data frame. We learn so much more from creating a plot. Let's revisit our newly acquired ggplot skills to make a scatter plot.
A lot of the code used [previously](02_bellCurve.html) can be re-used for the scatter plot. Two main differences are the following:
(1) Because we now have an additional variable, we need to **assign a `y = ` for the `aes( )` command within `ggplot( )`**. Create the plot so that `Celery_Eaten` will be on the y-axis.
(2) **Add (`+`) a `geom_point( )`** element instead of `geom_hist( )`
Also, we will add lines to our plot, representing the mean of each variable. Here's how we'll do that:
(3) **Add a horizontal line** by adding (`+`) a `geom_hline( )` component. This takes the argument `yintercept = `, which equals the value for where the horizontal line should cross the y-intercept.
* Since we want to place this line at the mean of y variable (${\bar{y}}$), we can use the `mean( )` function instead of specifying a numeric value directly.
(4) **Add a vertical line** by following the same structure as above but using `geom_vline( )` and `xintercept = ` instead. This vertical line will represent the mean (${\bar{x}}$) of the heights.
Construct a scatter plot of the data using the window below:
Great, now you have a scatter plot! But recalling the email from your advisor the last time you plotted data, you decide you'd like to customize the look of the plot the same way you did when you created the ggplot histogram.
Play around with some aesthetics in the window below, and then run the solution to see what we chose.
Looking at the scatter plot, there seems to be a relationship between height and celery eaten, but you will need to quantify this more formally to be sure.
# Relationship between two variables
How can the relationship between two variables (and its strength) be quantified? This can be done by assessing how the two variables change together -- one such measure is the **covariance**.
The covariance elegantly combines the deviations of observations from two different variables into a single value. This is how it's done:
(1) As we did for the variance, we begin by measuring how far each observation lies from its mean. But unlike when we calculated the variance, each observation now includes two variables. We will need to **calculate the observation's distance from each variable's mean**. We call each distance the **deviation** scores on x and on y, respectively. Like the variance, the observation can fall above or below the mean, and as a result the corresponding deviation score will have either a positive or negative sign.
We observe this below with a subset of 5 points from our scatter plot. Positive deviations are shown in red, negative deviations in blue.
Why do we use the deviations? Because we want to know whether the x-values and y-values move together with respect to their means or not. For example, when an observation's deviation on x is above $\bar{x}$, will its deviation on y also be above $\bar{y}$? Using this line of thought, we can begin to systematically characterize how "together" the x and y values will change as we go through all observations.
# Crossproduct
After obtaining the deviation scores, we need to combine them into a single measure. We do not simply combine the deviation scores themselves by adding (please see the [page about the variance](04_variance.html) for a discussion of why this is). Instead, we take both deviation scores from the same observation and multiply them together to create a two-dimensional "shape" (analagous to when we multiplied a single deviation score by itself to create a square in the variance calculation).
**NOTE:** The resulting value is now on the order of a "squared" term. This will become important later.
(2) **Multiply the two deviation scores.** This is called the **crossproduct**. The shapes created by the crossproduct will serve as the "squared" terms that we can then use in the next step to sum and summarize the deviations into a single value.
The equation is shown below for the *sample* crossproduct of the deviation scores.
* First, we should note that the *overall sign* of the crossproduct will depend on whether or not the two x- and y- values from the same observation move in the same directions relative to their means. Look at the annotated plot below. The crossproducts will either create "negative" shapes (shown in blue) or "positive" shapes (in red). A third outcome is that the crossproduct could also be 0 -- this will occur when an observation falls on the mean.
* Second, the **magnitude of the crossproduct** will scale with the absolute value of the deviation scores. In other words, the further away both deviation scores are from their means, the larger the area of their shapes will be.
As we add the crossproducts, some of the "negative" and "positive" values of the shapes will cancel each other out. This is okay because this tells us important information about our two variables. If the negative and positive shapes cancel each other out completely, it would mean that there is no relationship. In most cases this will not happen, and the sum of the crossproducts will be positive or negative. In general, the larger the magnitude of the sum of the crossproducts, the more strongly the two variables move together. The equation is shown below.
# Covariance
(4) We then **divide the sum of the crossproduct by $n-1$ (or $N$ in the population equation)** so that we have taken into account how many observations contributed to this quantity. (Why $n-1$? See [here](04_variance.html).) This final number is called the **covariance**, and its value tells us how much our two variables fluctuate together. The higher the absolute value, the stronger the relationship.
The equation for the covariance (abbreviated "cov") of the variables x and y is shown below. As a preference of style, we multiply by $\frac{1}{n-1}$ instead of dividing the entire term by $n-1$.
# Problem of interpretation
However, the covariance is not an intuitive value. Remember that we have been working with terms that are on the "squared" scale, which is not only difficult to interpret (just like the variance is) but it is also the product of two variables on possibly different scales. How could we interpret a covariance with units of millimeters*grams mean?
Another point to make is that value of the covariance will be vastly different if we had decided to change the units (millimeters vs centimeters, or grams vs kilograms).
As a result, the covariance is not an easy metric to work with or to compare with other covariances. So we need to standardize it!
# Pearson correlation coefficient, $r$
How do we standardize the covariance?
The solution is to (1) take the standard deviations of each variable, (2) multiply them together, and (3) divide the covariance by this product -- the resulting value is called the **Pearson correlation coefficient**. When referring to the population correlation coefficient, the symbol $\rho$ (pronounced "rho") is used. When referring to the sample correlation coefficient, a lowercase $r$ is used (often called "Pearson's r").
Here is the equation for the **population correlation**:
This equation is used for the **sample correlation**:
\begin{equation}
(\#eq:equation5)
\Large r(x,y) = \frac{ \frac{1}{n-1} \sum_{i=1}^n (x_i - \bar{x})(y_i - \bar{y})}{s_x s_y}
\end{equation}
# What does the correlation mean?
We can interpret the correlation as a measure of **the strength and direction** of the relationship between two variables. It is a "standardized" version of the covariance.
* The correlation will always be between -1 and 1. At these extreme values, the two variables have the strongest relationship possible, in which each data point will fall exactly on a line. When the absolute value of the correlation coefficient approaches 0, the observations will be more "scattered".
* The sign of the correlation coefficient indicates the direction of the linear relationship. When $r$= 0 there is no relationship between the variables. Look at the figure below to see what observations of different $r$ values look like.
**Your turn**
Imagine you're given a plot like the one below. What would you say it's correlation value is? Try out your guess for a few plots, and if you need a hint to help you visualize, click *Show trend line*.
# Code it up
* In the window below, write your own function to compute the sample covariance of two variables, and call it `my_covariance( )`.
* Then create a second function called `my_correlation( )` in which you will compute the correlation of two variables. You may incorporate your function `my_covariance( )` in this step to save yourself some time.
* Once you've created both functions, use them to compute the covariance and correlation between `Heights` and `Celery_Eaten` within the data frame `giraffe_data`.
* Finally, compare your functions' outputs with the base R functions for covariance, `cov( )` and `cor( )`.
Remember, you will need to write both functions so that they will take two parameters, one for each variable. The parameters for `my_covariance( )` have been setup for you.
\
Wow, you can see that there is a negative relationship between giraffe heights and how much celery teacup giraffes eat. Could this be due to celery being a negative calorie vegetable? Are these giraffes onto something?
{width=30%}
# The Standardizer
You can take a look at the animation below to see a conceptual summary of how correlation will standarize the covariance, translating it into an easily interpretable metric that will always be bound by -1 and 1.
# Why divide by $\sigma_x\sigma_y$?
Well it's complicated, (see [here](https://www.quora.com/What-is-an-intuitive-explanation-for-why-the-sample-correlation-coefficient-is-equal-to-the-sample-covariance-divided-by-the-standard-deviations-of-x-and-y-multiplied-by-one-another) and [here](https://math.stackexchange.com/questions/158449/proving-that-the-magnitude-of-the-sample-correlation-coefficient-is-at-most-1)) but it builds on the mathematical principle that the covariance of x and y will never exceed the product of the standard deviations of x and y. This means that the maximum correlation value will occur when the absolute value of the covariance and the product of the standard deviations are equal.
If you don't take our word for it, press play below to see what the relationship between ${s}_x{s}_y$ and ${cov(x,y)}$ looks like.
```{r, tut=FALSE, echo=FALSE, message= FALSE, cache=TRUE}
cor_var <- function(x){
r <- 0
d <- mvrnorm(n = 6, mu = c(0,0), Sigma = matrix(c(1, r, r, 2), nrow = 2))
d <- round(d, 1)
return(d)
}
d <- lapply(1:5006, function(x) cor_var())
dd <- as.data.frame(do.call(rbind, lapply(d, function(x) cbind(var(x)[1,1], var(x)[2,2], var(x)[1,2], cor(x)[1,2]))))
colnames(dd) <- c("var_x", "var_y", "cov_xy", "cor")
dd$p_sd <- sqrt(dd$var_x)*sqrt(dd$var_y)
ff <- function(x){
d <- dd[1:x,]
d$frame <- x+1
return(d)
}
pd <- do.call(rbind, lapply(seq(99, 3000, 100), function(x) ff(x)))
p <- data.frame(var_x=Inf, var_y=Inf, cov_xy=Inf, cor=0, p_sd=Inf, frame=0)
pd <- rbind(pd, p)
pd <- round(pd, 2)
m <- list(
l = 100,
r = 100,
b = 10,
t = 10,
pad = 4
)
p <- pd %>%
plot_ly(
width=700,
height=450,
type = 'scatter',
mode='markers',
x = ~cov_xy,
y = ~p_sd,
frame = ~frame,
color = ~cor,
colors = c("#289BF8","#FF5E78"),
marker = list(size = 12, opacity=1),
hoverinfo="text",
text=~paste("Covariance:", round(cov_xy, 2), "\nPooled SD:",
round(p_sd, 2))
)%>%
animation_opts(
frame = 100,
transition = 0,
redraw = FALSE
)%>%
config(displayModeBar = F) %>%
layout(margin= m, autosize=F,
xaxis = list(range=c(-3,3), zeroline=FALSE, title="Covariance"),
yaxis = list(range=c(-0.1,4.5), zeroline=FALSE, title="Product of standard deviations")
)%>%
animation_slider(currentvalue = list(prefix = "Number of Samples: ", font = list(color="grey70", size=14)))
htmltools::save_html(p,here("images/05_correlation/cov_vs_sxsy.html"))
```
As you look at the plot above, you may have the following questions:
* Why are there clearly defined boundaries?
+ Because at the edges is where the covariance is the greatest value that it can be-- it is equal to the product of the standard deviations there.
+ The slope is 1 at this boundary.
* Where in the plot do the strongest correlations end up?
+ On the edges -- when the absolute value of the numerator and denominator of the equation are equal-- the quotient will = 1 (or negative 1, depending on the sign of the covariance in the numerator).
# Things to think about
**Correlation does not capture relationships that are not linear**: If the relationship is not linear, then correlation will not be meaningful. Check out the plot below. There is a clear U-shaped relationship between the two variables, but the correlation coefficient for these data is very close to 0. To measure non-linear relationships a different metric must be used.
**Correlation is not causation**: Just because there is a linear relationship between two variables does not mean we have evidence that one variable causes the other. Even if there really was a cause-and-effect relationship, with correlation we cannot say which variable is the cause and which is the effect. It's also possible that there exists some other unmeasured variable affecting the linear relationship we observe. And of course, any apparent relationship may be due to nothing more than random chance.
================================================
FILE: 06_standardError.Rmd
================================================
---
title: "Intro to Inference"
output:
bookdown::html_document2:
includes:
in_header: assets/06_standardError_image.html
after_body: assets/foot.html
---
```{r setup, include = FALSE}
library(ggplot2)
library(tweenr)
library(parallel)
library(MASS)
```
:::obj
**Module learning objectives**
1. Determine how to quantify the uncertainty of an estimate
1. Describe the concept of statistical inference
1. Interpret sampling distributions and explain how they are influenced by sample size
1. Define and calculate standard error
1. Use the standard error to construct 95% confidence intervals
:::
# How accurate is our estimate of the mean?
Let's revisit the first few days during which we collected data stored in the vector `heights_island1`. We were able to verify that the heights were normally distributed and calculated our sample mean, ${\bar{x}}$. However, we know that ${\bar{x}}$ is only an *estimate* of the true population mean, ${\mu}$, which is the true value of interest. It is unlikely that we will ever know the value of ${\mu}$, since access to all possible observations is rare. Therefore we will have to rely on ${\bar{x}}$ estimates from random samples drawn from the population as the best approximation of ${\mu}$.
Not all sample means are created equal. Some are better estimates than others. Recall the [animation](03_mean.html#mean_animation) showing the relationship between sample size and variability of the mean. As we learned from this animation, in the long-run, large samples are necessary to get an accurate estimate of ${\mu}$.
> **A note about language:** here, words like "accuracy", "precision", and "uncertainty" are used in a rather fast and loose way. We're using the laymen's application of these terms to refer to the long-run variability of estimates produced from repeated, independent trials. There are stricter, more formal statistical uses for these words, but for right now, we're going to ignore these nuances so that we can move on with understanding these concepts in broad strokes.
One reason we care about our sample estimate's accuracy is because we want to be able to answer questions about the population by making inferences. **Statistical inference** uses math to draw conclusions about the population based on a subset of the full picture (i.e. a sample). Subsets of data are of course limited, so it's therefore important to acknowledge that the strength of the conclusions drawn about the population is dependent on the precision of the sample estimate. For example, say that we guess that the population mean value of giraffe heights on Island 1 is less than 11 cm. We can make some inferences about whether or not this is a good guess based on what we learn from our sample of giraffe heights. We'll revisit this question a few times below.
# Creating a sampling distribution
The mean of our sample of 50 giraffes from Island 1 was:
```{r, echo=FALSE}
set.seed(12)
heights_island1 <- rnorm(50, 10, 2)
```
```{r}
mean(heights_island1)
```
How can we quantify the accuracy of this estimate, given its sample size?
In theory, one way to illustrate this is to generate data not just from a single sample but from many samples of the same size (N) drawn from the same population.
Imagine that after you collected all 50 measurements for `heights_island1`, you wake up one morning with no memory of collecting data at all---and so you go out and collect 50 giraffe heights again and subsequently calculate the mean. Further imagine that this groundhog day (or more correctly, groundhog *week*) situation repeats itself many, many times.
When you finally return to your sanity, you find stacks of notebooks filled with mean values from each of your individual data collections.
Instead of viewing this as a massive waste of time, you make the best out of the situation and create a histogram of all the means. In other words you create a plot showing the distribution of the sample means, also known as a **sampling distribution**.
The animation below illustrates the process of creating the sampling distribution for 1,000 sample means.
On the left side, each histogram represents a sample (e.g. `heights_island1` would be one sample, and we're flashing through 1,000 of them in total). Correspondingly, each dot signifies an observation. After each sample histogram is completed, ${\bar{x}}$ is calculated. This ${\bar{x}}$ value is then subsequently added to the histogram of the sampling distribution on the right. As you can see below, this process is repeated, allowing the sampling distribution to build up.
Looking at the spread of ${\bar{x}}$ values that this groundhog experience generated, we can get a sense of the range of many possible estimates of ${\mu}$ that a sample of 50 giraffes can produce.
**The sampling distribution provides us with the first hint of the precision of our original `heights_island1` estimate**, which we'll quantify in more detail later on, but for now it's enough to notice that the range of possible ${\bar{x}}$ values are between `r round(min(d2$mean),1)` and `r round(max(d2$mean), 1)`. This means that ${\bar{x}}$ values outside of this range are essentially improbable.
Let's return to our question about whether the true mean of giraffe heights on Island 1 is less than 11 cm. Our sampling distribution suggests that ${\mu}$ *is* less than 11 cm, since values greater than that are not within the range of this sampling distribution.
# Sample size and sampling distribution
Back to the idea that larger samples are "better", we can explore what happens if we redo the groundhog scenario, this time sampling 500 individuals (instead of 50) before taking the mean each time, repeating this until thousands of ${\bar{x}}$ values have been recorded. For completeness, let's imagine the same marathon data collection using samples that are smaller---of 5 giraffes each. We compare the resulting sampling distributions from all three scenarios below. The middle sampling distribution corresponds to the sampling distribution we already generated above.
What do we notice?
1) All histograms look normal.
2) All distributions have approximately the same mean.
3) Distributions generated from larger samples are less dispersed.
We can take the mean of the sampling distribution itself-- **the mean of the sampling distribution is a mean of means.** This mean can be interpreted to be the same as a mean that would have resulted from a single large sample, made up of all the individual observations from each of the samples whose ${\bar{x}}$ values are included in the sampling distribution.
Note that if we had only generated a sampling distribution made up of samples of 5 giraffes, we would not have been able to exclude 11 cm as a possible value for ${\mu}$. In fact, if we were to draw a vertical line in the middle of each of the sampling distributions (the mean), we can tell that the population mean is likely even less than 10 cm.
In the following window, you will test the relationship between sampling distribution and sample size. The function below (behind-the-scenes code not shown) will plot a sampling distribution made up of 1000 samples, with each sample containing `N` number of observations. Try setting `N` to a few different values. What does the resulting sampling distribution looks like? See if you can confirm for yourself that the above points are true.
# Standard Error of the Mean
As we've done before, we want to summarize this spread of mean estimates with a single value. We've already learned how to quantify a measure of spread--the standard deviation. If we take the standard deviations of each of the three different sampling scenarios above, then we accept that *distributions based on smaller samples should have larger standard deviations*.
In the window below, calculate the standard deviation of each of the three sampling distributions (i.e. for N = 500, N = 50, and N = 5), and confirm that the italicized point above is true. (If you're working in R locally, use your "homemade" standard deviation function from the [Variance](04_variance.html) module.)
To complete this exercise, you will need to use the objects `sampling_distribution_N500`, `sampling_distribution_N50`, `sampling_distribution_N5`, which are vectors storing the thousands of ${\bar{x}}$ values from the corresponding groundhog sampling distributions.
When you calculate the standard deviation of a sampling distribution of ${\bar{x}}$ values, you are calculating the **standard error of the mean (SEM)**, or just "standard error". The SEM is the value that we use to capture the level of precision of our sample estimate. But, we need a better and more efficient way to arrive at this value without relying on a groundhog day situation. Keep reading to learn more.
> **A note about SEM:** Here "standard error" will imply standard error of the *mean*. But we can technically calculate the standard error of any sample statistic, not just the mean. We'll talk about that more in future modules.
# Standard error in practice
Deriving the equation used for calculating the standard error of the mean using theory (i.e. without going out and resampling MANY times) is a bit complicated, but if you're interested, you can learn more about it [here](https://stats.stackexchange.com/questions/89154/general-method-for-deriving-the-standard-error). Instead, we can capture the relationship between **standard deviation**, **sample size**, and **standard error** with the plot below.
The standard deviation in this plot is `2.1`, which represents ${\sigma}$ for giraffe heights on Island 1. This population value is technically still unknown but can be deduced in theory by repeating the groundhog day example for the standard deviation instead of for the mean. It's important to note that the plot would have the same *shape* regardless of what scenario or standard deviation we were using.
**Can you figure out what the equation is for the SEM?** Look at the plot above, hover over the points, and see if you can gather how standard error of the mean, standard deviation, and sample size are related. Here are some hints:
* SEM will be on one side of the equation, standard devation, and N will be on the other.
* The equation will involve division.
* There is one more missing piece of the puzzle: When you look at the shape of the plot above. What type of function does this remind you of? We haven't covered this explicitly, but take a look [here](https://www.mathsisfun.com/sets/functions-common.html) and see if you get any ideas.
Use the window below as a calculator to see if you can figure out the equation for the SEM.
In case you weren't able to figure it out, remember to check the `Solutions` tab in the exercise window or take a look at this [link](https://en.wikipedia.org/wiki/Standard_error) for the equation for calculating the SEM. Recall that we're working with the sample (and not population) standard deviation ($s$), so make sure you find the correct equation.
# Confirming that the SEM equation works
Let's test out the SEM equation on our original sample of `heights_island1` and compare it to what we would have gotten by taking the standard deviation of the sampling distribution example with the N= 50 case. **Does the SEM seem like a good approximation of the standard deviation of the sampling distribution?**
Below, you will use the object `heights_island1`, which contains our single sample of N=50, and the object `sampling_distribution_N50`, which contains the data from the corresponding groundhog sampling distribution.
Close enough! We wouldn't expect these to be *exactly* the same because of sampling variability.
# How do we apply the SEM?
Now that we have a better understanding of how to gauge the precision of our sample estimates, we can test our question about the ${\mu}$ being less than 11 cm once and for all.
To formally make inferences, we need to revisit the principles of the [empirical rule](04_variance.html#empirical) to construct confidence intervals. (Confidence intervals are just one way to make inferences-- we'll discuss other ways later.)
Remember, that the SEM is just the standard deviation of the sampling distribution, so we can apply the empirical rule. As a result, ± 2 SEM from a point estimate will capture ~95% of the sampling distribution. Actually, we were a little bit sloppy earlier when we said 2 standard deviations captures 95% of a normal distribution; this will actually give you 95.45% of the data. The true value is 1.96 standard deviations--and this is what we use to construct a 95% confidence interval (CI).
Loosely speaking, a 95% CI is the range of values that we are 95% confident contains the true mean of the population. We want to know whether our guess of 11 cm falls outside of this range of certainty. If it does -- we can be sure enough that the true ${\mu}$ of giraffe heights on Island 1 is less than 11 cm.
Use the window below to find out and make your first inference by constructing the 95% CI for the `heights_island1` mean estimate!
The upper limit of our 95% CI is less than 11 cm, so the population mean of heights on island 1 is likely less than 11 cm. In the scientific community, this is a bonafide way of drawing this conclusion.
# Things to think about
We've been a little fast and loose with our words. The formal definition of CIs is the following:
**If we were to sample over and over again, then 95% of the time the CIs would contain the true mean.**
Importantly, some examples of what the 95% CI does NOT mean are:
* A 95% CI does **not** mean that it contains 95% of the sample data.
* A CI is not a definitive range of likely values for the sample statistic, but you can think of it as estimate of likely values for the population parameter.
* It does not mean that values outside of the 95% CI have a 5% chance of being the true mean.
The precise interpretation of CIs is quite a nuanced and rather hotly debated topic [see here](https://featuredcontent.psychonomic.org/confidence-intervals-more-like-confusion-intervals/) and becomes somewhat philosophical-- so if these definition subtleties seem confusing, don't feel bad. As mentioned in the blog post linked above, one recent paper reported that 97% of surveyed researchers endorsed at least one misconception (out of 6) about CIs.
================================================
FILE: 07_ttest.Rmd
================================================
---
title: "t-test"
output:
bookdown::html_document2:
includes:
in_header: assets/07_tTest_image.html
after_body: assets/foot.html
---
:::obj
**Module learning objectives**
1. Determine how to quantify the uncertainty of an estimate
1. Describe the concept of statistical inference
:::
# The Two Islands
You'd like to make an [inference](06_standardError.html) to formally investigate whether you have any reason to believe that the mean of heights on Island 1 differs from the mean of the heights on Island 2.
The means of the heights of both islands are shown below:
```{r, echo= FALSE}
set.seed(12)
heights_island1 <- rnorm(50, 10, 2)
heights_island2 <- rnorm(50, 18, 1.2)
```
```{r}
mean(heights_island1)
mean(heights_island2)
```
It's obvious that the mean of the two **samples** we took are different, but the main question is if the **population** means are different?
This question is of interest because if the two population means are different, it may indicate that a cool evolutionary story is at play. For example, based on your experience it seems clear that the two groups of giraffes have been isolated. Their tiny stature would make it near impossible to move back and forth between the two islands, hence impeding mixing between the two groups. Over time, selection pressures could then have made the two groups distinct regarding height.
How do we test this?
# Testing for group difference
When testing for group mean differenes, [it is much more common for a researcher to be interested in the **difference** between means than in the specific values of the means themselves. ] stolen
When we focus on the **mean difference**, represented by $\Delta_{\bar{x}}$ (read "delta x-bar"), we can ask: is the $\Delta_{\bar{x}}$ meaningfully different from 0?
The sample mean difference is below:
```{r}
mean(heights_island2) - mean(heights_island1)
```
This is just another estimate at the sample level whose precision we need to quantify to be able to make inferences.
Formally, statistical inference is about testing hypotheses (which we intentionally did not introduce in the previous module). In research, a **hypothesis** is a statement of a suggested outcome for a study [THIS SENTENCE IS TOO VAGUE]. The goal of statistcal inference is to reject the **null hypotheses** ($H_0$, read "H-nought"), the default suggested outcome, which assumes that there is no association or difference between two or more groups. If we cannot reject $H_0$, then it means we must accept the alternative, $H_A$, that there *is* a meaningful difference or association.
Our null hypothesis is the following: the mean difference between the two populations is 0. The corresponding equations are shown below for the null and alternative hypotheses. Here, we use $\Delta_{\mu}$, because we are referring to the population values.
$H_0$ : $\Delta_{\mu}$ = 0
$H_A$ : $\Delta_{\mu}$ $\neq$ 0
[MAKE EQUATION??]
One way to reject the null hypothesis would be to construct 95% CIs around the estimate for $\Delta_{\bar{x}}$.
# Pooled standard deviation
As we learned previously, a prerequisite to calculating 95% CIs is to calculate the standard error (which we know will require the sample standard deviation). However, we're now working with two samples, so which sample's standard deviation do we use? Can we use both?
In this particular case, the standard error of the mean difference can be calculated based on a *combined*, or **pooled standard deviation** ($s_{p}$) from two samples. We use the standard deviation of each sample, weighted by sample size.
\begin{equation}
(\#eq:equation1)
\Large s_{p} = \sqrt{\frac{s^2(n_1-1) + s^2(n_2-1)}{(n_1-1) + (n_2-1)}}
\end{equation}
One thing to point out is that standard deviations from samples cannot be added together -- but variances can be, which is why in (1) we use $s^2$ in the numerator only to take then the square root of the entire term.
We see that the variances are being multiplied by a term containing the sample size ($n$). In the case that each of our samples were differently sized, we would want to more heavily weight the variance of the sample that was larger. In our case now, both of our samples have the same $n$, but we introduce the equation for more general cases.
If you're wondering why $n-1$ is necessary for the weighting, and not just $n$, hold that thought and we'll return to it later. But for now, we'll just point out that it is *not* to adjust for the inherent bias when calculating sample variance (i.e. not the same reason that we used N-1 in the variance module [link]).
```{r, include=FALSE}
tutorial::go_interactive(height = 160)
```
[DATA CAMP WINDOW WHERE THEY WILL CALCULATE THE S_P OF THE DATA]
# Standard Error (SE) of the mean difference
After calculating the pooled standard deviation, the next step is to determine the standard error of the mean difference. We will follow the same logic as we did when calculated the standard error based on a single sample -- except now we will use the pooled standard deviation. Again, we will be adding the terms from the two samples together, so to be able to sum these we need to use the pooled *variance* (the square of the pooled standard deviation.)
[ annotated?]
\begin{equation}
(\#eq:equation2)
\Large SE_{({\bar{x_1}-\bar{x_2})}} = \sqrt{\frac{s_p^2}{n_1} + {\frac{s_p^2}{n_2}}}
\end{equation}
[DATA CAMP WINDOW WHERE THEY WILL CALCULATE THE SE OF THE MEAN DIFF]
Now that you know the standard error, you are ready to construct the 95% CI around the mean difference. As before, this will be the mean difference plus/minus 1.96 * $SE_{({\bar{x_1}-\bar{x_2})}}$.
Now we can see whether or not our null hypothesis can be rejected. If the 95% CI for values of ${({\bar{x_1}-\bar{x_2})}$ includes 0, then we do not have reason to believe that the population means are different.
[Construct interval for yourself below, DATA CAMP, INSTRUCTIONS]
```{r, include=FALSE}
tutorial::go_interactive(height = 160)
```
```{r ex="ttest2", type="pre-exercise-code"}
set.seed(12)
heights_island1 <- rnorm(50,10,2)
heights_island2 <- rnorm(50, 18, 1.2)
```
```{r ex="ttest2", type="sample-code"}
# Calculate the mean diff
mean_diff <-
# Calculate the Sp for heights_island1 and heights_island2
Sp <-
# Calculate the SE_meandiff
SE_meandiff <-
# Add ± 1.96 SEM to the sample mean to construct the upper and lower bounds of the 95% CI
upperCI <-
lowerCI <-
meandiff_95CI <- c(lowerCI, upperCI)
meandiff_95CI
```
```{r ex="ttest2", type="solution"}
# Calculate the mean difference
mean_diff <- mean(heights_island2) - mean(heights_island1)
# Calculate the Sp for heights_island1 and heights_island2
N1=50
N2=50
Sp <- sqrt(((sd(heights_island1)^2)*(N1-1) + (sd(heights_island2)^2)*(N2-1))/((N1-1)+(N2-1)))
Sp <- sqrt((var(heights_island1)*(49) + var(heights_island2)*(49))/(N1+N2-2))
# Calculate the SE_meandiff
SE_meandiff <- sqrt(Sp^2/N1 + Sp^2/N2)
# Add ± 1.96 SEM to the sample mean to construct the upper and lower bounds of the 95% CI
upperCI <- mean_diff + 1.96*SE_meandiff
lowerCI <- mean_diff - 1.96*SE_meandiff
meandiff_95CI <- c(lowerCI, upperCI)
meandiff_95CI
```
Our 95% CI does not include 0 by a lot! So we can conclude that the population means from Island 1 and Island 2 are mostly likely distinct. In other words, we can reject $H_0$, the null hypothesis. In doing so, we say that the mean difference is *statistically significant*.
[T test function will calculate the 95CI using the t distribution @ 97.5%m, which doesn't always give 1.96]
# P-values
Even though CIs are great tools for inference, you are probably most familiar with seeing p-values in scientific literature. The **p-value** that is output from your statistical test gives you a metric that tells you whether or not your results are statistically significant. Typically, p-values < 0.05 meet this criterion.
The "p" in p-value stands for "probability". Probability of what? The **p-value** is the probability that you would have gotten your results or something more extreme if in fact the null hypothesis were true. "More extreme", in this case, would be a mean difference even greater than what we see in the present data. The lower the p-value, the less likely you'd be getting your results due to chance alone.
Now let's derive the p-value for the mean difference of the two island heights, and make sure that we can draw the same conclusion that we did when we used the 95% CI for inference. In order to do this, we will use a statistical test for comparing two groups: the **t-test**.
# The t-test
The t-test is all about the **t-statistic**, which is produced when the mean difference is divided by the standard error. When we divide by the standard error, we turn our mean difference estimate into a unitless metric that is not dependent on the scale that means were recorded with (i.e. centimeters). Furthermore, dividing by the standard error also will also account for the uncertainty in the estimate.
- The t-statistic is essentially combining whatever our sample estimate is (e.g. mean difference, sample mean, etc.) and its uncertainty (i.e. standard error) into one value.
- we convert the t-statistic into a p-value via the a specific kind of distribution called the t-distribution. [plot, here's an example of one]
- the further out in the tails of the t-distribution that the t-statistic falls. The lower the pvalue will be.
(3) Degrees of freedom; Hasse has some ideas.
(4) A formal definition of hypothesis including null hypothesis.
Implement, and then formally answer the question.
# Degrees of Freedom
- You know the final answer of a single estimate. You can pick whatever you want, until you're down to the last choice....then you don't have the "freedom". --> explains why we use Total -1 = DF.
- Explain Whyyyyyyyyy we need it.
- The t-distribution will look different depending on the degrees of freedom
Intermediate step here between CI and p-values.
T-test statistic greater than 1.96 will result in a significant p-value.
Write your own t-test function.
Start with same N, but then “to make it a more useful model, you need to be able to handle when the sample sizes are not the same.”
**Unequal variance: more fodder for things to think about**
================================================
FILE: Giraffe.Rproj
================================================
Version: 1.0
RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default
EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8
RnwWeave: Sweave
LaTeX: pdfLaTeX
BuildType: Website
================================================
FILE: README.md
================================================
# Teacups, Giraffes, & Statistics
An open resource which uses R Markdown to create a series of modules for learning R basics and statistics. Built with R Markdown in RStudio. This project is ongoing, and more modules are in development.
Created by:
* [Hasse Walum](https://github.com/haswal), Emory University, USA
* [Desirée De Leon](https://github.com/dcossyleon), Emory University, USA
This repository holds the source materials for this project. To view the online modules, please visit: https://tinystats.github.io/teacups-giraffes-and-statistics/.
### Licensing
The illustrations and [narrative](https://tinystats.github.io/teacups-giraffes-and-statistics/00_narrative.html) in this project are licensed under the [cc-by-nc-nd](https://creativecommons.org/licenses/by-nc-nd/2.0/) license. This means that these specific parts of the project cannot be modified or remixed if you use our materials.
Everything else in this project is licensed under the [cc-by-nc-sa 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/) license. In summary, this means that you can adapt the lesson material to suit your needs as long as you cite the original project and authors.
# Getting Started
[**Intro to R:**](https://tinystats.github.io/teacups-giraffes-and-statistics/01_introToR.html) This module will introduce you to some basic tools and terminology that you will need to be familiar with to complete the modules. You can work through the exercises or use this page as a reference.
# The Modules
1. [**Introduction to the Normal Distribution.**](02_bellCurve.html) This module introduces the basics to visualizing data using `ggplot` and evaluating the data for normality.
2. [**Mean, Median, Mode.**](03_mean.html) Write your own functions for calculating the median and mean.
3. [**Spread of the Data**](04_variance.html). Write your own functions for calculating the variance and the standard deviation.
4. [**Covariance and Correlation**](05_correlation.html) Write your own function to quantify the strength of the relationship between two variables.
5. [**Standard Error, Introduction to Inference**](06_standardError.html)
## Planned Modules
6. T-Test, Inference Part II
7. ANOVA
8. Linear Regression
9. Revisiting T-Tests, ANOVA, and Regression: Assumptions & Violations
10. Bootstrapping
11. Permutation
12. Simulation & Power
13. Winner's Curse
14. Multiple Comparisons & Questionable Research Practices
# apps
================================================
FILE: _bookdown_files/02_bellCurve_cache/html/__packages
================================================
here
ggplot2
plotly
tweenr
================================================
FILE: _bookdown_files/03_mean_cache/html/__packages
================================================
here
ggplot2
plotly
tweenr
emdbook
================================================
FILE: _bookdown_files/03_mean_cache/html/unnamed-chunk-3_10110fd506496d65a16c9f8d338a2668.rdb
================================================
================================================
FILE: _bookdown_files/04_variance_cache/html/__packages
================================================
here
ggplot2
plotly
tweenr
emdbook
================================================
FILE: _bookdown_files/05_correlation_cache/html/__packages
================================================
here
ggplot2
plotly
tweenr
MASS
emdbook
================================================
FILE: _bookdown_files/06_standardError_cache/html/__packages
================================================
here
ggplot2
plotly
tweenr
MASS
emdbook
================================================
FILE: _site.yml
================================================
name: "cars"
output_dir: "docs"
navbar:
title: " "
left:
- icon: "fa-home"
href: index.html
- text: "History of Teacup Giraffes"
href: 00_narrative.html
- text: "Modules"
menu:
- text: "Intro to R"
href: 01_introToR.html
- text: "Intro to the Normal Distribution"
href: 02_bellCurve.html
- text: "Mean, Median, Mode"
href: 03_mean.html
- text: "Spread of the Data"
href: 04_variance.html
- text: "Covariance and Correlation"
href: 05_correlation.html
- text: "Standard Error"
href: 06_standardError.html
- text: "About the Authors"
href: aboutTheAuthors.html
right:
- icon: "fa-github"
href: https://github.com/tinystats/teacups-giraffes-and-statistics
output:
bookdown::html_document2:
toc: true
toc_float: true
theme: cosmo
number_sections: false
================================================
FILE: aboutTheAuthors.Rmd
================================================
---
title: "About the authors"
output:
bookdown::html_document2:
includes:
in_header: assets/aboutTheAuthors_image.html
toc: false
---
================================================
FILE: assets/00_narrative.html
================================================
Teacup giraffes were first discovered by the Swedish naturalist and priest Olof Torén, an apostle of Carl Linnaeus, during his travels on the Swedish East India Company ship Hoppet. Departing from the port of Gothenburg, Hoppet set sail on the 26th of January 1748, an extraordinarily gloomy day when sea and sky was a single ashen entity and winds from the northwest brought a snow-mixed horizontal rain. The mood onboard the ship was cheerful nonetheless, as the crew dreamt of the transparent waters of southern seas, filled with colorful beings and stories to bring back home.
One suffocating evening in July 1748, shortly after Hoppet had rounded the Cape of Good Hope and sailed east of Madagascar, captain Erik Moreen was attacked by a headache so vicious it made him seasick for the first time since the beginning of his nautical career. Desperate to ease the pain he called on the ship surgeon, a crooked man who spent most of his time investigating fungi in the deepest corners of the ship’s bilge where the air tasted like year old bread.
The surgeon brought a small bottle containing a beverage brewed of equal parts fermented goat milk and a distillate of mushrooms which, when dissolved, produced such a colorful luminance that the use of protective goggles was necessary to prevent achromatopsia. Ignoring the surgeon’s recommendation to limit the dose to a few drops of the brew, the captain emptied the bottle in one gulp and for a few moments lost all senses while the hair on the back of his head spun into permanent curls.
Although the pain subsided within less than thirty minutes, this was just the beginning of a nightmarish intoxication. The captain heard sirens sing lost lullabies from all directions, saw the kraken swallow the moon and spit it out as thousands of stars, and was convinced that all of his crew had turned into sharp-stemmed ferns, stationary and threatening to amputate him with every wavering step he took across the deck. During this confusion the captain tied himself to the fore-mast, prepared to fall head first into the flaming depths of the underworld. He was standing there as the sun rose, as his inebriation started to fade into melancholy, and saw two small islands appear on the horizon.
The ocean then formed a frothy path in front of Erik Moreen, from the bow to the two islands. In the captain’s still quivering mind time seemed to move backwards, and he tried to find solace from the rootlessness of this experience by searching his mind for pleasant memories. The path suddenly seemed indistinguishable from the road connecting the port of Gothenburg to his home in Guldheden, many nautical miles away. He even swore he could see a colony of black-backed gulls circling in the sky above him, establishing his notion that Hoppet somehow had made it back to the north. Confident he would soon be reunited with his wife, the captain gave orders to sail in the direction of the islands and would not listen to any objections until the ship was stranded on a sandbank no more than a stone’s throw away from its destination. Although Erik Moreen recovered from the surgeon’s medicament after a few days, the crew’s trust in him was not as easily restored and he was temporarily relieved of his duties as captain.
During the days it took the crew to dig Hoppet out of the sand, naturalist and ship priest Olof Torén visited the islands a few times but was apparently not very impressed by this occurrence. In his journals from this time, only one sentence makes mention of this part of the voyage:
Northeast of Madagascar lie two small islands not worth visiting for any other reason than the fact that they are inhabited by small, quadrupedal mammals with relatively long necks.
In line with the often undemocratic nature of human experiences, Olof Torén would encounter teacup giraffes twice over the course of a few years, while it would take centuries after they were first discovered before anybody else did. The second time Olof Torén visited the two small islands where teacup giraffes still live today, it was during his second and last intercontinental journey at sea, this time on the Swedish East India Company ship Götha Leijon. In 1750 he found himself back in the cerulean waters northeast of Madagascar. Again, it was an event characterized by pain that would bring the naturalist and ship priest to the teacup giraffe islands, but pain of the soul rather than the body.
In the evenings, Olof Torén and supercargo Anders Gotheen, a man who would not hesitate to let people know his personal values aligned perfectly with the new ideals of the intellectual revolution today known as the Enlightenment, enjoyed sitting down for a game of backgammon on the main deck. Or rather, Olof Torén enjoyed the game while Anders Gotheen, as he liked to say, enjoyed engaging in dialectic discourse while playing a game only slightly distracting since winning after all was mostly a question of luck. Olof Torén had noticed that conversations with Anders Gotheen often made him worried about things he had not previously contemplated and was therefore less amused by the dialectic aspect of their game nights.
This particular evening it seemed Anders Gotheen would definitely lose. He watched Olof Torén bear off three of his white checkers and as he collected the dice supercargo Gotheen turned to the naturalist.
– For the intellectually brave and honest, he said, no comfort can be found in the concept of infinity. Everything typical of human life, he continued, is characterized by the notion of a finite world, and therefore a free human mind can never truly be consoled by the appreciation of endlessness. He paused and drank some wine from a metal cup.
– This is also true for the afterlife, he then added.
The reason why, as Olof Torén would explain it a few days later during a one-way conversation with a group of patiently listening teacup giraffes, what Anders Gotheen said that evening had so effectively shaken the foundation of his world view, was not because it directly challenged his faith. No, it was the fact that what Anders Gotheen had said instead questioned how much existential relief any belief could offer whatsoever that resulted in a detrimental blow to his conviction.
For what felt like a very long time, Olof Torén listened to Anders Gotheen in detail describe the philosophical abyss all perspectives on life after death inescapably lead to. He started to feel lightheaded and ended the game early so that he could climb up to the upper deck to get some fresh air. It was a clear night and the sky seemed filled with an infinite number of stars. His head was now spinning, he lost his balance, made a futile attempt to reach for the shrouds, but inevitably fell overboard.
Olof Torén treaded water all night, motivated by his new fear of death. When the sun rose he for a moment forgot about the thoughts that had been plaguing him during those long hours in the dark water, because the early morning light draped the world in a calming emerald luster. He could now see he had drifted to only a yards away from the sandbank where Hoppet had been stranded a few years earlier. The sunlight was filtered through the foliage of a thousand palm trees on the islands between him and the rising sun and this is what created the green morning experience. He swam to the island closest to him and passed out on the beach, exhausted both physically and mentally.
When Olof Torén woke up the sun was about to set. As he slowly opened his eyes he saw a group of teacup giraffes walking towards him on the beach, and briefly thought they had come to welcome him back to the islands. But the small giraffe caravan walked past him and continued towards a large flat stone projecting out of the sand not far from where he was lying. In the light of dusk, everything on the beach seemed to cast extraordinarily long shadows and from where Olof Torén was positioned the stone looked enormous. The shape made him think of runestones, rocks Vikings would raise and decorate with runic inscriptions, but the size more resembled the moai stone statues of Easter island. The teacup giraffes took turns standing in front of the stone observing a large silhouette appear on the granite surface ahead. They seemed infatuated by watching their shadow twin move as they moved, grasping for palm tree leaves non-shadow teacup giraffes would never reach, and making low pitched vocal sounds reminding Olof Torén of larger animals. It was as if these giraffes knew about their unusually small stature and used the evening light to pretend they were tall, like their full-size relatives on the African mainland.
This time around Olof Torén spent two days in the company of teacup giraffes and grew to appreciate their cheerful demeanor, social competence and general friendliness. When not playing with shadows, chasing fireflies or resting in the crisp air next to a waterfall, they foraged the jungle for celery which seemed to be their favorite food. Time passed quickly on the islands because the teacup giraffes had an almost endless repertoire of delightful behaviors and at times it even appeared they adapted their conduct to what Olof Torén showed appreciation for.
On the Swedish East India Company ship Götha Leijon, the consensus was that Olof Torén was unlikely to still be alive, but most of the crew were god fearing men and the idea of leaving a priest to drown without a convincing rescue attempt seemed like the type of thing the almighty would never forget. Some of the them had been present for the Erik Moreen debacle a few years earlier and remembered the location of the islands from this incident.
Olof Torén saw the ship approach the islands from far away. After two days on the teacup giraffe diet he was famished because the more celery he ate the hungrier he became. Longing for the salted meats that made up a large part of the meals onboard Swedish East India Company ships, he said goodbye to his new four-legged friends and waded out in the water to meet the rowboat launched to pick him up. As he climbed onboard he turned around to catch a last glimpse of the giraffes and saw them gather on the beach for another evening shadow play ritual.
Back on Götha Leijon, Olof Torén wrote down all the details of teacup giraffes’ lives he could remember. During a game of backgammon, he tried to describe their unusual physique and social behaviors, but was soon interrupted by Anders Gotheen who said
– The human mind works in mysterious ways when deprived of energy
Olof Torén would in fact have a hard time convincing friends and colleagues, people he met as Götha Leijon sailed to Surat and Canton, as well as Carl Linnaeus, that his accounts of the teacup giraffe islands reflected reality. Maybe the feeling of not being trusted contributed to why his health started to deteriorate after his return to Sweden in 1752. On the 3rd of May 1753, a few months before his death, he wrote a last letter to Linnaeus. On the final page of this letter he drew a picture of two giraffes, one short and one tall. They were standing in front of a pair of heavy church doors and both their shadows stretched far above the tower and into the sky. The short giraffe, a female with pointy ears, looked like she was smiling.
Even today, when there are few true mysteries left in the world and all corners of the globe have been so carefully measured and illuminated that no colorless spots stain the pages of the modern atlas, teacup giraffes remain a relatively well kept secret. These animals were briefly mentioned in 9th edition of Carl Linnaeus’ Systema Naturae and given the Latin name Giraffa minima, but were removed before printing the 10th edition because of doubts about the trustworthiness of Olof Torén’s descriptions. No songs have been sung about them, Rudyard Kipling was oblivious to their existence when writing the Jungle Book, schoolchildren have never wondered how many teacup giraffes they could fit in a backpack, and they were never made famous by a Twinings advertisement campaign.
But in December 2018, the Olof Torén foundation in Uppsala decided it was time that teacup giraffes were given a more prominent place in the world of zoology. The foundation set aside money for a stipend and requested applications from “young aspiring scientists devoted to conducting old-school observational field work and interested in spending time on remote islands in the Indian Ocean studying possibly the least known mammal on the planet”. In the announcement they also wrote “One crucial piece of information missing about teacup giraffes regards their physique, because although it is known that they are small, no reliable records of their exact size can be found anywhere”. And this is true because people falling overboard rarely bring measuring equipment.
Hasse is a tall, Swedish teacup giraffe (one of the few giraffes from the North). When not munching on celery and doing creative writing, he spends his time working as an instructor at Emory University, coding in R, thinking about issues related to scientific rigor, and analyzing gene expression data.
DESIRÉE DE LEON
Desirée is a multidisciplinary teacup giraffe who is a recent PhD grad in neuroscience at Emory University, and also enjoys illustrating and bluegrass (both the music and the plant). Her graduate research involved using R to analyze pedigree data of cute monkey families living at the Yerkes National Primate Research Center.
Teacup giraffes were first discovered by the Swedish naturalist and priest Olof Torén, an apostle of Carl Linnaeus, during his travels on the Swedish East India Company ship Hoppet. Departing from the port of Gothenburg, Hoppet set sail on the 26th of January 1748, an extraordinarily gloomy day when sea and sky was a single ashen entity and winds from the northwest brought a snow-mixed horizontal rain. The mood onboard the ship was cheerful nonetheless, as the crew dreamt of the transparent waters of southern seas, filled with colorful beings and stories to bring back home.
One suffocating evening in July 1748, shortly after Hoppet had rounded the Cape of Good Hope and sailed east of Madagascar, captain Erik Moreen was attacked by a headache so vicious it made him seasick for the first time since the beginning of his nautical career. Desperate to ease the pain he called on the ship surgeon, a crooked man who spent most of his time investigating fungi in the deepest corners of the ship’s bilge where the air tasted like year old bread.
The surgeon brought a small bottle containing a beverage brewed of equal parts fermented goat milk and a distillate of mushrooms which, when dissolved, produced such a colorful luminance that the use of protective goggles was necessary to prevent achromatopsia. Ignoring the surgeon’s recommendation to limit the dose to a few drops of the brew, the captain emptied the bottle in one gulp and for a few moments lost all senses while the hair on the back of his head spun into permanent curls.
Although the pain subsided within less than thirty minutes, this was just the beginning of a nightmarish intoxication. The captain heard sirens sing lost lullabies from all directions, saw the kraken swallow the moon and spit it out as thousands of stars, and was convinced that all of his crew had turned into sharp-stemmed ferns, stationary and threatening to amputate him with every wavering step he took across the deck. During this confusion the captain tied himself to the fore-mast, prepared to fall head first into the flaming depths of the underworld. He was standing there as the sun rose, as his inebriation started to fade into melancholy, and saw two small islands appear on the horizon.
The ocean then formed a frothy path in front of Erik Moreen, from the bow to the two islands. In the captain’s still quivering mind time seemed to move backwards, and he tried to find solace from the rootlessness of this experience by searching his mind for pleasant memories. The path suddenly seemed indistinguishable from the road connecting the port of Gothenburg to his home in Guldheden, many nautical miles away. He even swore he could see a colony of black-backed gulls circling in the sky above him, establishing his notion that Hoppet somehow had made it back to the north. Confident he would soon be reunited with his wife, the captain gave orders to sail in the direction of the islands and would not listen to any objections until the ship was stranded on a sandbank no more than a stone’s throw away from its destination. Although Erik Moreen recovered from the surgeon’s medicament after a few days, the crew’s trust in him was not as easily restored and he was temporarily relieved of his duties as captain.
During the days it took the crew to dig Hoppet out of the sand, naturalist and ship priest Olof Torén visited the islands a few times but was apparently not very impressed by this occurrence. In his journals from this time, only one sentence makes mention of this part of the voyage:
Northeast of Madagascar lie two small islands not worth visiting for any other reason than the fact that they are inhabited by small, quadrupedal mammals with relatively long necks.
In line with the often undemocratic nature of human experiences, Olof Torén would encounter teacup giraffes twice over the course of a few years, while it would take centuries after they were first discovered before anybody else did. The second time Olof Torén visited the two small islands where teacup giraffes still live today, it was during his second and last intercontinental journey at sea, this time on the Swedish East India Company ship Götha Leijon. In 1750 he found himself back in the cerulean waters northeast of Madagascar. Again, it was an event characterized by pain that would bring the naturalist and ship priest to the teacup giraffe islands, but pain of the soul rather than the body.
In the evenings, Olof Torén and supercargo Anders Gotheen, a man who would not hesitate to let people know his personal values aligned perfectly with the new ideals of the intellectual revolution today known as the Enlightenment, enjoyed sitting down for a game of backgammon on the main deck. Or rather, Olof Torén enjoyed the game while Anders Gotheen, as he liked to say, enjoyed engaging in dialectic discourse while playing a game only slightly distracting since winning after all was mostly a question of luck. Olof Torén had noticed that conversations with Anders Gotheen often made him worried about things he had not previously contemplated and was therefore less amused by the dialectic aspect of their game nights.
This particular evening it seemed Anders Gotheen would definitely lose. He watched Olof Torén bear off three of his white checkers and as he collected the dice supercargo Gotheen turned to the naturalist.
– For the intellectually brave and honest, he said, no comfort can be found in the concept of infinity. Everything typical of human life, he continued, is characterized by the notion of a finite world, and therefore a free human mind can never truly be consoled by the appreciation of endlessness. He paused and drank some wine from a metal cup.
– This is also true for the afterlife, he then added.
The reason why, as Olof Torén would explain it a few days later during a one-way conversation with a group of patiently listening teacup giraffes, what Anders Gotheen said that evening had so effectively shaken the foundation of his world view, was not because it directly challenged his faith. No, it was the fact that what Anders Gotheen had said instead questioned how much existential relief any belief could offer whatsoever that resulted in a detrimental blow to his conviction.
For what felt like a very long time, Olof Torén listened to Anders Gotheen in detail describe the philosophical abyss all perspectives on life after death inescapably lead to. He started to feel lightheaded and ended the game early so that he could climb up to the upper deck to get some fresh air. It was a clear night and the sky seemed filled with an infinite number of stars. His head was now spinning, he lost his balance, made a futile attempt to reach for the shrouds, but inevitably fell overboard.
Olof Torén treaded water all night, motivated by his new fear of death. When the sun rose he for a moment forgot about the thoughts that had been plaguing him during those long hours in the dark water, because the early morning light draped the world in a calming emerald luster. He could now see he had drifted to only a yards away from the sandbank where Hoppet had been stranded a few years earlier. The sunlight was filtered through the foliage of a thousand palm trees on the islands between him and the rising sun and this is what created the green morning experience. He swam to the island closest to him and passed out on the beach, exhausted both physically and mentally.
When Olof Torén woke up the sun was about to set. As he slowly opened his eyes he saw a group of teacup giraffes walking towards him on the beach, and briefly thought they had come to welcome him back to the islands. But the small giraffe caravan walked past him and continued towards a large flat stone projecting out of the sand not far from where he was lying. In the light of dusk, everything on the beach seemed to cast extraordinarily long shadows and from where Olof Torén was positioned the stone looked enormous. The shape made him think of runestones, rocks Vikings would raise and decorate with runic inscriptions, but the size more resembled the moai stone statues of Easter island. The teacup giraffes took turns standing in front of the stone observing a large silhouette appear on the granite surface ahead. They seemed infatuated by watching their shadow twin move as they moved, grasping for palm tree leaves non-shadow teacup giraffes would never reach, and making low pitched vocal sounds reminding Olof Torén of larger animals. It was as if these giraffes knew about their unusually small stature and used the evening light to pretend they were tall, like their full-size relatives on the African mainland.
This time around Olof Torén spent two days in the company of teacup giraffes and grew to appreciate their cheerful demeanor, social competence and general friendliness. When not playing with shadows, chasing fireflies or resting in the crisp air next to a waterfall, they foraged the jungle for celery which seemed to be their favorite food. Time passed quickly on the islands because the teacup giraffes had an almost endless repertoire of delightful behaviors and at times it even appeared they adapted their conduct to what Olof Torén showed appreciation for.
On the Swedish East India Company ship Götha Leijon, the consensus was that Olof Torén was unlikely to still be alive, but most of the crew were god fearing men and the idea of leaving a priest to drown without a convincing rescue attempt seemed like the type of thing the almighty would never forget. Some of the them had been present for the Erik Moreen debacle a few years earlier and remembered the location of the islands from this incident.
Olof Torén saw the ship approach the islands from far away. After two days on the teacup giraffe diet he was famished because the more celery he ate the hungrier he became. Longing for the salted meats that made up a large part of the meals onboard Swedish East India Company ships, he said goodbye to his new four-legged friends and waded out in the water to meet the rowboat launched to pick him up. As he climbed onboard he turned around to catch a last glimpse of the giraffes and saw them gather on the beach for another evening shadow play ritual.
Back on Götha Leijon, Olof Torén wrote down all the details of teacup giraffes’ lives he could remember. During a game of backgammon, he tried to describe their unusual physique and social behaviors, but was soon interrupted by Anders Gotheen who said
– The human mind works in mysterious ways when deprived of energy
Olof Torén would in fact have a hard time convincing friends and colleagues, people he met as Götha Leijon sailed to Surat and Canton, as well as Carl Linnaeus, that his accounts of the teacup giraffe islands reflected reality. Maybe the feeling of not being trusted contributed to why his health started to deteriorate after his return to Sweden in 1752. On the 3rd of May 1753, a few months before his death, he wrote a last letter to Linnaeus. On the final page of this letter he drew a picture of two giraffes, one short and one tall. They were standing in front of a pair of heavy church doors and both their shadows stretched far above the tower and into the sky. The short giraffe, a female with pointy ears, looked like she was smiling.
Even today, when there are few true mysteries left in the world and all corners of the globe have been so carefully measured and illuminated that no colorless spots stain the pages of the modern atlas, teacup giraffes remain a relatively well kept secret. These animals were briefly mentioned in 9th edition of Carl Linnaeus’ Systema Naturae and given the Latin name Giraffa minima, but were removed before printing the 10th edition because of doubts about the trustworthiness of Olof Torén’s descriptions. No songs have been sung about them, Rudyard Kipling was oblivious to their existence when writing the Jungle Book, schoolchildren have never wondered how many teacup giraffes they could fit in a backpack, and they were never made famous by a Twinings advertisement campaign.
But in December 2018, the Olof Torén foundation in Uppsala decided it was time that teacup giraffes were given a more prominent place in the world of zoology. The foundation set aside money for a stipend and requested applications from “young aspiring scientists devoted to conducting old-school observational field work and interested in spending time on remote islands in the Indian Ocean studying possibly the least known mammal on the planet”. In the announcement they also wrote “One crucial piece of information missing about teacup giraffes regards their physique, because although it is known that they are small, no reliable records of their exact size can be found anywhere”. And this is true because people falling overboard rarely bring measuring equipment.
================================================
FILE: docs/00_narrative.utf8.html
================================================
History of Teacup Giraffes
Teacup giraffes were first discovered by the Swedish naturalist and priest Olof Torén, an apostle of Carl Linnaeus, during his travels on the Swedish East India Company ship Hoppet. Departing from the port of Gothenburg, Hoppet set sail on the 26th of January 1748, an extraordinarily gloomy day when sea and sky was a single ashen entity and winds from the northwest brought a snow-mixed horizontal rain. The mood onboard the ship was cheerful nonetheless, as the crew dreamt of the transparent waters of southern seas, filled with colorful beings and stories to bring back home.
One suffocating evening in July 1748, shortly after Hoppet had rounded the Cape of Good Hope and sailed east of Madagascar, captain Erik Moreen was attacked by a headache so vicious it made him seasick for the first time since the beginning of his nautical career. Desperate to ease the pain he called on the ship surgeon, a crooked man who spent most of his time investigating fungi in the deepest corners of the ship’s bilge where the air tasted like year old bread.
The surgeon brought a small bottle containing a beverage brewed of equal parts fermented goat milk and a distillate of mushrooms which, when dissolved, produced such a colorful luminance that the use of protective goggles was necessary to prevent achromatopsia. Ignoring the surgeon’s recommendation to limit the dose to a few drops of the brew, the captain emptied the bottle in one gulp and for a few moments lost all senses while the hair on the back of his head spun into permanent curls.
Although the pain subsided within less than thirty minutes, this was just the beginning of a nightmarish intoxication. The captain heard sirens sing lost lullabies from all directions, saw the kraken swallow the moon and spit it out as thousands of stars, and was convinced that all of his crew had turned into sharp-stemmed ferns, stationary and threatening to amputate him with every wavering step he took across the deck. During this confusion the captain tied himself to the fore-mast, prepared to fall head first into the flaming depths of the underworld. He was standing there as the sun rose, as his inebriation started to fade into melancholy, and saw two small islands appear on the horizon.
The ocean then formed a frothy path in front of Erik Moreen, from the bow to the two islands. In the captain’s still quivering mind time seemed to move backwards, and he tried to find solace from the rootlessness of this experience by searching his mind for pleasant memories. The path suddenly seemed indistinguishable from the road connecting the port of Gothenburg to his home in Guldheden, many nautical miles away. He even swore he could see a colony of black-backed gulls circling in the sky above him, establishing his notion that Hoppet somehow had made it back to the north. Confident he would soon be reunited with his wife, the captain gave orders to sail in the direction of the islands and would not listen to any objections until the ship was stranded on a sandbank no more than a stone’s throw away from its destination. Although Erik Moreen recovered from the surgeon’s medicament after a few days, the crew’s trust in him was not as easily restored and he was temporarily relieved of his duties as captain.
During the days it took the crew to dig Hoppet out of the sand, naturalist and ship priest Olof Torén visited the islands a few times but was apparently not very impressed by this occurrence. In his journals from this time, only one sentence makes mention of this part of the voyage:
Northeast of Madagascar lie two small islands not worth visiting for any other reason than the fact that they are inhabited by small, quadrupedal mammals with relatively long necks.
In line with the often undemocratic nature of human experiences, Olof Torén would encounter teacup giraffes twice over the course of a few years, while it would take centuries after they were first discovered before anybody else did. The second time Olof Torén visited the two small islands where teacup giraffes still live today, it was during his second and last intercontinental journey at sea, this time on the Swedish East India Company ship Götha Leijon. In 1750 he found himself back in the cerulean waters northeast of Madagascar. Again, it was an event characterized by pain that would bring the naturalist and ship priest to the teacup giraffe islands, but pain of the soul rather than the body.
In the evenings, Olof Torén and supercargo Anders Gotheen, a man who would not hesitate to let people know his personal values aligned perfectly with the new ideals of the intellectual revolution today known as the Enlightenment, enjoyed sitting down for a game of backgammon on the main deck. Or rather, Olof Torén enjoyed the game while Anders Gotheen, as he liked to say, enjoyed engaging in dialectic discourse while playing a game only slightly distracting since winning after all was mostly a question of luck. Olof Torén had noticed that conversations with Anders Gotheen often made him worried about things he had not previously contemplated and was therefore less amused by the dialectic aspect of their game nights.
This particular evening it seemed Anders Gotheen would definitely lose. He watched Olof Torén bear off three of his white checkers and as he collected the dice supercargo Gotheen turned to the naturalist.
– For the intellectually brave and honest, he said, no comfort can be found in the concept of infinity. Everything typical of human life, he continued, is characterized by the notion of a finite world, and therefore a free human mind can never truly be consoled by the appreciation of endlessness. He paused and drank some wine from a metal cup.
– This is also true for the afterlife, he then added.
The reason why, as Olof Torén would explain it a few days later during a one-way conversation with a group of patiently listening teacup giraffes, what Anders Gotheen said that evening had so effectively shaken the foundation of his world view, was not because it directly challenged his faith. No, it was the fact that what Anders Gotheen had said instead questioned how much existential relief any belief could offer whatsoever that resulted in a detrimental blow to his conviction.
For what felt like a very long time, Olof Torén listened to Anders Gotheen in detail describe the philosophical abyss all perspectives on life after death inescapably lead to. He started to feel lightheaded and ended the game early so that he could climb up to the upper deck to get some fresh air. It was a clear night and the sky seemed filled with an infinite number of stars. His head was now spinning, he lost his balance, made a futile attempt to reach for the shrouds, but inevitably fell overboard.
Olof Torén treaded water all night, motivated by his new fear of death. When the sun rose he for a moment forgot about the thoughts that had been plaguing him during those long hours in the dark water, because the early morning light draped the world in a calming emerald luster. He could now see he had drifted to only a yards away from the sandbank where Hoppet had been stranded a few years earlier. The sunlight was filtered through the foliage of a thousand palm trees on the islands between him and the rising sun and this is what created the green morning experience. He swam to the island closest to him and passed out on the beach, exhausted both physically and mentally.
When Olof Torén woke up the sun was about to set. As he slowly opened his eyes he saw a group of teacup giraffes walking towards him on the beach, and briefly thought they had come to welcome him back to the islands. But the small giraffe caravan walked past him and continued towards a large flat stone projecting out of the sand not far from where he was lying. In the light of dusk, everything on the beach seemed to cast extraordinarily long shadows and from where Olof Torén was positioned the stone looked enormous. The shape made him think of runestones, rocks Vikings would raise and decorate with runic inscriptions, but the size more resembled the moai stone statues of Easter island. The teacup giraffes took turns standing in front of the stone observing a large silhouette appear on the granite surface ahead. They seemed infatuated by watching their shadow twin move as they moved, grasping for palm tree leaves non-shadow teacup giraffes would never reach, and making low pitched vocal sounds reminding Olof Torén of larger animals. It was as if these giraffes knew about their unusually small stature and used the evening light to pretend they were tall, like their full-size relatives on the African mainland.
This time around Olof Torén spent two days in the company of teacup giraffes and grew to appreciate their cheerful demeanor, social competence and general friendliness. When not playing with shadows, chasing fireflies or resting in the crisp air next to a waterfall, they foraged the jungle for celery which seemed to be their favorite food. Time passed quickly on the islands because the teacup giraffes had an almost endless repertoire of delightful behaviors and at times it even appeared they adapted their conduct to what Olof Torén showed appreciation for.
On the Swedish East India Company ship Götha Leijon, the consensus was that Olof Torén was unlikely to still be alive, but most of the crew were god fearing men and the idea of leaving a priest to drown without a convincing rescue attempt seemed like the type of thing the almighty would never forget. Some of the them had been present for the Erik Moreen debacle a few years earlier and remembered the location of the islands from this incident.
Olof Torén saw the ship approach the islands from far away. After two days on the teacup giraffe diet he was famished because the more celery he ate the hungrier he became. Longing for the salted meats that made up a large part of the meals onboard Swedish East India Company ships, he said goodbye to his new four-legged friends and waded out in the water to meet the rowboat launched to pick him up. As he climbed onboard he turned around to catch a last glimpse of the giraffes and saw them gather on the beach for another evening shadow play ritual.
Back on Götha Leijon, Olof Torén wrote down all the details of teacup giraffes’ lives he could remember. During a game of backgammon, he tried to describe their unusual physique and social behaviors, but was soon interrupted by Anders Gotheen who said
– The human mind works in mysterious ways when deprived of energy
Olof Torén would in fact have a hard time convincing friends and colleagues, people he met as Götha Leijon sailed to Surat and Canton, as well as Carl Linnaeus, that his accounts of the teacup giraffe islands reflected reality. Maybe the feeling of not being trusted contributed to why his health started to deteriorate after his return to Sweden in 1752. On the 3rd of May 1753, a few months before his death, he wrote a last letter to Linnaeus. On the final page of this letter he drew a picture of two giraffes, one short and one tall. They were standing in front of a pair of heavy church doors and both their shadows stretched far above the tower and into the sky. The short giraffe, a female with pointy ears, looked like she was smiling.
Even today, when there are few true mysteries left in the world and all corners of the globe have been so carefully measured and illuminated that no colorless spots stain the pages of the modern atlas, teacup giraffes remain a relatively well kept secret. These animals were briefly mentioned in 9th edition of Carl Linnaeus’ Systema Naturae and given the Latin name Giraffa minima, but were removed before printing the 10th edition because of doubts about the trustworthiness of Olof Torén’s descriptions. No songs have been sung about them, Rudyard Kipling was oblivious to their existence when writing the Jungle Book, schoolchildren have never wondered how many teacup giraffes they could fit in a backpack, and they were never made famous by a Twinings advertisement campaign.
But in December 2018, the Olof Torén foundation in Uppsala decided it was time that teacup giraffes were given a more prominent place in the world of zoology. The foundation set aside money for a stipend and requested applications from “young aspiring scientists devoted to conducting old-school observational field work and interested in spending time on remote islands in the Indian Ocean studying possibly the least known mammal on the planet”. In the announcement they also wrote “One crucial piece of information missing about teacup giraffes regards their physique, because although it is known that they are small, no reliable records of their exact size can be found anywhere”. And this is true because people falling overboard rarely bring measuring equipment.
================================================
FILE: docs/01_introToR.html
================================================
Introduction to the R programming language
You’re about to start an adventure to learn R and statistics. If this is your first time working with R, then you should begin on this page. If you’re comfortable with R basics and you’d like to start with the statistical content, please proceed onto the islands with this link.
Using interactive windows
Throughout this material we will use an interactive version of R provided by learnR. Although this way of using R comes with some limitations regarding functionality, it will give us the benefit of being able to run R code without switching from a web browser to a standalone application such as RStudio.
Below you can see an example of an interactive R window. In the white window (under the button with the text Start Over) is where you will write code to be executed. To run code you have written, click the green Run Code button and observe how information pops up under the window. If no errors were made, this is where the answer will be returned. Error messages are shown in a red box, also under the learnR window.
Now spend a few minutes using the window below as a calculator and run simple calculations by clicking Run Code.
Objects in R
R is an object oriented programming language, meaning when working in R we will create different types of objects and use operators and functions to manipulate these objects.
To create a new object in R we first pick a name for the object and tell R what to assign to it using assignment operators; either = or a small arrow <- made from the combination of < and -.
Use the window below to assign numbers to objects and output the content by typing out the name of the object.
After creating two numerical objects, use the mathematical operator + to add these objects together and see what happens when you run the code.
Common functions
Most of the time when we work in R, we will use functions; either pre-written functions or ones we write ourselves. Functions make it easy to use sets of code instructions repeatedly (without filling our scripts with the code underlying the function) and help us carry out multiple tasks in a single step without having to go through the details of how the steps are executed.
To use functions in R, we type the name of the function followed by parentheses (e.g. print( )). Within the parentheses is where we will specify the function input and options, separated by commas ,. One function you will use a lot is the combine functionc( ), which as the name suggests lets you concatenate different types of values.
In the window below, create an object combining a set of numerical values using c( ), separating the different values with commas.
Then sum up the content of this object using sum( ).
Writing your own function
R makes it easy to create user defined functions by using function( ). Here is how it works:
Give your function an object name and assign the function to it, e.g. my_function_name <- function( ).
Within the parentheses you specify inputs and options just like how pre-written functions work, e.g. function(input_data)
Next, put all the code you want your function to execute inside curly brackets like this: function(input_data){code to run}
Use return( ) to specify what you want to your function to output once it is done running the code.
Use the following instructions to complete the function in the window below:
We’ve started writing a function for you that will sum up values and take the square root of the sum. To take the square root, we use the sqrt( ) function.
Complete this function by filling in input_data for the sum( ), and then filling in the remaining empty parentheses with the appropriate object names.
Now create an object containing a set of numerical values and call it my_combined_values. Then try out your new function on this object, which will compute the square root of the object’s sum.
Functions within functions
It is also possible (and sometimes very useful) to put a function within another function. For example, we could combine multiple steps into one like this: sqrt(prod(c(1,2))), resulting in one line of code that both generates the values and directly calculates the square root of the product of those values.
Vectors
The c( ) function will combine values and create a specific type of data structure called an atomic vector. A vector is a simple one-dimensional structure that can contain only one type of value. This means that storing multiple numeric values in a vector, as we have already done, works just fine. But there are other types of values that can be used in R, for example character values. These values are created by putting text or numbers within quotes such as "Giraffe". If we try to combine numeric and character values in the same vector, R will convert both values to the character type.
c("Giraffe", 123)
## [1] "Giraffe" "123"
This behavior is not always desirable, so it is a good idea to try to only combine values of the same type.
Boolean values and logical operators
Another type of variable in R is the boolean (TRUE/FALSE) type. Boolean or logical vectors can only take two different values; TRUE or FALSE (case sensitive). You will see these types of values mostly when logical operators are used to test how different objects relate to each other. For example, the logical operator== can be used to test if two different objects are exactly the same and TRUE will be returned only if the identity is fulfilled.
"Giraffe" == "Teacup"
## [1] FALSE
sqrt(100) == 10
## [1] TRUE
Data frames
Vectors are one of multiple data structures in R. We will not cover all types of structures here, but perhaps the most common one encountered when analyzing data in R is the data frame. Data frames are two dimensional, which basically means that data frames let you store multiple vectors in one object. Oftentimes, each vector will be a new column in the data frame. One constraint though is that all vectors/columns need to be of the same length.
In the window below, use the data.frame() function to create a data frame with two columns called x and y. When creating data frames we specify a column by giving the column a name and assigning values or vectors to it, e.g. data.frame(x= c("Giraffe", "Teacup")).
Also observe what happens when you try to combine an x and y vector of different lengths.
As you could see above, running a line of code with just the name of a data structure (in this case, the letter d) will print the full data frame in the console output (if no errors were made!). If you instead are interested in only one of the columns, this can be specified by using the $ operator followed by the column’s name, e.g d$x. Try it out below!
You’re ready to go!
This was a brief introduction into R, but now you know what you need to get started with the first module!
This project was created entirely in RStudio using R Markdown and published on GitHub pages.
================================================
FILE: docs/02_bellCurve.html
================================================
The Normal Distribution
Describe the characteristics of a normal distribution
Create a histogram using ggplot and modify its appearance
Define sample and population
Explain why distributions of sample data that come from populations with normal distibutions don’t always look normal
Welcome to the island
Two weeks after starting grad school, you’re assigned to go to a small island east of Madagascar to study a mysterious mammalian species that has caught your advisor’s interest. After a long trip on various ships and smaller boats, you arrive on the island, excited to collect data on this new species.
The only information your advisor has given you is that they are small giraffe-like creatures. He called them teacup giraffes. You waste no time to suit up in your field gear, and your guide leads you deep into the dense island brush. Filled with anticipation, you start searching for your first subject.
After a one hour hike, you reach a clearing where tall cypress trees encircle low growth vegetation. Suddenly, you experience your first encounter with a little giraffe, whose cool drink from a puddle you seem to have interrupted. Smaller than you had imagined–its slender body does not even clear the height of a dandelion. You toss a celery stick in its direction, and you’re pleasantly surprised that it immediately trots over to you and starts vigorously munching, creating a celery confetti cloud. After observing this behavior for a while and taking some notes on how quickly it devours your celery supply, you bring out your measuring tape and record the giraffe’s height.
A few minuters later, you leave the clearing and forge a path through thick ferns and palm leaves. You pause for a drink from your water bottle long enough to pinpoint a fast-paced crunching noise. In the shade of a fallen tree, there’s another teacup giraffe annihilating a small patch of wild celery for its afternoon snack. You’re surprised how much smaller this one is–about as tall as your Swiss army knife. Throughout the day you encounter several teacup giraffes, and you realize that although all petite, they are remarkably variable in stature.
The next week is spent trekking and measuring every giraffe you can manage to get near enough. You take advantage of the fact that they come running everytime you pull out a celery stalk from your lunch bag, and you’re relieved that it takes them long enough to finish snacking for you to measure their height. With the help of your guide, you manage to measure 50 giraffes in the first week.
There is a second island not too far away, where your guide has indicated there may be more giraffes. You wonder how the population of giraffes on the second island may be different, and so you make arrangements to go to Island #2 the following week. It is not too long until you have added another 50 measurements of these tiny giraffes from this second excursion.
Visualize the data
After collecting 100 measurements, you decide to take a first look at the data. What’s the best way to look at data when you know nothing about it…? You take a long walk on the beach to ponder this.
You can start by scanning the values for the shortest and tallest heights. You see the range is between 6 and 20cm. So you draw a ruler in the sand with the extreme heights on either end. You’d like to see how many times each height occurs in your data set, and so you grab a small colorful stone from the shore to represent each individual giraffe’s height and place it just above your ruler mark. You put out a new stone for each height and continue doing this for each individual to see which heights “stack up” along your ruler. To keep track of which heights came from different islands, you pick differently colored stones for each group. Look below for a sped-up version of this process! The y-axis is frequency. The x-axis is the height.
As you put the last stone in place, your local guide saunters by and glances at your markings, “What a nice histogram!”
Distributions
The histogram above shows the distribution, or shape, of your data. The distribution of a variable in a data set gives you information about:
All the values the variable takes on in your data set, when the data are split into reasonably sized groups
How often each value occurs
The shape, center, and amount of variability in the data
Checking the distribution of the data is always one of the first steps of data anlaysis. By knowing the shape of the data, you gain insights into some of the data’s statistical properties (which become useful down the line, for example, when you need to decide whether a particular statistical test would be appropriate).
The normal distribution
Although the data can be distributed in many shapes, there are some general shapes that occur so frequently in nature that these distributions are given their own names. The most well-known distribution has a shape similar to a bell and is called the normal distribution (or sometimes “the bell curve” or just “normal curve”).
There are a few characteristics of the normal distribution:
There is a single peak
The mass of the distribution is at its center
There is symmetry about the center line
Taking a look at the stones in the sand, you see two bell-shaped distributions. One for each island. It looks like giraffe heights on each island follow a normal distribution— and that’s a good thing because you remember your stats textbook always talking about how normally distributed data behaves well! Phew!
Happy with your progress thus far, you are excited to send your histogram results to your PhD mentor back in the homeland. Instead of taking a picture of your stone histogram, you turn to R to create the perfect figure.
Our dataframe
Time to apply your Intro to R knowledge. The heights from your logbook have been stored in a data frame called d. Below we show the last few observations from this vector, using the tail() function, which all happen to be from Island #2.
tail(d)
## Height Location
## 95 19.24044 Island #2
## 96 17.58925 Island #2
## 97 18.54274 Island #2
## 98 17.16631 Island #2
## 99 17.71318 Island #2
## 100 16.79124 Island #2
Making a histogram with ggplot2
We will use the ggplot2 package for all our graphing. Check out this page as a reference.
We need some basic components as a bare minimum to get started. We can customize components later to make the graph more to our liking. The steps we will go through are:
First we need to tell R that we want to create a ggplot. This is done by using the ggplot( ) function. Within the parentheses, we can specify the data frame that contains what we want to plot, using the option data = d. We also have to tell ggplot what columns of the data frame to actually plot– we do this with the argument that stands for aesthetics: aes( ). In our case, only the x-axis variable Height needs to be specified.
Next, add a geom layer, which will determine the type of visual representation that will be used for the data. Different ggplot layers and options are added using a plus sign +. In our case, we will write + and then geom_histogram( ). To make your plot look similar to your sand drawing, you want to add an optional argument within the parentheses of geom_histogram, which will set the bin width to 1cm: geom_histogram( binwidth = 1 ).
Here we are using geom_histogram, but there are many other geom_ layers that you could use instead for different plot types. Check some of them out here.
A note about the +: You can keep adding new specifications on one long continuous line of code, separating each one with a +. However, if you’d like to make the code easier to read by adding each specification to a new line, make sure the + is added to the end of the first line and not the new one.
It’s a good idea to save any ggplot you make as an object. It’s a helpful practice for when you’ll do more complicated graphing later (e.g. combining plots).
Run the code below to see what this basic histogram in ggplot looks like:
Customize your ggplot
Let’s go over some quick ways we can customize any ggplot. First, we can tell ggplot that we want the data from the two islands to be different colors. And second, we can to specify the colors we want to use.
Different color for each group: Within aes( ), we add a fill = argument. Here is where you put the name of the variable that contains the categories that you want to distinguish with different colors.
You might be wondering why we don’t use the color= here instead (which is a valid argument for aes( )), and this is because we want to change the color of the fill of the bars, while color = would change only the bar outline color (see below).
To choose colors ggplot should use, we need to add a new option + scale_fill_manual( ) and then specify the colors with the argument values =. To read more about how to create your own color scale, see this page. If you have more than one color you need to specify, make sure you combine them within the c( ) function.
Colors in R can be specified in different ways. For example, you can use a string of the color name (see possible colors here) or with hex color codes.
Outline Color: To change the color of the outline, specify color = within the parentheses of the geom_ (i.e. geom_histogram).
In the window below, we have added some options that you can play around with. Use the descriptions above to:
Specify the variable that fill should be set to, as well as the colors for the fill and outline.
Try out some color specifications on your own, and then check out the solution to see what we picked.
Playing around with “complete themes”: ggplot has a nice way of changing many non-data display parameters at once though what is referred to as “complete themes”. Check this page for the available options.
Have fun testing out a few different complete themes by adding the argument with a + sign.
Try 3 different complete themes and take note of how the plot changes.
After trying different themes, you pick theme_light() and feel pretty good about your ggplot accomplishment. You send your plot to your PhD advisor, and within what feels like only minutes, you have a new attachment in your email inbox:
You’ve got some more changes to make to your plot. Let’s start with something easy:
Remove the space between the bars and the x-axis: Use the scale_y_continuous() argument, and inside the parentheses specify expand = followed by two numbers within the c( ) command. These two numbers represent how much above or below the data’s range you would like to extend the y-axis by.
The function scale_y_continuous can be used for other purposes. See some examples and more documentation can be found here.
Set axis limits
Set axis breaks
Transformations
Change axes labels: Add labs( ) to the existing ggplot layers and specify each axis you’d like to label as arguments, e.g. x =, followed by the string for your label. If you’d like to learn more about manipulations you can do with labs( ), see this reference.
In addition, labs( ) can be used to remove labels. In this case, we can also include fill = NULL to remove the legend label (recall that the categories for our legend were determined by the fill argument in aes() previously).
Use the window below to:
Remove the space and legend label
Change the x-axis label to “Teacup Giraffe heights” and the y-axis label to “Frequency”.
Remove panel border and 5.Remove minor grid lines: To make detailed changes to the layout, we can add a theme( ) argument. Nested within theme( ) we can use additional arguments, such as panel.border= and panel.grid.minor=. Many theme( ) arguments can be set to element_blank( ) to remove the element in question. To read more about what can be modifed with theme( ), check out this resource.
In the window below, use what you just learned to remove the panel border and the minor grid lines of the plot.
Move the Legend: We will add two more arguments to theme( ) to move the legend and make its background transparent. To change its position, use legend.position = followed by the c( ) command, in which you will specify the x- and y- positions. These values must be between 0 and 1. Specifying c(0,0), for example, would place the legend at the bottom left of the plot, while c(1,1) would place it at the top right.
To change the legend background to be transparent, we essentially remove it. Add the argument legend.background =. Take a look at previous steps to determine how you remove an element.
Hopefully your PhD advisor in the homeland will be satisfied with your new ggplot!
Things to think about
Since we could not take the height of every giraffe on each of the two islands, and it is unclear how many giraffes live on the islands, we had to rely on taking the heights of randomly selected groups of giraffes.
A sample, in our case the 50 giraffes from each island, is a subset of a population. The population is defined as all available observations in a defined geographic area at a given point in time – in this case, all existing giraffes on one of the islands while you are there.
If we pick our sample in a random way, then our hope is that our sample data will be representative of the population. The larger our sample is, the more of the population it will include, and thus, the more closely the sample will resemble the population in its statistical attributes (e.g the distribution). We then must acknowledge that the smaller our sample is, the less likely it is that it will be representative of the population.
The animation below illustrates how small samples can depart from the characteristics of the population.
The panels below show samples that all come from the same population.
Each frame of a panel, is a new sample drawn of the specified size.
Observe that the smaller samples tend to:
Have oddly shaped distributions
Jump around a lot
Take heed that with inadequate sample sizes, your sample data may barely resemble the population you’re interested in!
You decided that you had the resources to collect data on 50 giraffes on each of the islands. Will a sample of 50 be good enough to get a sense for the true values of the giraffe populations?
This project was created entirely in RStudio using R Markdown and published on GitHub pages.
================================================
FILE: docs/03_mean.html
================================================
Mean, Median, and Mode
Explain the mathematical notation used for calculating the mean
Write a function which calculates the mean for any vector
Write a function which calculates the median for any vector
Describe how the reliability of a sample mean will scale with increasing sample size
What are measures of centrality?
You’ve just collected a lot data and graphed heights. Although informative, a graphical display of these data is difficult to summarize – we need to describe these heights with a single number that will be meaningful and allow us to do statistics.
We can do this with a measure of centrality, the concept that one number in the “center” of the data set is a good summary of all the values. Below are examples of different measures of centrality.
The mean is the average and the measure of centrality that you are probably most familiar with. This is a good measure to use when the data are normally distributed. We describe it in detail below.
The median is the value in the middle of the data set. Half of the observations lie above the median and half below. When the data are normally distributed, the median and the mean will be very close to each other. When your data are not normally distributed (skewed to the left or right) the median is a more appropriate measure of centrality (see the animation below).
The mode is the value (height, in our case) that occurs most frequently in the data set. It’s not typically used in statistics, and we won’t cover it further here.
Taking the mean
The mean of a variable is the sum of its values, divided by the number of values.
This concept can be represented with equation below. In our case, each “x” represents a giraffe height (i.e. a single observation), and the numerical subscript indicates its order in the sample. We’ll use \({\bar{x}}\) (read “x-bar”) to represent the mean of the height variable.
To make this more efficient, instead of writing “\({x_1 + x_2 + ... + x_n}\)”, we can use the uppercase sigma symbol \(\sum{}\) to represent summation of all the observations.
This might look intimidating, but equation (2) is really showing the same thing as (1). Let’s go through the steps again, breaking the symbols apart a bit (see annotated equation (3) below). The sigma means ‘add up’. What are we adding up? All the heights “x”. The “i = ” part indicates which term to begin with. For our purposes, this will always be the first observation, hence \(i\) = 1. The character on top of the sigma is the last observation we include in our summation. In this case it’s n – because we’re adding all n = 50 observations in each group of giraffes. In both equations, we still divide by the total number of observations in each group we have: again, n.
Recall our discussion about a sample versus a population. Different symbols are used to represent the mean for each of these. We’ve already discussed \(\bar{x}\) for the sample mean. The analogous symbol for the population mean is \({\mu}\) (read “mu”). Additionally, when referring to the size of the population, we will use a capital \({N}\) instead of a lowercase one.
Code it up
Using (2), it’s easy to translate this equation into code in R. The heights recorded from island 1 have been stored in a vector called heights_island1. Below we show the first few observations from this vector, using the head() function.
Use the interactive window below to calculate the mean “by hand”.
Create your own function
Now it’s your turn to write your own function. Call it “my_mean” and have it calculate the mean of any given vector. You’re going to use the rules for writing a function in R that you’ve used previously. As a reminder, you’ll use function() and embed your code (that you completed in the window above) within curly brackets{}. The advantage of making a “homemade” function is that you can string together all the steps from the previous exercise into a single command.
You can also complete the exercise above in RStudio on your local computer. This way you will be able to save your my_mean() function and script for future use.
Take a tea break!
Taking the median
To calculate the median go through the following steps:
Assess whether there is an odd or even number of observations
Order all observations from smallest to largest
If an odd number, then the median is the middle value at position: (n + 1) / 2
If an even number, then:
Find the value at the position: n / 2
Find the value at the position: (n / 2) + 1
The median will be the mean of the values at these two positions.
Before you write your own median function, two concepts need to be introduced: 1) the modulus operator %% and 2) if...else statements.
The modulus operation gives the remainder after division of one number by another. For example, in R 11 %% 5 returns the 1, which is the remainer of 11 divided by 5. If the modulus operation returns 0, then there is no remainder. It is useful to apply the modulus operation x %% 2 to determine whether a number x is even or odd by testing if the result is exactly equal to 0. See example code below.
> 10 %% 2
## [1] 0
> 10 %% 2 == 0
## [1] TRUE
> 11 %% 2
## [1] 1
> 11 %% 2 == 0
## [1] FALSE
An if...else statement is useful when you want to specify distinct outcomes for objects dependent on whether they meet your set criteria. See below.
Now that you have a sense for how the %% operator can be used to test whether a number is EVEN or ODD, and how if...else statements work, use both of these concepts in the window below to write your own function that calculates the median of any vector.
Things to think about
Remember that the sample mean is an estimate of the entire population’s mean (which would often be impossibly large to measure). How reliably does the mean of a sample represent the population mean? Warning: if a small sample has been used, the sample mean may not be a reliable at all! Estimates from small samples are subject to the whims of randomness. On the other hand, the larger the sample, the closer the sample size appraches the population size, and the more reliable the sample estimate becomes.
Pressing ‘Play’ on the plot below will illustrate this concept.
The animation above shows the values of means calculated from increasingly larger samples: small samples on the left and larger samples to the right (on the x-axis).
Each point on the zig-zag line is the mean calculated from a random sample. The true mean of the population is 0.
The y-axis shows what the mean is for a sample of that particular size. Though the y-values vary here, remember that if the sample were a good estimate of the population, the y-values should be very close to 0.
You can see that when the samples are small the sample mean isn’t necessarily a good representation of the population that it was sampled from–and that is not a good thing.
Describe the steps for constructing the sum of squares
Describe how the standard deviations can allow us to determine which data values are common or rare
Write a function for the variance and standard deviation
Explain why the sample variance would be downwardly biased if we did not correct it by diving by (n-1)
Measures of spread
After successfully computing the mean, you return to the memory of the first day you had collected data. There was one teacup giraffe that was your favorite– it was relatively small with purple spots and perky tail! You begin to wonder how rare it would be to encounter a giraffe smaller than this one. To answer this question, you need to be able to calculate a measure of spread.
You might start by quantifying the simplest measure of spread, the range. This at least tells us the boundaries within which all the sample heights fall, but the range ignores important contextual information. For example, two data sets can have very different spreads but still have the same range.
If we want to avoid undue influence of outliers for the measure of spread, the range is not good enough to provide us with a wholistic, robust measure.
What is a more stable measurement? The answer is the variance.
Variance in plain language
You need a solid understanding of variance in order to grasp the mechanics of any statistical test. But what does the concept variance really capture?
Recall the normal distribution: when we inspect the distributions below visually, we see that they all have the same mean, but some distributions are more spread out. Bell curves that are more “squished together” are composed of observations that are more similar to one another, while bell curves that are more “spread out” are composed of observations that have greater variability. Wider bell-curves mean greater variance! In plain language, the variance gives us an idea of how similar observations are to one another, and to the average value.
How to calculate variance
Let’s begin by going through the steps and equations for calculating the variance of a population. We’ll explain how to modify this for calculating the sample variance later on.
First, the idea is to capture how far away individual observations lie from the mean. In other words, we could subtract the mean from each observation. This is called the deviation from the mean. And since we’re calculating a population variance, we will use \(\mu\) for the mean instead of \({\bar{x}}\).
Calculating the deviations is a great start, but we’re back to the problem of needing to summarize multiple values. Since these newly calculated values are both negative and positive, we quickly realize that adding them up (like the first step when calculating the mean) would not be a productive idea since the negatives cancel the positives.
What’s the easiest thing to do when you want to retain how far away a point is from the mean irrespective of whether it’s above or below the mean? How about taking the absolute value?
Though the absolute value of the deviation would be a valid measure of distance from the mean, it turns out that it has some mathematical properties that don’t make it the best choice, especially for more complex statistial analyses involving the variance later down the line.
Why we square the deviations
There is an alternative with simpler, “better behaved” mathematical properties: squaring the deviations. Squaring will always give us positive values, so the values can never cancel each other out. It’s worth pointing out, however, that a consequence of squaring deviations will tend to amplify the influence of values at extreme distances from the mean. You can read this thread for a more detailed discussion about absolute values versus squared deviations.
Sum of squares
Now we have positive, squared deviation values that can be summed to a single total. We call this total the sum of squares, and the equation is shown below.
The sum of squares is an important calculation that we will see again for other statistical operations. The animation below illustrates how these sums of squares are “constructed” starting with the sample observations and then squaring each one’s distance away from the mean.
Once the squares have been “constructed”, we sum their squares, producing a single value.
Variance, \(\sigma^2\)
We need to take into account how many observations contributed to these sum of squares. So, we divide the sum of squares by N. This step essentially takes the average of the squared differences from the mean. This is the variance.
The problem with variance is that its value is not easily interpretable, the units will be squared and therefore not on the same scale as the mean. It would not be very intuitive to interpret giraffe heights written in millimeters squared! The standard deviation fixes that. We “un-square” the variance, and now we return to the data’s original units (millimeters). The standard deviation equation is below:
One more thing: the equations above are for calculating the variance and standard deviation of a population. In real life applications, the population equations will almost never be used during data analysis. To calculate the variance and standard deviation for a sample instead, we will need to divide by n-1 instead of N, which we explain at the end of this module. Note that we also change to the corresponding symbols for the sample mean (\(\bar{x}\)), sample size (lowercase \(n\)), and use a lowercase \(s\) in place of \(\sigma\).
When we apply this change, our equation for the sample variance, \(s^2\) is:
\[\begin{equation}
\tag{5}
\Large s = \sqrt{\frac{\sum_{i=1}^n (x_i - \bar{x})^2}{n-1}}
\end{equation}\]
Meaning of the standard deviation
Since we’re now focusing on samples, let’s think about how we can apply the standard deviation in a useful way to normal distributions to predict how “rare” or “common” particular observations in a data set may be. For the normal distribution, almost all of the data will fall within ± 3 standard deviations from the mean. This rule of thumb, called the empirical rule, is illustrated below and you can read more about it here.
The entire normal distribution includes 100% of the data. The empirical rule states that the interval created by 1 standard deviation above and below the mean includes 68% of all the data. Observations within these bounds would be fairly common, but it would not be exceedingly rare to observe data that fall outside of these bounds.
2 standard deviations above and below the mean encompasses approximately 95% of the data. Observations that fall within these bounds include the common and also infrequent observations. Observations that fall outside of 2 standard deviations would be uncommon.
3 standard deviations above and below the mean encompass 99.7% of the data, capturing almost all possible observations in the set. Observations that fall oustide of these bounds into the extremes of distribution’s tails would be exceedingly rare to observe (but still possible if you sample large enough groups to detect these rare events!).
Example
Let’s calculate the variance and standard deviation using 6 observations of giraffe heights from a subset of our data, including your favorite small one with the purple spots.
Calculate the sample mean, \(\bar{x}\):
h <- c(113, 146.5, 132, 70.5, 121, 55)
mean(h)
## [1] 106.3333
We’ll plot the mean \(\bar{x}\) below with a gray line.
(2) Find the deviation from the mean, the difference between each giraffe’s height and \(\bar{x}\).
Calculate Variance: Square the deviations, add them all up to get the sum of squares, and then take the average of the sum of squares (adjusted to “n-1” because we’re using a sample).
SS <- sum(deviation^2)
variance <- SS/(length(h)-1) # Divides by N-1
variance
## [1] 1290.167
Standard Deviation: Take the square root of the variance.
sqrt(variance)
## [1] 35.91889
Because the standard devation is a standardized score– we can now focus on particular giraffes and see whether or not they lie within 1 standard deviation of the mean.
We see the little blue spotted giraffe is more than 1 standard deviation below the mean– and so we can conclude that a little guy of his height is rather short– even smaller than your favorite! Similarly, the giraffe with bright pink spots is taller than 1 standard deviation above the mean– quite tall!
Standard deviation application example
Using the standard deviation and the empirical rule described earlier, we now finally have the tools to answer our original question from the start of the module: how probable it is to find a giraffe smaller than our favorite purple-spotted one?
Our giraffe of interest happens to be almost exactly 1 standard deviation below the mean, so this makes it easy to assess the probability of encountering a giraffe shorter than him.
If we assume our sample comes from a normally distributed population, then what percentage of giraffes will be shorter than the one with purple spots?
We can apply the knowledge that the full percentage area under the curve is 100%, and what we know from the empirical rule, to conclude that there is approximately 16% of giraffes will be shorter than the one with purple spots. So, it would be common to find giraffes taller than our favorite but somewhat of a treat to find ones smaller–like the blue one!
Maybe this explains why the little blue spotted giraffe is so cute— it is not so common to find ones so small!
Code it up
Using (4) and (5), it’s easy to translate the equations for the variance and standard deviation into code in R.
In the window below, you will write two separate functions, one to calculate the sample variance and another to calculate the sample standard deviation. Name your functions my_variance and my_sd.
Test your functions on the vector heights_island1 and compare the output of your “handwritten” functions with the base R function of var( ) and sd( ).
Population vs Sample (\(N\) vs \(n-1\))
We have to correct the calculated variance by dividing by \(n-1\). Let’s explain why:
Let’s recall that when we calculate the sum of squares, we only have the sample mean \(\bar{x}\) to go off of as our center point.
We must first acknowledge that while the population \(\mu\) is unknowable, the chance that the sample \(\bar{x}\) and the population \(\mu\) are the same is unlikely.
It’s also worth pointing out that the risk that \(\bar{x}\) and \(\mu\) are not even values close to each other is much increased when \(\bar{x}\) has been calculated from a small sample.
Recognizing that the true population mean value is probably some other value than \(\bar{x}\), let’s recalculate the sum of squares. This time we will use an imaginary true population \(\mu\) as our center point, which in the animation below will be represented with a line at an arbitrary distance away from \(\bar{x}\).
When we compare the sum of squares in both of these scenarios: 1) using \(\bar{x}\) or 2) using our imaginary \(\mu\), we see that the sum of squares from \(\mu\) will always be greater than the \(\bar{x}\) sum of squares. This is true because by definition of being the sample mean, the line at \(\bar{x}\) will always be the “center” of the values in our sample. Its location already minimizes the total distance of all the observations to the center. A line at any other location (i.e. \(\mu\)) would be a line that is not mimimizing the distance for observations in our sample.
Therefore, when we calculate the sum of squares (and consequently, the variance and the standard deviation) using the sample mean \(\bar{x}\), we are most likely arriving at a value that is downwardly biased compared to what the true variance or standard deviation would be if we were able to know and use the population mean \(\mu\).
This is why we need to adjust our sample variance by diving by \(n-1\) instead of just \(N\). By diving by a smaller value (i.e. \(n-1\) instead of N), we ensure that the overall value of the variance and standard deviation will be a little larger, correcting for the downward bias we just described.
Things to think about
How badly might the sample variance be downwardly biased?: Well, it depends on how far away \(\bar{x}\) is from the true \(\mu\). The further away it is, the worse the downward bias will be!
Of course, we want to avoid having a very downwardly biased variance. What controls how far away \(\bar{x}\) is from \(\mu\)? The sample size! As pointed out previously, the larger the sample, the greater the likelihood that your sample mean will resemble the population mean.
Press Play on the animation below. The plot shows the relationship between bias in the variance, the sample size, and the distance between \(\bar{x}\) and \(\mu\). Each dot represents one out of a thousand random samples all from the same population. The vertical dotted line represents \(\mu\), and the horizontal dotted line represents the true population variance (animation inspired by Khan Academy video.)
When the samples whose means \(\bar{x}\) are far off from the true population mean, they tend to have downwardly biased variance.
Take a look at the points that are furthest away from the true population mean– the samples represented by these points primarily came from small sample sizes (dark blue dots).
How the correction works
The plot below shows the percentage of the true population variance that an uncorrected sample variance achieves on average. These data were generated by sampling from the same population as the animation above. This time the data have been grouped into bars by how many observations each random sample had. (Animation inspired by Khan Academy video)
Notice that the variances from smaller samples do the worst job of approaching 100% of the true variance. In fact, without correction the sample variance is downwardly biased by a factor of \(n/(n-1)\).
You can hover over the bars above to see what the average percentage of the true variance actually is for the different samples sizes. If we multiply this percentage by the correction, we fix the discrepancy between sample and population variance. We demonstrate this below for samples of size n = 3.
n = 3
correction = n/(n-1)
hover_value = 67.22902 # % value when hovering over bar for n = 3
# Apply correction
percent_of_true_variance <- hover_value * correction
percent_of_true_variance
## [1] 100.8435
As we can see, the correction works by adjusting the downwardly biased sample variance to close to 100% of the true variance.
Try hovering over a few other bars and see yourself that correction works independent of the sample size. You can use the window below as a calculator to change the N and the hover values and then run the code.
This project was created entirely in RStudio using R Markdown and published on GitHub pages.
================================================
FILE: docs/05_correlation.html
================================================
Covariance and Correlation
Identify the similarities and differences between calculating the variance and covariance
Write a function for the covariance and Pearson’s correlation coefficient
Interpret the meaning behind Pearson’s correlation correlation
Describe the purpose of dividing by the product of the standard deviations when calculating the correlation.
Gathering data on another variable
Over the course of your time on the islands, you notice that the teacup giraffes seem to have an affinity for celery, which you have already used to entice so they come close enough for a height measurement. Suprisingly, one of the small ones had eaten so much of it! You decide to quantify how much celery each of the giraffes consumed to see if there is any relationship to height.
You systematically measure the amount of celery eaten and add it to your log of data, which is stored as a data frame called giraffe_data.
We can check out the first entries of the data frame by using the head( ) command:
It’s difficult to get an idea if there’s any relationship by just looking at the data frame. We learn so much more from creating a plot. Let’s revisit our newly acquired ggplot skills to make a scatter plot.
A lot of the code used previously can be re-used for the scatter plot. Two main differences are the following:
Because we now have an additional variable, we need to assign a y = for the aes( ) command within ggplot( ). Create the plot so that Celery_Eaten will be on the y-axis.
Add (+) a geom_point( ) element instead of geom_hist( )
Also, we will add lines to our plot, representing the mean of each variable. Here’s how we’ll do that:
Add a horizontal line by adding (+) a geom_hline( ) component. This takes the argument yintercept =, which equals the value for where the horizontal line should cross the y-intercept.
Since we want to place this line at the mean of y variable (\({\bar{y}}\)), we can use the mean( ) function instead of specifying a numeric value directly.
Add a vertical line by following the same structure as above but using geom_vline( ) and xintercept = instead. This vertical line will represent the mean (\({\bar{x}}\)) of the heights.
Construct a scatter plot of the data using the window below:
Great, now you have a scatter plot! But recalling the email from your advisor the last time you plotted data, you decide you’d like to customize the look of the plot the same way you did when you created the ggplot histogram.
Play around with some aesthetics in the window below, and then run the solution to see what we chose.
Looking at the scatter plot, there seems to be a relationship between height and celery eaten, but you will need to quantify this more formally to be sure.
Relationship between two variables
How can the relationship between two variables (and its strength) be quantified? This can be done by assessing how the two variables change together – one such measure is the covariance.
The covariance elegantly combines the deviations of observations from two different variables into a single value. This is how it’s done:
As we did for the variance, we begin by measuring how far each observation lies from its mean. But unlike when we calculated the variance, each observation now includes two variables. We will need to calculate the observation’s distance from each variable’s mean. We call each distance the deviation scores on x and on y, respectively. Like the variance, the observation can fall above or below the mean, and as a result the corresponding deviation score will have either a positive or negative sign.
We observe this below with a subset of 5 points from our scatter plot. Positive deviations are shown in red, negative deviations in blue.
points
Why do we use the deviations? Because we want to know whether the x-values and y-values move together with respect to their means or not. For example, when an observation’s deviation on x is above \(\bar{x}\), will its deviation on y also be above \(\bar{y}\)? Using this line of thought, we can begin to systematically characterize how “together” the x and y values will change as we go through all observations.
Crossproduct
After obtaining the deviation scores, we need to combine them into a single measure. We do not simply combine the deviation scores themselves by adding (please see the page about the variance for a discussion of why this is). Instead, we take both deviation scores from the same observation and multiply them together to create a two-dimensional “shape” (analagous to when we multiplied a single deviation score by itself to create a square in the variance calculation).
NOTE: The resulting value is now on the order of a “squared” term. This will become important later.
Multiply the two deviation scores. This is called the crossproduct. The shapes created by the crossproduct will serve as the “squared” terms that we can then use in the next step to sum and summarize the deviations into a single value.
The equation is shown below for the sample crossproduct of the deviation scores.
First, we should note that the overall sign of the crossproduct will depend on whether or not the two x- and y- values from the same observation move in the same directions relative to their means. Look at the annotated plot below. The crossproducts will either create “negative” shapes (shown in blue) or “positive” shapes (in red). A third outcome is that the crossproduct could also be 0 – this will occur when an observation falls on the mean.
Second, the magnitude of the crossproduct will scale with the absolute value of the deviation scores. In other words, the further away both deviation scores are from their means, the larger the area of their shapes will be.
The animation below shows the “construction” of the crossproducts from the 5 observations we have been following:
Sum the crossproducts. The sum of the crossproduct gives us a single number.
As we add the crossproducts, some of the “negative” and “positive” values of the shapes will cancel each other out. This is okay because this tells us important information about our two variables. If the negative and positive shapes cancel each other out completely, it would mean that there is no relationship. In most cases this will not happen, and the sum of the crossproducts will be positive or negative. In general, the larger the magnitude of the sum of the crossproducts, the more strongly the two variables move together. The equation is shown below.
We then divide the sum of the crossproduct by \(n-1\) (or \(N\) in the population equation) so that we have taken into account how many observations contributed to this quantity. (Why \(n-1\)? See here.) This final number is called the covariance, and its value tells us how much our two variables fluctuate together. The higher the absolute value, the stronger the relationship.
The equation for the covariance (abbreviated “cov”) of the variables x and y is shown below. As a preference of style, we multiply by \(\frac{1}{n-1}\) instead of dividing the entire term by \(n-1\).
However, the covariance is not an intuitive value. Remember that we have been working with terms that are on the “squared” scale, which is not only difficult to interpret (just like the variance is) but it is also the product of two variables on possibly different scales. How could we interpret a covariance with units of millimeters*grams mean?
Another point to make is that value of the covariance will be vastly different if we had decided to change the units (millimeters vs centimeters, or grams vs kilograms).
As a result, the covariance is not an easy metric to work with or to compare with other covariances. So we need to standardize it!
Pearson correlation coefficient, \(r\)
How do we standardize the covariance?
The solution is to (1) take the standard deviations of each variable, (2) multiply them together, and (3) divide the covariance by this product – the resulting value is called the Pearson correlation coefficient. When referring to the population correlation coefficient, the symbol \(\rho\) (pronounced “rho”) is used. When referring to the sample correlation coefficient, a lowercase \(r\) is used (often called “Pearson’s r”).
Here is the equation for the population correlation:
This equation is used for the sample correlation:
\[\begin{equation}
\tag{5}
\Large r(x,y) = \frac{ \frac{1}{n-1} \sum_{i=1}^n (x_i - \bar{x})(y_i - \bar{y})}{s_x s_y}
\end{equation}\]
What does the correlation mean?
We can interpret the correlation as a measure of the strength and direction of the relationship between two variables. It is a “standardized” version of the covariance.
The correlation will always be between -1 and 1. At these extreme values, the two variables have the strongest relationship possible, in which each data point will fall exactly on a line. When the absolute value of the correlation coefficient approaches 0, the observations will be more “scattered”.
The sign of the correlation coefficient indicates the direction of the linear relationship. When \(r\)= 0 there is no relationship between the variables. Look at the figure below to see what observations of different \(r\) values look like.
Your turn
Imagine you’re given a plot like the one below. What would you say it’s correlation value is? Try out your guess for a few plots, and if you need a hint to help you visualize, click Show trend line.
Code it up
In the window below, write your own function to compute the sample covariance of two variables, and call it my_covariance( ).
Then create a second function called my_correlation( ) in which you will compute the correlation of two variables. You may incorporate your function my_covariance( ) in this step to save yourself some time.
Once you’ve created both functions, use them to compute the covariance and correlation between Heights and Celery_Eaten within the data frame giraffe_data.
Finally, compare your functions’ outputs with the base R functions for covariance, cov( ) and cor( ).
Remember, you will need to write both functions so that they will take two parameters, one for each variable. The parameters for my_covariance( ) have been setup for you.
Wow, you can see that there is a negative relationship between giraffe heights and how much celery teacup giraffes eat. Could this be due to celery being a negative calorie vegetable? Are these giraffes onto something?
The Standardizer
You can take a look at the animation below to see a conceptual summary of how correlation will standarize the covariance, translating it into an easily interpretable metric that will always be bound by -1 and 1.
Why divide by \(\sigma_x\sigma_y\)?
Well it’s complicated, (see here and here) but it builds on the mathematical principle that the covariance of x and y will never exceed the product of the standard deviations of x and y. This means that the maximum correlation value will occur when the absolute value of the covariance and the product of the standard deviations are equal.
If you don’t take our word for it, press play below to see what the relationship between \({s}_x{s}_y\) and \({cov(x,y)}\) looks like.
As you look at the plot above, you may have the following questions:
Why are there clearly defined boundaries?
Because at the edges is where the covariance is the greatest value that it can be– it is equal to the product of the standard deviations there.
The slope is 1 at this boundary.
Where in the plot do the strongest correlations end up?
On the edges – when the absolute value of the numerator and denominator of the equation are equal– the quotient will = 1 (or negative 1, depending on the sign of the covariance in the numerator).
Things to think about
Correlation does not capture relationships that are not linear: If the relationship is not linear, then correlation will not be meaningful. Check out the plot below. There is a clear U-shaped relationship between the two variables, but the correlation coefficient for these data is very close to 0. To measure non-linear relationships a different metric must be used.
Correlation is not causation: Just because there is a linear relationship between two variables does not mean we have evidence that one variable causes the other. Even if there really was a cause-and-effect relationship, with correlation we cannot say which variable is the cause and which is the effect. It’s also possible that there exists some other unmeasured variable affecting the linear relationship we observe. And of course, any apparent relationship may be due to nothing more than random chance.
This project was created entirely in RStudio using R Markdown and published on GitHub pages.
================================================
FILE: docs/06_standardError.html
================================================
Intro to Inference
Determine how to quantify the uncertainty of an estimate
Describe the concept of statistical inference
Interpret sampling distributions and explain how they are influenced by sample size
Define and calculate standard error
Use the standard error to construct 95% confidence intervals
How accurate is our estimate of the mean?
Let’s revisit the first few days during which we collected data stored in the vector heights_island1. We were able to verify that the heights were normally distributed and calculated our sample mean, \({\bar{x}}\). However, we know that \({\bar{x}}\) is only an estimate of the true population mean, \({\mu}\), which is the true value of interest. It is unlikely that we will ever know the value of \({\mu}\), since access to all possible observations is rare. Therefore we will have to rely on \({\bar{x}}\) estimates from random samples drawn from the population as the best approximation of \({\mu}\).
Not all sample means are created equal. Some are better estimates than others. Recall the animation showing the relationship between sample size and variability of the mean. As we learned from this animation, in the long-run, large samples are necessary to get an accurate estimate of \({\mu}\).
A note about language: here, words like “accuracy”, “precision”, and “uncertainty” are used in a rather fast and loose way. We’re using the laymen’s application of these terms to refer to the long-run variability of estimates produced from repeated, independent trials. There are stricter, more formal statistical uses for these words, but for right now, we’re going to ignore these nuances so that we can move on with understanding these concepts in broad strokes.
One reason we care about our sample estimate’s accuracy is because we want to be able to answer questions about the population by making inferences. Statistical inference uses math to draw conclusions about the population based on a subset of the full picture (i.e. a sample). Subsets of data are of course limited, so it’s therefore important to acknowledge that the strength of the conclusions drawn about the population is dependent on the precision of the sample estimate. For example, say that we guess that the population mean value of giraffe heights on Island 1 is less than 11 cm. We can make some inferences about whether or not this is a good guess based on what we learn from our sample of giraffe heights. We’ll revisit this question a few times below.
Creating a sampling distribution
The mean of our sample of 50 giraffes from Island 1 was:
mean(heights_island1)
## [1] 9.714141
How can we quantify the accuracy of this estimate, given its sample size?
In theory, one way to illustrate this is to generate data not just from a single sample but from many samples of the same size (N) drawn from the same population.
Imagine that after you collected all 50 measurements for heights_island1, you wake up one morning with no memory of collecting data at all—and so you go out and collect 50 giraffe heights again and subsequently calculate the mean. Further imagine that this groundhog day (or more correctly, groundhog week) situation repeats itself many, many times.
When you finally return to your sanity, you find stacks of notebooks filled with mean values from each of your individual data collections.
Instead of viewing this as a massive waste of time, you make the best out of the situation and create a histogram of all the means. In other words you create a plot showing the distribution of the sample means, also known as a sampling distribution.
The animation below illustrates the process of creating the sampling distribution for 1,000 sample means.
On the left side, each histogram represents a sample (e.g. heights_island1 would be one sample, and we’re flashing through 1,000 of them in total). Correspondingly, each dot signifies an observation. After each sample histogram is completed, \({\bar{x}}\) is calculated. This \({\bar{x}}\) value is then subsequently added to the histogram of the sampling distribution on the right. As you can see below, this process is repeated, allowing the sampling distribution to build up.
A histogram of the sampling distribution is shown below. It is a histogram made up of many means.
Looking at the spread of \({\bar{x}}\) values that this groundhog experience generated, we can get a sense of the range of many possible estimates of \({\mu}\) that a sample of 50 giraffes can produce.
The sampling distribution provides us with the first hint of the precision of our original heights_island1 estimate, which we’ll quantify in more detail later on, but for now it’s enough to notice that the range of possible \({\bar{x}}\) values are between 8.9 and 10.7. This means that \({\bar{x}}\) values outside of this range are essentially improbable.
Let’s return to our question about whether the true mean of giraffe heights on Island 1 is less than 11 cm. Our sampling distribution suggests that \({\mu}\)is less than 11 cm, since values greater than that are not within the range of this sampling distribution.
Sample size and sampling distribution
Back to the idea that larger samples are “better”, we can explore what happens if we redo the groundhog scenario, this time sampling 500 individuals (instead of 50) before taking the mean each time, repeating this until thousands of \({\bar{x}}\) values have been recorded. For completeness, let’s imagine the same marathon data collection using samples that are smaller—of 5 giraffes each. We compare the resulting sampling distributions from all three scenarios below. The middle sampling distribution corresponds to the sampling distribution we already generated above.
What do we notice?
All histograms look normal.
All distributions have approximately the same mean.
Distributions generated from larger samples are less dispersed.
We can take the mean of the sampling distribution itself– the mean of the sampling distribution is a mean of means. This mean can be interpreted to be the same as a mean that would have resulted from a single large sample, made up of all the individual observations from each of the samples whose \({\bar{x}}\) values are included in the sampling distribution.
Note that if we had only generated a sampling distribution made up of samples of 5 giraffes, we would not have been able to exclude 11 cm as a possible value for \({\mu}\). In fact, if we were to draw a vertical line in the middle of each of the sampling distributions (the mean), we can tell that the population mean is likely even less than 10 cm.
In the following window, you will test the relationship between sampling distribution and sample size. The function below (behind-the-scenes code not shown) will plot a sampling distribution made up of 1000 samples, with each sample containing N number of observations. Try setting N to a few different values. What does the resulting sampling distribution looks like? See if you can confirm for yourself that the above points are true.
Standard Error of the Mean
As we’ve done before, we want to summarize this spread of mean estimates with a single value. We’ve already learned how to quantify a measure of spread–the standard deviation. If we take the standard deviations of each of the three different sampling scenarios above, then we accept that distributions based on smaller samples should have larger standard deviations.
In the window below, calculate the standard deviation of each of the three sampling distributions (i.e. for N = 500, N = 50, and N = 5), and confirm that the italicized point above is true. (If you’re working in R locally, use your “homemade” standard deviation function from the Variance module.)
To complete this exercise, you will need to use the objects sampling_distribution_N500, sampling_distribution_N50, sampling_distribution_N5, which are vectors storing the thousands of \({\bar{x}}\) values from the corresponding groundhog sampling distributions.
When you calculate the standard deviation of a sampling distribution of \({\bar{x}}\) values, you are calculating the standard error of the mean (SEM), or just “standard error”. The SEM is the value that we use to capture the level of precision of our sample estimate. But, we need a better and more efficient way to arrive at this value without relying on a groundhog day situation. Keep reading to learn more.
A note about SEM: Here “standard error” will imply standard error of the mean. But we can technically calculate the standard error of any sample statistic, not just the mean. We’ll talk about that more in future modules.
Time for a tea break!
Standard error in practice
Deriving the equation used for calculating the standard error of the mean using theory (i.e. without going out and resampling MANY times) is a bit complicated, but if you’re interested, you can learn more about it here. Instead, we can capture the relationship between standard deviation, sample size, and standard error with the plot below.
The standard deviation in this plot is 2.1, which represents \({\sigma}\) for giraffe heights on Island 1. This population value is technically still unknown but can be deduced in theory by repeating the groundhog day example for the standard deviation instead of for the mean. It’s important to note that the plot would have the same shape regardless of what scenario or standard deviation we were using.
Can you figure out what the equation is for the SEM? Look at the plot above, hover over the points, and see if you can gather how standard error of the mean, standard deviation, and sample size are related. Here are some hints:
SEM will be on one side of the equation, standard devation, and N will be on the other.
The equation will involve division.
There is one more missing piece of the puzzle: When you look at the shape of the plot above. What type of function does this remind you of? We haven’t covered this explicitly, but take a look here and see if you get any ideas.
Use the window below as a calculator to see if you can figure out the equation for the SEM.
In case you weren’t able to figure it out, remember to check the Solutions tab in the exercise window or take a look at this link for the equation for calculating the SEM. Recall that we’re working with the sample (and not population) standard deviation (\(s\)), so make sure you find the correct equation.
Confirming that the SEM equation works
Let’s test out the SEM equation on our original sample of heights_island1 and compare it to what we would have gotten by taking the standard deviation of the sampling distribution example with the N= 50 case. Does the SEM seem like a good approximation of the standard deviation of the sampling distribution?
Below, you will use the object heights_island1, which contains our single sample of N=50, and the object sampling_distribution_N50, which contains the data from the corresponding groundhog sampling distribution.
Close enough! We wouldn’t expect these to be exactly the same because of sampling variability.
How do we apply the SEM?
Now that we have a better understanding of how to gauge the precision of our sample estimates, we can test our question about the \({\mu}\) being less than 11 cm once and for all.
To formally make inferences, we need to revisit the principles of the empirical rule to construct confidence intervals. (Confidence intervals are just one way to make inferences– we’ll discuss other ways later.)
Remember, that the SEM is just the standard deviation of the sampling distribution, so we can apply the empirical rule. As a result, ± 2 SEM from a point estimate will capture ~95% of the sampling distribution. Actually, we were a little bit sloppy earlier when we said 2 standard deviations captures 95% of a normal distribution; this will actually give you 95.45% of the data. The true value is 1.96 standard deviations–and this is what we use to construct a 95% confidence interval (CI).
Loosely speaking, a 95% CI is the range of values that we are 95% confident contains the true mean of the population. We want to know whether our guess of 11 cm falls outside of this range of certainty. If it does – we can be sure enough that the true \({\mu}\) of giraffe heights on Island 1 is less than 11 cm.
Use the window below to find out and make your first inference by constructing the 95% CI for the heights_island1 mean estimate!
The upper limit of our 95% CI is less than 11 cm, so the population mean of heights on island 1 is likely less than 11 cm. In the scientific community, this is a bonafide way of drawing this conclusion.
Things to think about
We’ve been a little fast and loose with our words. The formal definition of CIs is the following:
If we were to sample over and over again, then 95% of the time the CIs would contain the true mean.
Importantly, some examples of what the 95% CI does NOT mean are:
A 95% CI does not mean that it contains 95% of the sample data.
A CI is not a definitive range of likely values for the sample statistic, but you can think of it as estimate of likely values for the population parameter.
It does not mean that values outside of the 95% CI have a 5% chance of being the true mean.
The precise interpretation of CIs is quite a nuanced and rather hotly debated topic see here and becomes somewhat philosophical– so if these definition subtleties seem confusing, don’t feel bad. As mentioned in the blog post linked above, one recent paper reported that 97% of surveyed researchers endorsed at least one misconception (out of 6) about CIs.
This project was created entirely in RStudio using R Markdown and published on GitHub pages.
Determine how to quantify the uncertainty of an estimate
Describe the concept of statistical inference
The Two Islands
You’d like to make an inference to formally investigate whether you have any reason to believe that the mean of heights on Island 1 differs from the mean of the heights on Island 2.
The means of the heights of both islands are shown below:
mean(heights_island1)
## [1] 9.714141
mean(heights_island2)
## [1] 18.09671
It’s obvious that the mean of the two samples we took are different, but the main question is if the population means are different?
This question is of interest because if the two population means are different, it may indicate that a cool evolutionary story is at play. For example, based on your experience it seems clear that the two groups of giraffes have been isolated. Their tiny stature would make it near impossible to move back and forth between the two islands, hence impeding mixing between the two groups. Over time, selection pressures could then have made the two groups distinct regarding height.
How do we test this?
Testing for group difference
When testing for group mean differenes, [it is much more common for a researcher to be interested in the difference between means than in the specific values of the means themselves. ] stolen
When we focus on the mean difference, represented by \(\Delta_{\bar{x}}\) (read “delta x-bar”), we can ask: is the \(\Delta_{\bar{x}}\) meaningfully different from 0?
The sample mean difference is below:
mean(heights_island2) - mean(heights_island1)
## [1] 8.38257
This is just another estimate at the sample level whose precision we need to quantify to be able to make inferences.
Formally, statistical inference is about testing hypotheses (which we intentionally did not introduce in the previous module). In research, a hypothesis is a statement of a suggested outcome for a study [THIS SENTENCE IS TOO VAGUE]. The goal of statistcal inference is to reject the null hypotheses (\(H_0\), read “H-nought”), the default suggested outcome, which assumes that there is no association or difference between two or more groups. If we cannot reject \(H_0\), then it means we must accept the alternative, \(H_A\), that there is a meaningful difference or association.
Our null hypothesis is the following: the mean difference between the two populations is 0. The corresponding equations are shown below for the null and alternative hypotheses. Here, we use \(\Delta_{\mu}\), because we are referring to the population values.
One way to reject the null hypothesis would be to construct 95% CIs around the estimate for \(\Delta_{\bar{x}}\).
Pooled standard deviation
As we learned previously, a prerequisite to calculating 95% CIs is to calculate the standard error (which we know will require the sample standard deviation). However, we’re now working with two samples, so which sample’s standard deviation do we use? Can we use both?
In this particular case, the standard error of the mean difference can be calculated based on a combined, or pooled standard deviation (\(s_{p}\)) from two samples. We use the standard deviation of each sample, weighted by sample size.
One thing to point out is that standard deviations from samples cannot be added together – but variances can be, which is why in (1) we use \(s^2\) in the numerator only to take then the square root of the entire term.
We see that the variances are being multiplied by a term containing the sample size (\(n\)). In the case that each of our samples were differently sized, we would want to more heavily weight the variance of the sample that was larger. In our case now, both of our samples have the same \(n\), but we introduce the equation for more general cases.
If you’re wondering why \(n-1\) is necessary for the weighting, and not just \(n\), hold that thought and we’ll return to it later. But for now, we’ll just point out that it is not to adjust for the inherent bias when calculating sample variance (i.e. not the same reason that we used N-1 in the variance module [link]).
[DATA CAMP WINDOW WHERE THEY WILL CALCULATE THE S_P OF THE DATA]
Standard Error (SE) of the mean difference
After calculating the pooled standard deviation, the next step is to determine the standard error of the mean difference. We will follow the same logic as we did when calculated the standard error based on a single sample – except now we will use the pooled standard deviation. Again, we will be adding the terms from the two samples together, so to be able to sum these we need to use the pooled variance (the square of the pooled standard deviation.)
[DATA CAMP WINDOW WHERE THEY WILL CALCULATE THE SE OF THE MEAN DIFF]
Now that you know the standard error, you are ready to construct the 95% CI around the mean difference. As before, this will be the mean difference plus/minus 1.96 * \(SE_{({\bar{x_1}-\bar{x_2})}}\).
Now we can see whether or not our null hypothesis can be rejected. If the 95% CI for values of \({({\bar{x_1}-\bar{x_2})}\) includes 0, then we do not have reason to believe that the population means are different.
[Construct interval for yourself below, DATA CAMP, INSTRUCTIONS]
Our 95% CI does not include 0 by a lot! So we can conclude that the population means from Island 1 and Island 2 are mostly likely distinct. In other words, we can reject \(H_0\), the null hypothesis. In doing so, we say that the mean difference is statistically significant.
[T test function will calculate the 95CI using the t distribution @ 97.5%m, which doesn’t always give 1.96]
P-values
Even though CIs are great tools for inference, you are probably most familiar with seeing p-values in scientific literature. The p-value that is output from your statistical test gives you a metric that tells you whether or not your results are statistically significant. Typically, p-values < 0.05 meet this criterion.
The “p” in p-value stands for “probability”. Probability of what? The p-value is the probability that you would have gotten your results or something more extreme if in fact the null hypothesis were true. “More extreme”, in this case, would be a mean difference even greater than what we see in the present data. The lower the p-value, the less likely you’d be getting your results due to chance alone.
Now let’s derive the p-value for the mean difference of the two island heights, and make sure that we can draw the same conclusion that we did when we used the 95% CI for inference. In order to do this, we will use a statistical test for comparing two groups: the t-test.
The t-test
The t-test is all about the t-statistic, which is produced when the mean difference is divided by the standard error. When we divide by the standard error, we turn our mean difference estimate into a unitless metric that is not dependent on the scale that means were recorded with (i.e. centimeters). Furthermore, dividing by the standard error also will also account for the uncertainty in the estimate.
The t-statistic is essentially combining whatever our sample estimate is (e.g. mean difference, sample mean, etc.) and its uncertainty (i.e. standard error) into one value.
we convert the t-statistic into a p-value via the a specific kind of distribution called the t-distribution. [plot, here’s an example of one]
the further out in the tails of the t-distribution that the t-statistic falls. The lower the pvalue will be.
Degrees of freedom; Hasse has some ideas.
A formal definition of hypothesis including null hypothesis.
Implement, and then formally answer the question.
Degrees of Freedom
You know the final answer of a single estimate. You can pick whatever you want, until you’re down to the last choice….then you don’t have the “freedom”. –> explains why we use Total -1 = DF.
Explain Whyyyyyyyyy we need it.
The t-distribution will look different depending on the degrees of freedom
Intermediate step here between CI and p-values.
T-test statistic greater than 1.96 will result in a significant p-value.
Write your own t-test function.
Start with same N, but then “to make it a more useful model, you need to be able to handle when the sample sizes are not the same.”
Unequal variance: more fodder for things to think about
This project was created entirely in RStudio using R Markdown and published on GitHub pages.
================================================
FILE: docs/aboutTheAuthors.html
================================================
About the authors
ABOUT THE AUTHORS
HASSE WALUM
Hasse is a tall, Swedish teacup giraffe (one of the few giraffes from the North). When not munching on celery and doing creative writing, he spends his time working as an instructor at Emory University, coding in R, thinking about issues related to scientific rigor, and analyzing gene expression data.
DESIRÉE DE LEON
Desirée is a multidisciplinary teacup giraffe who is a recent PhD grad in neuroscience at Emory University, and also enjoys illustrating and bluegrass (both the music and the plant). Her graduate research involved using R to analyze pedigree data of cute monkey families living at the Yerkes National Primate Research Center.
Teacup giraffes were first discovered by the Swedish naturalist and priest Olof Torén, an apostle of Carl Linnaeus, during his travels on the Swedish East India Company ship Hoppet. Departing from the port of Gothenburg, Hoppet set sail on the 26th of January 1748, an extraordinarily gloomy day when sea and sky was a single ashen entity and winds from the northwest brought a snow-mixed horizontal rain. The mood onboard the ship was cheerful nonetheless, as the crew dreamt of the transparent waters of southern seas, filled with colorful beings and stories to bring back home.
One suffocating evening in July 1748, shortly after Hoppet had rounded the Cape of Good Hope and sailed east of Madagascar, captain Erik Moreen was attacked by a headache so vicious it made him seasick for the first time since the beginning of his nautical career. Desperate to ease the pain he called on the ship surgeon, a crooked man who spent most of his time investigating fungi in the deepest corners of the ship’s bilge where the air tasted like year old bread.
The surgeon brought a small bottle containing a beverage brewed of equal parts fermented goat milk and a distillate of mushrooms which, when dissolved, produced such a colorful luminance that the use of protective goggles was necessary to prevent achromatopsia. Ignoring the surgeon’s recommendation to limit the dose to a few drops of the brew, the captain emptied the bottle in one gulp and for a few moments lost all senses while the hair on the back of his head spun into permanent curls.
Although the pain subsided within less than thirty minutes, this was just the beginning of a nightmarish intoxication. The captain heard sirens sing lost lullabies from all directions, saw the kraken swallow the moon and spit it out as thousands of stars, and was convinced that all of his crew had turned into sharp-stemmed ferns, stationary and threatening to amputate him with every wavering step he took across the deck. During this confusion the captain tied himself to the fore-mast, prepared to fall head first into the flaming depths of the underworld. He was standing there as the sun rose, as his inebriation started to fade into melancholy, and saw two small islands appear on the horizon.
The ocean then formed a frothy path in front of Erik Moreen, from the bow to the two islands. In the captain’s still quivering mind time seemed to move backwards, and he tried to find solace from the rootlessness of this experience by searching his mind for pleasant memories. The path suddenly seemed indistinguishable from the road connecting the port of Gothenburg to his home in Guldheden, many nautical miles away. He even swore he could see a colony of black-backed gulls circling in the sky above him, establishing his notion that Hoppet somehow had made it back to the north. Confident he would soon be reunited with his wife, the captain gave orders to sail in the direction of the islands and would not listen to any objections until the ship was stranded on a sandbank no more than a stone’s throw away from its destination. Although Erik Moreen recovered from the surgeon’s medicament after a few days, the crew’s trust in him was not as easily restored and he was temporarily relieved of his duties as captain.
During the days it took the crew to dig Hoppet out of the sand, naturalist and ship priest Olof Torén visited the islands a few times but was apparently not very impressed by this occurrence. In his journals from this time, only one sentence makes mention of this part of the voyage:
Northeast of Madagascar lie two small islands not worth visiting for any other reason than the fact that they are inhabited by small, quadrupedal mammals with relatively long necks.
In line with the often undemocratic nature of human experiences, Olof Torén would encounter teacup giraffes twice over the course of a few years, while it would take centuries after they were first discovered before anybody else did. The second time Olof Torén visited the two small islands where teacup giraffes still live today, it was during his second and last intercontinental journey at sea, this time on the Swedish East India Company ship Götha Leijon. In 1750 he found himself back in the cerulean waters northeast of Madagascar. Again, it was an event characterized by pain that would bring the naturalist and ship priest to the teacup giraffe islands, but pain of the soul rather than the body.
In the evenings, Olof Torén and supercargo Anders Gotheen, a man who would not hesitate to let people know his personal values aligned perfectly with the new ideals of the intellectual revolution today known as the Enlightenment, enjoyed sitting down for a game of backgammon on the main deck. Or rather, Olof Torén enjoyed the game while Anders Gotheen, as he liked to say, enjoyed engaging in dialectic discourse while playing a game only slightly distracting since winning after all was mostly a question of luck. Olof Torén had noticed that conversations with Anders Gotheen often made him worried about things he had not previously contemplated and was therefore less amused by the dialectic aspect of their game nights.
This particular evening it seemed Anders Gotheen would definitely lose. He watched Olof Torén bear off three of his white checkers and as he collected the dice supercargo Gotheen turned to the naturalist.
– For the intellectually brave and honest, he said, no comfort can be found in the concept of infinity. Everything typical of human life, he continued, is characterized by the notion of a finite world, and therefore a free human mind can never truly be consoled by the appreciation of endlessness. He paused and drank some wine from a metal cup.
– This is also true for the afterlife, he then added.
The reason why, as Olof Torén would explain it a few days later during a one-way conversation with a group of patiently listening teacup giraffes, what Anders Gotheen said that evening had so effectively shaken the foundation of his world view, was not because it directly challenged his faith. No, it was the fact that what Anders Gotheen had said instead questioned how much existential relief any belief could offer whatsoever that resulted in a detrimental blow to his conviction.
For what felt like a very long time, Olof Torén listened to Anders Gotheen in detail describe the philosophical abyss all perspectives on life after death inescapably lead to. He started to feel lightheaded and ended the game early so that he could climb up to the upper deck to get some fresh air. It was a clear night and the sky seemed filled with an infinite number of stars. His head was now spinning, he lost his balance, made a futile attempt to reach for the shrouds, but inevitably fell overboard.
Olof Torén treaded water all night, motivated by his new fear of death. When the sun rose he for a moment forgot about the thoughts that had been plaguing him during those long hours in the dark water, because the early morning light draped the world in a calming emerald luster. He could now see he had drifted to only a yards away from the sandbank where Hoppet had been stranded a few years earlier. The sunlight was filtered through the foliage of a thousand palm trees on the islands between him and the rising sun and this is what created the green morning experience. He swam to the island closest to him and passed out on the beach, exhausted both physically and mentally.
When Olof Torén woke up the sun was about to set. As he slowly opened his eyes he saw a group of teacup giraffes walking towards him on the beach, and briefly thought they had come to welcome him back to the islands. But the small giraffe caravan walked past him and continued towards a large flat stone projecting out of the sand not far from where he was lying. In the light of dusk, everything on the beach seemed to cast extraordinarily long shadows and from where Olof Torén was positioned the stone looked enormous. The shape made him think of runestones, rocks Vikings would raise and decorate with runic inscriptions, but the size more resembled the moai stone statues of Easter island. The teacup giraffes took turns standing in front of the stone observing a large silhouette appear on the granite surface ahead. They seemed infatuated by watching their shadow twin move as they moved, grasping for palm tree leaves non-shadow teacup giraffes would never reach, and making low pitched vocal sounds reminding Olof Torén of larger animals. It was as if these giraffes knew about their unusually small stature and used the evening light to pretend they were tall, like their full-size relatives on the African mainland.
This time around Olof Torén spent two days in the company of teacup giraffes and grew to appreciate their cheerful demeanor, social competence and general friendliness. When not playing with shadows, chasing fireflies or resting in the crisp air next to a waterfall, they foraged the jungle for celery which seemed to be their favorite food. Time passed quickly on the islands because the teacup giraffes had an almost endless repertoire of delightful behaviors and at times it even appeared they adapted their conduct to what Olof Torén showed appreciation for.
On the Swedish East India Company ship Götha Leijon, the consensus was that Olof Torén was unlikely to still be alive, but most of the crew were god fearing men and the idea of leaving a priest to drown without a convincing rescue attempt seemed like the type of thing the almighty would never forget. Some of the them had been present for the Erik Moreen debacle a few years earlier and remembered the location of the islands from this incident.
Olof Torén saw the ship approach the islands from far away. After two days on the teacup giraffe diet he was famished because the more celery he ate the hungrier he became. Longing for the salted meats that made up a large part of the meals onboard Swedish East India Company ships, he said goodbye to his new four-legged friends and waded out in the water to meet the rowboat launched to pick him up. As he climbed onboard he turned around to catch a last glimpse of the giraffes and saw them gather on the beach for another evening shadow play ritual.
Back on Götha Leijon, Olof Torén wrote down all the details of teacup giraffes’ lives he could remember. During a game of backgammon, he tried to describe their unusual physique and social behaviors, but was soon interrupted by Anders Gotheen who said
– The human mind works in mysterious ways when deprived of energy
Olof Torén would in fact have a hard time convincing friends and colleagues, people he met as Götha Leijon sailed to Surat and Canton, as well as Carl Linnaeus, that his accounts of the teacup giraffe islands reflected reality. Maybe the feeling of not being trusted contributed to why his health started to deteriorate after his return to Sweden in 1752. On the 3rd of May 1753, a few months before his death, he wrote a last letter to Linnaeus. On the final page of this letter he drew a picture of two giraffes, one short and one tall. They were standing in front of a pair of heavy church doors and both their shadows stretched far above the tower and into the sky. The short giraffe, a female with pointy ears, looked like she was smiling.
Even today, when there are few true mysteries left in the world and all corners of the globe have been so carefully measured and illuminated that no colorless spots stain the pages of the modern atlas, teacup giraffes remain a relatively well kept secret. These animals were briefly mentioned in 9th edition of Carl Linnaeus’ Systema Naturae and given the Latin name Giraffa minima, but were removed before printing the 10th edition because of doubts about the trustworthiness of Olof Torén’s descriptions. No songs have been sung about them, Rudyard Kipling was oblivious to their existence when writing the Jungle Book, schoolchildren have never wondered how many teacup giraffes they could fit in a backpack, and they were never made famous by a Twinings advertisement campaign.
But in December 2018, the Olof Torén foundation in Uppsala decided it was time that teacup giraffes were given a more prominent place in the world of zoology. The foundation set aside money for a stipend and requested applications from “young aspiring scientists devoted to conducting old-school observational field work and interested in spending time on remote islands in the Indian Ocean studying possibly the least known mammal on the planet”. In the announcement they also wrote “One crucial piece of information missing about teacup giraffes regards their physique, because although it is known that they are small, no reliable records of their exact size can be found anywhere”. And this is true because people falling overboard rarely bring measuring equipment.
Hasse is a tall, Swedish teacup giraffe (one of the few giraffes from the North). When not munching on celery and doing creative writing, he spends his time working as an instructor at Emory University, coding in R, thinking about issues related to scientific rigor, and analyzing gene expression data.
DESIRÉE DE LEON
Desirée is a multidisciplinary teacup giraffe who is a recent PhD grad in neuroscience at Emory University, and also enjoys illustrating and bluegrass (both the music and the plant). Her graduate research involved using R to analyze pedigree data of cute monkey families living at the Yerkes National Primate Research Center.
This project was created entirely in RStudio using R Markdown and published on GitHub pages.
================================================
FILE: docs/assets/landing.css
================================================
@import url('https://fonts.googleapis.com/css?family=Lora:400,400i&display=swap');
/*Loads background-images in HTML as tags, so that they are already loaded when animated to display later*/
#preload {
display: none;
}
/* JS appear: */
/* After clicking, the class `.expanded` is added to the body tag, which applies the styles below to the elements below */
body.expanded .image-descript {
display: flex;
}
body.expanded .singleGiraffe-clickme {
display: none;
}
body.expanded .singleGiraffe {
display: block;
}
body.expanded .giraffeForest {
display: block;
}
/*------------NARRATIVE-----------*/
.narrative-container {
max-width: 650px;
margin: 4em auto;
}
.narrative-cover {
text-align: center;
margin: 0 auto 2em auto;
max-width: 650px;
background-color: white;
}
.narrative-illo-200 {
max-width: 200px !important;
display: inline-block !important;
margin: 0.5em 1em 0.5em 0.5em;
}
.narrative-illo-small {
max-width: 350px !important;
display: inline-block !important;
margin: 0.5em 1em 0.5em 0.5em;
}
.narrative-illo-big {
display: block !important;
max-width: 90% !important;
margin: 0.5em auto;
}
.narrative-text {
font-family: "Source Sans Pro";
line-height: 1.75;
}
.dialogue {
text-indent: 2em;
}
p.blockquote{
background: #fcf8f0;
border-left: 10px solid #f2e0c3;
padding: 1.5em 10px;
font-family: "Lora";
font-style: italic;
font-size: 0.85em;
margin: 1em 2em 1em 2em !important;
}
.slide-content > .narrative-text > p:first-of-type:first-letter { /*drop cap for first p that is a direct child of .narrative-text, which is in turn a direct child of .slide-content*/
color: #3fb5bd;
float: left;
font-family: 'Lora', serif;
font-weight: bold;
font-size: 7em;
line-height: 65px;
padding: 20px 8px 0 3px;
margin-bottom: 9px;
}
.narrative-text p {
margin-bottom: 1.5em;
}
.slide-content { /* space separating each "chapter" */
margin-bottom: 3em;
}
.end-text{
text-align: center;
margin-top: 100px 0 50px 0;
font-size: 3em;
font-family: 'Lora';
font-weight: 600;
color: #3fb5bd;
}
/*------------NAVBAR-------------*/
.navbar-default {
background-color: #ffffff33;
box-shadow: 0 0 50px rgba(255, 255, 255, 0.76);
background-image: linear-gradient(to bottom, #ffffff, #ffffff, #ffffffed, #ffffffba, #ffffff33) !important;
border: none;
}
.navbar-default .navbar-nav>li>a {
color: black;
}
.navbar-default .navbar-nav>.active>a,
.navbar-default .navbar-nav>.active>a:hover,
.navbar-default .navbar-nav>.active>a:focus
/*.navbar-default .navbar-nav>li>a:hover,*/
/*.navbar-default .navbar-nav>li>a:focus */{
color: #989898;
background-color: #ffffff33;
background-image: linear-gradient(to bottom, #ffffff, #ffffff, #ffffffed, #ffffffba, #ffffff33) !important;
}
/* Hovering over navbar + dropdown */
.navbar-default .navbar-nav>li>a:hover,
.navbar-default .navbar-nav>li>a:focus{
color: #989898;
background-color: white;
}
/* hamburger menu on mobile */
.icon-bar {
background-color: black !important;
}
.navbar-default .navbar-toggle:hover,
.navbar-default .navbar-toggle:focus {
background-color: #ffffff !important;
}
/* Whole document: */
body {
font-size: 19px;
}
.title{
display: none;
}
.main-container {
max-width: 1326px !important;
}
.container-fluid { /* margin: 0 Left-aligns container; margin: auto to "center" (Default)*/
margin-right: auto !important;
margin-left: auto !important;
}
.row-fluid{
display: flex;
position: relative;
}
.col-xs-12 .col-sm-4 .col-md-3{
width: 25%;
position: relative;
}
.tocify-extend-page {
height: 0px !important; /* Gets rid of extra space after footer*/
}
body, html {
height: 100%;
}
/*@media screen and (max-width: 300px) {
.image-descript {
display: none;
}*/
/* --------------v2 Landing Page---------------*/
.landing-container {
max-width: 700px;
}
.giraffe-container {
height: 500px;
margin: 0.5em auto 0em auto;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.singleGiraffe-clickme {
height: 400px;
width: 354px;
left: 52%;
margin-left: -177px; /*half the container's width*, along with left: 50%*/
position: absolute;
display: block;
cursor: pointer;
background-repeat: no-repeat;
background-size: contain;
background-position: bottom;
background-image: url("../images/Landing_page/singleGiraffe-clickme.jpg");
}
.singleGiraffe-clickme-hover {
height: 400px;
width: 354px;
left: 52%;
margin-left: -177px; /*half the container's width*, along with left: 50%*/
position: absolute;
display: block;
cursor: pointer;
background-repeat: no-repeat;
background-size: contain;
background-position: bottom;
background-image: url("../images/Landing_page/singleGiraffe-clickme-hover.jpg");
opacity: 0;
}
.singleGiraffe-clickme-hover:hover {
opacity: 1;
}
.singleGiraffe {
height: 400px;
width: 354px;
left: 52%;
margin-left: -177px; /*half the container's width*, along with left: 50%*/
position: absolute;
display: none;
background-repeat: no-repeat;
background-size: contain;
background-position: bottom;
background-image: url("../images/Landing_page/singleGiraffe.png");
animation: slide-up 1s forwards;
}
.giraffeForest {
position: absolute;
height: 500px;
width: 1200px;
left: 50%;
margin-left: -600px;
display: none;
background-repeat: no-repeat;
background-size: contain;
background-position: center;
}
.g1 {
display: block;
height: 500px;
background-image: url('../images/Landing_page/g1_1200px.jpg');
background-size: contain;
background-repeat: no-repeat;
animation: slide-in-from-top 1s forwards;
}
@keyframes slide-up {
0% { opacity: 1; transform: translateY(0px); }
100% { opacity: 1; transform: translateY(-15px); }
}
@keyframes slide-in-from-bottom {
0% { opacity: 0; transform: translateY(100px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes slide-in-from-top {
0% { opacity: 0; transform: translateY(-100px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes slide-in-from-right{
0% { opacity: 0; transform: translateX(100px); }
100% { opacity: 1; transform: translateX(0); }
}
@keyframes slide-in-from-left {
0% { opacity: 0; transform: translateX(100px); }
100% { opacity: 1; transform: translateX(0); }
}
.image-descript {
position: relative;
display: none;
animation: slide-in-from-bottom 1s forwards;
justify-content: center; /* flex horizontal center */
align-items: center; /*flex vertical center*/
}
.big-text {
text-align: center;
color: #ffffff;
visibility: visible;
font-size: 3em;
text-shadow: 1px 1px 35px #0e0d0d38;
letter-spacing: 0.065em;
}
.small-text {
color: white;
margin: 0 auto 5px auto;
text-align: center;
display:block;
font-family: 'Lora', serif;
font-size: 22px;
font-weight: 500;
line-height: 1.5em;
letter-spacing: 1px;
font-style: italic;
text-shadow: 1px 1px 35px #0e0d0d38;
}
/*------ LANDING WELCOME INFO----- */
.appears-after-click {
display: none;
}
.appears-after-click .about {
text-align: center;
font-family: "Source Sans Pro";
font-size: 1.2em;
color: #707070;
max-width: 725px;
margin: 0 auto 1em auto;
}
/* Tablet */
@media only screen and (min-width: 480px) and (max-width: 540px) {
.toc-content {
width: 100%;
}
}
@media only screen and (min-width: 541px) and (max-width: 649px) {
.toc-content {
width: 100%;
}
}
@media only screen and (min-width: 650px) and (max-width: 767px) {
.toc-content {
width: 100%;
}
}
/*iPad portrait*/
@media only screen and (min-width: 768px) and (max-width: 1023px) {
.toc-content {
width: 100%;
}
}
/*iPad Pro portarit */
@media only screen and (min-width: 1024px) and (max-width: 1225px) {
.toc-content {
width: 100%;
}
}
@media only screen and (max-width: 768px) {
.col-xs-12.col-sm-4.col-md-3 {
display: none !important;
}
}
/* New hero images */
.hero-container {
height: 550px;
background-color: #1b3a42;
margin-bottom: 2em;
}
.hero-image {
max-width: 1800px;
margin-top: -50px !important;
margin-left: auto;
margin-right: auto;
position: relative;
margin-bottom: 2em;
background-image: linear-gradient( rgba(0,0,0,.2), rgba(0,0,0,.2) ),
url(../images/big_image/Log.jpg); /* liner gradient tints the image darker for readability*/
height: 550px;
background-size: cover;
background-color: #1b3a42;
background-position: center bottom;
display: flex; /* Change to `display: none` for no hero image */
justify-content: center;
align-items: center;
}
.hero-image-text-little {
position: relative;
font-family: 'Lora', serif;
font-size: 24px;
font-weight: 500;
line-height: 1.5em;
letter-spacing: 1px;
font-style: italic;
font-weight: normal;
color: #fff;
text-align: center;
}
.hero-image-text-big {
position: relative;
letter-spacing: 0.065em;
line-height: 1em;
font-size: 68px;
text-transform: uppercase;
text-align: center;
display: block;
color: #fff;
margin-bottom: 2.5rem;
font-weight: bold;
}
@media only screen and (max-width: 770px) {
.hero-image-text-big{
font-size: 3rem;
padding: 2rem 0rem 0rem 0rem;
}
.hero-image-text-little{
font-size: 19px;
}
.hero-image {
max-width: 770px;
height: 300px;
}
.hero-container {
height: 300px;
background-color: #1b3a42;
margin-bottom: 1em;
}
.toc-content.col-xs-12.col-sm-8.col-md-9{
width: 100%;
padding-left: 1px;
padding-right: 1px;
}
}
================================================
FILE: docs/assets/landing_styles.css
================================================
* {
margin: 0;
padding: 0;
}
body {
font-size: 1em;
color: rgba(26,26,26,.9);
font-family: 'Open Sans', sans-serif;
background-color: #f5f5f5;
}
.branding-bar{
width: 100%;
height: 60px;
background-color: #22222270;
padding-right:4em;
text-align: left;
font-size: 1em;
color:#f7f7f7;
line-height: 80px;
position:fixed;
z-index: 999;
}
.button-link{
padding-left: 2em;
padding-right: 2em;
padding-top:1em;
padding-bottom:1em;
border-radius: 50px;
/* this is irrelevant, and just so the element can be visualised/displayed: */
margin: 2em auto;
border: 1.5px solid #fff;
color: #fff;
font-size: .75em;
text-decoration: none;
margin-right: 5%;
letter-spacing: 0.065em;
}
.button-link:hover {
color: #002878;
background-color:white;
}
.bar-link{
color: #fff;
text-decoration: none;
font-size: .75em;
text-transform: uppercase;
margin-right: 2em;
letter-spacing: 0.065em;
}
.bar-link:hover{
color:#f5f5f5;
font-style: bold;
}
.image-descript {
position: relative;
}
.main-image {
width: 100%;
display: block; /*This gets rid of the weird little space from inline-block*/
}
.image-text{
position: absolute;
top:30%;
left:0;
right:0;
bottom:0;
}
.lil-image-text {
color: #ededea;
margin-top: 20px;
margin-right: auto;
margin-bottom: 20px;
margin-left: auto;
padding-top: 2em;
text-align: center;
display:block;
font-family: 'Lora', serif;
font-size: 18px;
font-weight: 400;
line-height: 1.5em;
letter-spacing: 1px;
font-style: italic;
color: #fff;
}
.big-image-text {
letter-spacing: 0.065em;
line-height: 1em;
font-size: 68px;
text-transform: uppercase;
text-align: center;
display:block;
color: #fff;
}
.color-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,.1);
}
================================================
FILE: docs/assets/mobile_landing.css
================================================
/* IPHONE 6/7/8 */
/* In summary:
1. Makes text on image smaller
1. Gets rid of hover-specific styles/ images
*/
@media only screen and (max-width: 500px) {
.giraffe-container { /* takes margin-top from 0.5em to 0*/
margin: 0 auto;
}
.big-text {
font-size: 2.1em; /*down from 3em */
text-shadow: 1px 1px 35px #0e0d0d78; /*text shadow a little darker */
line-height: 1.2; /* makes smaller */
}
.small-text {
font-size:18px; /*smaller*/
text-shadow: 1px 1px 35px #0e0d0d78; /*text shadow a little darker */
}
.singleGiraffe-clickme-hover {
display: none;
}
.landing.about {
font-size: 1.1em; /*smaller*/
}
/*-------- BUTTONS on iphone-------- */
/*Takes hovered styles and applies them to the static element */
button.learn-more .circle .icon.arrow {
background-color: white;
}
button.learn-more .circle {
width: 100%;
}
button.learn-more .circle .icon.arrow {
background: none; /*Eliminates arrow's horizontal line*/
-webkit-transform: translate(1.7rem, 0);
transform: translate(1.7rem, 0);
}
button.learn-more.deemphasized .circle .icon.arrow {
background: none; /*Eliminates arrow's horizontal line*/
}
button.learn-more .button-text {
color: white;
}
button.learn-more.deemphasized .button-text {
color: #282936;
}
}
/*--------- IPAD portrait-------- */
@media only screen and (min-width: 501px) and (max-width: 768px) {
.big-text {
text-shadow: 1px 1px 35px #0e0d0d78; /*text shadow a little darker */
line-height: 1.2; /* makes smaller */
}
.small-text {
font-size:18px; /*smaller*/
text-shadow: 1px 1px 35px #0e0d0d78; /*text shadow a little darker */
}
/* Remove hovered styles for clickme giraffe and buttons*/
.singleGiraffe-clickme-hover {
display: none;
}
/*--BUTTON--*/
button.learn-more .circle .icon.arrow {
background-color: white;
}
button.learn-more .circle {
width: 100%;
}
button.learn-more .circle .icon.arrow {
background: none; /*Eliminates arrow's horizontal line*/
-webkit-transform: translate(1.7rem, 0);
transform: translate(1.7rem, 0);
}
button.learn-more.deemphasized .circle .icon.arrow {
background: none; /*Eliminates arrow's horizontal line*/
}
button.learn-more .button-text {
color: white;
}
button.learn-more.deemphasized .button-text {
color: #282936;
}
}
/* LARGE MONITORS */
@media only screen and (min-width: 1500px) {
.giraffe-container {
height: 500px;
margin: 2em auto 1em auto;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.singleGiraffe {
animation: none;
}
}
@media (max-width: 767px) {
.navbar-default .navbar-nav .open .dropdown-menu>li>a {
color: black !important;
}
}
================================================
FILE: docs/assets/mobile_narrative.css
================================================
/* IPHONE6/7/8 */
@media only screen and (max-width: 540px) {
.narrative-container {
margin: 0 auto 1em auto; /* reduce margin top */
}
.narrative-text {
padding: 0 16px; /* adds padding to sides */
margin: 0 auto; /* attempts to center */
}
.narrative-illo-200 {
max-width: 300px !important; /*makes larger, essentially full screen*/
}
.narrative-illo-small {
margin: 0.5em auto; /* override margin so doesn't overflow */
}
.narrative-illo-big {
max-width: 100% !important; /* overrides 90% */
}
p.blockquote {
margin: 0em 1em 0em 1em !important; /* makes wider */
}
.slide-content > .narrative-text > p:first-of-type:first-letter { /*drop cap*/
font-size: 4.5em;
line-height: 30px;
padding: 20px 5px 0 0;
}
}
@media (max-width: 767px) {
.navbar-default .navbar-nav .open .dropdown-menu>li>a {
color: black !important;
}
}
================================================
FILE: docs/assets/style.css
================================================
@import url('https://fonts.googleapis.com/css?family=Lora:400,400i&display=swap');
/*------------NAVBAR-------------*/
.navbar-default {
background-color: #22222270;
border: none;
}
/* Whole document: */
body {
font-size: 20px;
}
.title{
display: none;
}
h1 {
padding-top: 2em !important;
margin-top: -1em !important;
}
p {
margin: 1em 0 1em 0 !important;
}
ul {
margin-bottom: 1em;
}
.main-container {
max-width: 1326px !important;
}
.alert-info { /* old objective box */
padding: 1.5em;
background-color: #a0d1ef !important;
}
.alert-note {
color: #777777;
background-color: #f6f3f3;
width: 90%;
margin: 0 auto;
}
blockquote {
margin: 0 0 0 0 !important;
padding: 0.5px 21px; /* I changed from default of 10.5px 21px*/
}
.blockquote-note {
color: #777777;
background-color: #f6f3f3;
width: 90%;
margin: 0 auto;
}
.container-fluid { /* margin: 0 Left-aligns container; margin: auto to "center" (Default)*/
margin-right: auto !important;
margin-left: auto !important;
}
.row-fluid{
display: flex;
position: relative;
}
.col-xs-12.col-sm-4.col-md-3{
width: 35%; /* should agree with body width aka toc-content, overriding default 25% from cosmo theme */
position: relative;
}
/*-----------TABLE OF CONTENTS-------------*/
/* GIRAFFE LOGO */
#TOC::before {
content: "";
display: block; /*change from block to none for landing page */
height: 150px;
margin: 10px 10px 40px 10px;
background-image: url("../images/toc-giraffe-logo.jpg");
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
}
div.tocify {
width: 100% !important;
max-width: 260px;
max-height: 85%;
font-size: 16px;
}
.tocify {
border: none;
position: -webkit-sticky;
position: sticky;
top: 60px;
width: 100%;
}
#TOC {
position: -webkit-sticky !important;
position: sticky !important;
margin-left: auto !important;
margin-right: auto !important; /* to center the TOC within it's col-xs-12, etc. container */
}
.toc-content { /*actually, the body content*/
width: 65%
}
.list-group-item {
position: relative;
display: block;
padding: 10px 15px;
margin-bottom: -1px;
background-color: white;
border: none;
color: gray;
}
.list-group-item:hover {
z-index: 2;
color: DarkSlateGray;
background-color: white;
border: none;
}
.list-group-item.active{
z-index: 2;
color: black;
background-color: white;
border: none;
}
.list-group-item.active:focus {
z-index: 2;
color: black;
background-color: white;
border: none;
}
.list-group-item.active:hover {
z-index: 2;
color: DarkSlateGray;
background-color: white;
border: none;
}
/*---------FOOTER-----------*/
.foot_image {
width: 50px;
filter: grayscale(0%);
margin-left: 1em;
margin-right: 1em;
}
.foot {
font-size: .75em;
text-transform: uppercase;
color: #808080;
text-align: center;
}
.foot hr{
width: 90%;
}
.tocify-extend-page {
height: 0px !important; /* Gets rid of extra space after footer*/
}
/* DATA CAMP WINDOWS */
.powered-by-datacamp {
display: none !important;
}
.datacamp-exercise {
margin: 0 0 1em 0 !important;
}
/* LARGE IMAGE AT TOP OF EACH MODULE PAGE*/
.image-descript {
position: relative;
}
/*.main-image is for landing page*/
.main-image {
width: 100%;
display: block; /*This gets rid of the weird little space from inline-block*/
margin-top: -50px;
margin-bottom: 10px;
}
.button {
border: 2px solid white;
background-color: #0000001a;
text-align: center;
color: white;
padding: 1rem;
font-size: 2rem;
cursor: pointer;
margin: 0 auto;
width: 150px;
text-transform: uppercase;
}
.button:hover {
background-color: white;
color: DarkSlateGray;
text-decoration: none !important;
}
a.button-landing-page {
color: white !important;
border: none;
text-decoration: none !important;
}
a.button-landing-page:focus{
z-index: 2;
color: DarkSlateGray;
border: none;
text-decoration: none !important;
}
a.button-landing-page:hover{
z-index: 2;
color: DarkSlateGray;
border: none;
text-decoration: none !important;
}
body, html {
height: 100%;
}
/*.big_image is for module pages*/
.big_image {
margin-top: -66px;
margin-bottom: 2em;
width: 100%;
}
.image-text{
position: absolute;
top:30%;
left:0;
right:0;
bottom:0;
}
.lil-image-text {
color: white;
margin-top: 0px;
margin-right: auto;
margin-bottom: 20px;
margin-left: auto;
padding-top: 0px;
text-align: center;
display:block;
font-family: 'Lora', serif;
font-size: 22px;
font-weight: 500;
line-height: 1.5em;
letter-spacing: 1px;
font-style: italic;
font-weight: normal;
color: #fff;
}
/*@media screen and (max-width: 300px) {
.image-descript {
display: none;
}*/
.big-image-text {
letter-spacing: 0.065em;
line-height: 1em;
font-size: 68px;
text-transform: uppercase;
text-align: center;
display:block;
color: #fff;
margin-bottom: 3rem;
}
.color-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,.1);
}
/* Landing Page*/
.back-panel {
max-width: 1500px;
background: white;
margin: 0 auto;
margin-top: 3em;
padding: 0 0em;
}
.back-panel-contents {
width: 100%;
display: flex;
margin: 0 auto;
}
.back-panel-contents_row-reverse {
flex-direction: row-reverse;
}
.color-textbox {
width:70%;
background: #4accc2;
color: white;
text-align: center;
font-weight: normal;
margin-bottom: 3em;
}
.color-textbox-blue {
background: #5eade3;
}
.color-textbox-orchid {
background: orchid;
}
.color-textbox-title {
text-align: center;
font-weight: bold;
font-size: 3.2rem;
padding: 8rem 3rem 0rem 3rem;
}
.color-textbox-text {
font-size: 3rem;
padding: 3rem 8rem 0rem 8rem;
font-weight: normal;
text-transform: normal;
}
.landing-subimage {
width:30%;
margin-bottom: 3em;
}
.images-landing {
margin-bottom: 0em !important;
}
/* SMARTPHONES PORTRAIT */
@media only screen and (min-width: 300px) and (max-width: 479px) {
.lil-image-text {
font-size: 1rem;
}
.big-image-text {
line-height: normal;
font-size: 1.5rem;
}
.back-panel {
margin-top: 0;
}
.back-panel-contents {
display: flex;
}
.landing-subimage {
width: 75%;
margin: 0 auto;
}
.color-textbox {
width: 100%;
padding: 1.25rem;
margin-bottom: 1.5rem;
}
.color-textbox-title{
font-size: 2rem;
margin-bottom: 1.5rem;
padding: 0rem;
}
.color-textbox-text{
font-size: 1.5rem;
padding: 0rem;
}
}
/* Tablet */
@media only screen and (min-width: 480px) and (max-width: 540px) {
.color-textbox-title{
font-size: 1.5rem;
padding: 2.25rem 0rem 0em 0rem;
}
.color-textbox-text{
font-size: 1.4rem;
padding: 1rem 5rem 0rem 5rem;
}
.toc-content {
width: 100%;
}
}
@media only screen and (min-width: 541px) and (max-width: 649px) {
.color-textbox-title{
font-size: 2rem;
padding: 2.5rem 0rem 0em 0rem;
}
.color-textbox-text{
font-size: 1.75rem;
padding: 1rem 5rem 0rem 5rem;
}
.toc-content {
width: 100%;
}
}
@media only screen and (min-width: 650px) and (max-width: 767px) {
.color-textbox-title{
font-size: 2rem;
padding: 4rem 0rem 0em 0rem;
}
.color-textbox-text{
font-size: 2rem;
padding: 1rem 5rem 0rem 5rem;
}
.toc-content {
width: 100%;
}
}
/*iPad portrait*/
@media only screen and (min-width: 768px) and (max-width: 1023px) {
.color-textbox-title{
font-size: 2.35rem;
padding: 6rem 0rem 0rem 0rem;
}
.color-textbox-text{
font-size: 2.25rem;
padding: 1rem 5rem 0rem 5rem;
}
}
/*iPad Pro portarit */
@media only screen and (min-width: 1024px) and (max-width: 1225px) {
.color-textbox-title{
font-size: 3rem;
padding: 6rem 0rem 0rem 0rem;
}
.color-textbox-text{
font-size: 3rem;
padding: 1rem 5rem 0rem 5rem;
}
}
/* ABOUT THE AUTHORS CSS */
.image-text-about-the-authors {
top:44%;
}
.about-the-author-color-textbox {
width:33.3%;
background: white;
color: black;
text-align: center;
font-weight: normal;
margin-bottom: 1em;
margin: 0 auto;
}
.about-the-author-color-textbox-title {
text-align: center;
font-weight: bold;
font-size: 2.2rem;
padding: 4rem 3rem 0rem 3rem;
margin: 0 auto;
}
.about-the-author-color-textbox-text {
font-size: 2rem;
padding: 3rem 8rem 0rem 8rem;
font-weight: normal;
text-transform: normal;
}
.about-the-author-subimage {
width: 40%;
margin: 0 auto;
}
@media only screen and (max-width: 768px) {
.about-the-author-color-textbox {
width:100%;
}
.about-the-author-color-textbox-text {
min-width: 300px;
padding: 3rem 2rem 0rem 2rem;
}
.about-the-author-color-textbox-title {
text-align: center;
font-weight: bold;
font-size: 2.2rem;
padding: 8rem 3rem 0rem 3rem;
max-width: 100%;
min-width: 300px;
}
.about-the-author-subimage {
min-width: 300px;
margin: 0 auto;
}
.back-panel-contents {
width: 100%;
display: flex;
flex-wrap: wrap;
margin: 0 auto;
}
.lil-image-text {
font-size: 15px;
}
.big-image-text {
line-height: normal;
font-size: 31px;
}
.col-xs-12.col-sm-4.col-md-3 {
display: none !important;
}
}
/* New hero images */
.hero-container {
height: 550px;
background-color: #1b3a42;
margin-bottom: 2em;
}
.hero-image {
max-width: 1800px;
margin-top: -66px !important;
margin-left: auto;
margin-right: auto;
position: relative;
margin-bottom: 2em;
background-image: linear-gradient( rgba(0,0,0,.2), rgba(0,0,0,.2) ),
url(../images/big_image/Log.jpg); /* liner gradient tints the image darker for readability*/
height: 550px;
background-size: cover;
background-color: #1b3a42;
background-position: center bottom;
display: flex; /* Change to `display: none` for no hero image */
justify-content: center;
align-items: center;
}
.hero-image-text-little {
position: relative;
font-family: 'Lora', serif;
font-size: 24px;
font-weight: 500;
line-height: 1.5em;
letter-spacing: 1px;
font-style: italic;
font-weight: normal;
color: #fff;
text-align: center;
}
.hero-image-text-big {
position: relative;
letter-spacing: 0.065em;
line-height: 1em;
font-size: 68px;
text-transform: uppercase;
text-align: center;
display: block;
color: #fff;
margin-bottom: 2.5rem;
font-weight: bold;
}
@media only screen and (max-width: 770px) {
.hero-image-text-big{
font-size: 3rem;
padding: 2rem 0rem 0rem 0rem;
}
.hero-image-text-little{
font-size: 19px;
}
.hero-image {
max-width: 770px;
height: 300px;
}
.hero-container {
height: 300px;
background-color: #1b3a42;
margin-bottom: 1em;
}
.toc-content.col-xs-12.col-sm-8.col-md-9{
width: 100%;
padding-left: 1px;
padding-right: 1px;
}
}
/*------------iframe-----------------*/
iframe.interactive {
min-width: 100%;
margin: 0 auto;
}
/*----------- video for gif----------- */
video {
width: 100%;
}
.small_vid {
width:600px;
display: block;
margin: 0 auto;
}
/*----------DIV TIPS------------*/
div.note, div.tip, div.gotcha, div.design, div.hat, div.obj{
border: 4px #c5ede2;
border-style: solid;
padding: 1em;
margin: 1em 0;
/*uncomment for icon */
/* padding-left: 100px;
background-size: 70px;
background-repeat: no-repeat;
background-position: 15px center; */
min-height: 120px;
color: #638f9d;
background-color: #ffffff;
}
div.obj {
font-size: 16px;
line-height: 1.5em;
/*background-image: url("../images/arrow.png");*/
}
div.obj>p:first-child {
padding-top: 0;
margin-top: 0 !important;
font-family: "Source Sans Pro";
text-transform: uppercase;
letter-spacing: 0.1em;
}
================================================
FILE: docs/images/02_bellCurve/lib/crosstalk-1.0.0/css/crosstalk.css
================================================
/* 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;
}
@media only screen and (max-width:480px) {
.crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column {
display: block;
padding-right: inherit;
}
}
================================================
FILE: docs/images/02_bellCurve/lib/crosstalk-1.0.0/js/crosstalk.js
================================================
(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 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) defineProperties(Constructor, staticProps); return Constructor; }; }();
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; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
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) defineProperties(Constructor, staticProps); return Constructor; }; }();
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) defineProperties(Constructor, staticProps); return Constructor; }; }();
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) defineProperties(Constructor, staticProps); return Constructor; }; }();
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
================================================
FILE: docs/images/02_bellCurve/lib/crosstalk-1.2.0/js/crosstalk.js
================================================
(function(){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 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) defineProperties(Constructor, staticProps); return Constructor; }; }();
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");
var _input = 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,
bind: _input.bind
};
/**
* @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;
exports.bind = bind;
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) {
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 {
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 {
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; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
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 round = $el.data("round");
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));
};
} else if (dataType === "number") {
if (typeof round !== "undefined") opts.prettify = function (num) {
var factor = Math.pow(10, round);
return Math.round(num * factor) / factor;
};
}
$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) defineProperties(Constructor, staticProps); return Constructor; }; }();
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"); } }
/**
* 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.
*/
var SelectionHandle = exports.SelectionHandle = function () {
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);
}
}, {
key: "value",
get: function get() {
return this._var ? this._var.get() : null;
}
}]);
return SelectionHandle;
}();
/**
* @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.
*/
},{"./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) defineProperties(Constructor, staticProps); return Constructor; }; }();
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) defineProperties(Constructor, staticProps); return Constructor; }; }();
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
================================================
FILE: docs/images/02_bellCurve/lib/crosstalk-1.2.0/scss/crosstalk.scss
================================================
/* 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;
}
@media only screen and (max-width:480px) {
.crosstalk-input-checkboxgroup .crosstalk-options-group .crosstalk-options-column {
display: block;
padding-right: inherit;
}
}
/* Relevant BS3 styles to make filter_checkbox() look reasonable without Bootstrap */
.crosstalk-input {
margin-bottom: 15px; /* a la .form-group */
.control-label {
margin-bottom: 0;
vertical-align: middle;
}
input[type="checkbox"] {
margin: 4px 0 0;
margin-top: 1px;
line-height: normal;
}
.checkbox {
position: relative;
display: block;
margin-top: 10px;
margin-bottom: 10px;
}
.checkbox > label{
padding-left: 20px;
margin-bottom: 0;
font-weight: 400;
cursor: pointer;
}
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
position: absolute;
margin-top: 2px;
margin-left: -20px;
}
.checkbox + .checkbox {
margin-top: -5px;
}
.checkbox-inline {
position: relative;
display: inline-block;
padding-left: 20px;
margin-bottom: 0;
font-weight: 400;
vertical-align: middle;
cursor: pointer;
}
.checkbox-inline + .checkbox-inline {
margin-top: 0;
margin-left: 10px;
}
}
================================================
FILE: docs/images/02_bellCurve/lib/htmlwidgets-1.3/htmlwidgets.js
================================================
(function() {
// If window.HTMLWidgets is already defined, then use it; otherwise create a
// new object. This allows preceding code to set options that affect the
// initialization process (though none currently exist).
window.HTMLWidgets = window.HTMLWidgets || {};
// See if we're running in a viewer pane. If not, we're in a web browser.
var viewerMode = window.HTMLWidgets.viewerMode =
/\bviewer_pane=1\b/.test(window.location);
// See if we're running in Shiny mode. If not, it's a static document.
// Note that static widgets can appear in both Shiny and static modes, but
// obviously, Shiny widgets can only appear in Shiny apps/documents.
var shinyMode = window.HTMLWidgets.shinyMode =
typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings;
// We can't count on jQuery being available, so we implement our own
// version if necessary.
function querySelectorAll(scope, selector) {
if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) {
return scope.find(selector);
}
if (scope.querySelectorAll) {
return scope.querySelectorAll(selector);
}
}
function asArray(value) {
if (value === null)
return [];
if ($.isArray(value))
return value;
return [value];
}
// Implement jQuery's extend
function extend(target /*, ... */) {
if (arguments.length == 1) {
return target;
}
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
}
return target;
}
// IE8 doesn't support Array.forEach.
function forEach(values, callback, thisArg) {
if (values.forEach) {
values.forEach(callback, thisArg);
} else {
for (var i = 0; i < values.length; i++) {
callback.call(thisArg, values[i], i, values);
}
}
}
// Replaces the specified method with the return value of funcSource.
//
// Note that funcSource should not BE the new method, it should be a function
// that RETURNS the new method. funcSource receives a single argument that is
// the overridden method, it can be called from the new method. The overridden
// method can be called like a regular function, it has the target permanently
// bound to it so "this" will work correctly.
function overrideMethod(target, methodName, funcSource) {
var superFunc = target[methodName] || function() {};
var superFuncBound = function() {
return superFunc.apply(target, arguments);
};
target[methodName] = funcSource(superFuncBound);
}
// Add a method to delegator that, when invoked, calls
// delegatee.methodName. If there is no such method on
// the delegatee, but there was one on delegator before
// delegateMethod was called, then the original version
// is invoked instead.
// For example:
//
// var a = {
// method1: function() { console.log('a1'); }
// method2: function() { console.log('a2'); }
// };
// var b = {
// method1: function() { console.log('b1'); }
// };
// delegateMethod(a, b, "method1");
// delegateMethod(a, b, "method2");
// a.method1();
// a.method2();
//
// The output would be "b1", "a2".
function delegateMethod(delegator, delegatee, methodName) {
var inherited = delegator[methodName];
delegator[methodName] = function() {
var target = delegatee;
var method = delegatee[methodName];
// The method doesn't exist on the delegatee. Instead,
// call the method on the delegator, if it exists.
if (!method) {
target = delegator;
method = inherited;
}
if (method) {
return method.apply(target, arguments);
}
};
}
// Implement a vague facsimilie of jQuery's data method
function elementData(el, name, value) {
if (arguments.length == 2) {
return el["htmlwidget_data_" + name];
} else if (arguments.length == 3) {
el["htmlwidget_data_" + name] = value;
return el;
} else {
throw new Error("Wrong number of arguments for elementData: " +
arguments.length);
}
}
// http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function hasClass(el, className) {
var re = new RegExp("\\b" + escapeRegExp(className) + "\\b");
return re.test(el.className);
}
// elements - array (or array-like object) of HTML elements
// className - class name to test for
// include - if true, only return elements with given className;
// if false, only return elements *without* given className
function filterByClass(elements, className, include) {
var results = [];
for (var i = 0; i < elements.length; i++) {
if (hasClass(elements[i], className) == include)
results.push(elements[i]);
}
return results;
}
function on(obj, eventName, func) {
if (obj.addEventListener) {
obj.addEventListener(eventName, func, false);
} else if (obj.attachEvent) {
obj.attachEvent(eventName, func);
}
}
function off(obj, eventName, func) {
if (obj.removeEventListener)
obj.removeEventListener(eventName, func, false);
else if (obj.detachEvent) {
obj.detachEvent(eventName, func);
}
}
// Translate array of values to top/right/bottom/left, as usual with
// the "padding" CSS property
// https://developer.mozilla.org/en-US/docs/Web/CSS/padding
function unpackPadding(value) {
if (typeof(value) === "number")
value = [value];
if (value.length === 1) {
return {top: value[0], right: value[0], bottom: value[0], left: value[0]};
}
if (value.length === 2) {
return {top: value[0], right: value[1], bottom: value[0], left: value[1]};
}
if (value.length === 3) {
return {top: value[0], right: value[1], bottom: value[2], left: value[1]};
}
if (value.length === 4) {
return {top: value[0], right: value[1], bottom: value[2], left: value[3]};
}
}
// Convert an unpacked padding object to a CSS value
function paddingToCss(paddingObj) {
return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px";
}
// Makes a number suitable for CSS
function px(x) {
if (typeof(x) === "number")
return x + "px";
else
return x;
}
// Retrieves runtime widget sizing information for an element.
// The return value is either null, or an object with fill, padding,
// defaultWidth, defaultHeight fields.
function sizingPolicy(el) {
var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']");
if (!sizingEl)
return null;
var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}");
if (viewerMode) {
return sp.viewer;
} else {
return sp.browser;
}
}
// @param tasks Array of strings (or falsy value, in which case no-op).
// Each element must be a valid JavaScript expression that yields a
// function. Or, can be an array of objects with "code" and "data"
// properties; in this case, the "code" property should be a string
// of JS that's an expr that yields a function, and "data" should be
// an object that will be added as an additional argument when that
// function is called.
// @param target The object that will be "this" for each function
// execution.
// @param args Array of arguments to be passed to the functions. (The
// same arguments will be passed to all functions.)
function evalAndRun(tasks, target, args) {
if (tasks) {
forEach(tasks, function(task) {
var theseArgs = args;
if (typeof(task) === "object") {
theseArgs = theseArgs.concat([task.data]);
task = task.code;
}
var taskFunc = eval("(" + task + ")");
if (typeof(taskFunc) !== "function") {
throw new Error("Task must be a function! Source:\n" + task);
}
taskFunc.apply(target, theseArgs);
});
}
}
function initSizing(el) {
var sizing = sizingPolicy(el);
if (!sizing)
return;
var cel = document.getElementById("htmlwidget_container");
if (!cel)
return;
if (typeof(sizing.padding) !== "undefined") {
document.body.style.margin = "0";
document.body.style.padding = paddingToCss(unpackPadding(sizing.padding));
}
if (sizing.fill) {
document.body.style.overflow = "hidden";
document.body.style.width = "100%";
document.body.style.height = "100%";
document.documentElement.style.width = "100%";
document.documentElement.style.height = "100%";
if (cel) {
cel.style.position = "absolute";
var pad = unpackPadding(sizing.padding);
cel.style.top = pad.top + "px";
cel.style.right = pad.right + "px";
cel.style.bottom = pad.bottom + "px";
cel.style.left = pad.left + "px";
el.style.width = "100%";
el.style.height = "100%";
}
return {
getWidth: function() { return cel.offsetWidth; },
getHeight: function() { return cel.offsetHeight; }
};
} else {
el.style.width = px(sizing.width);
el.style.height = px(sizing.height);
return {
getWidth: function() { return el.offsetWidth; },
getHeight: function() { return el.offsetHeight; }
};
}
}
// Default implementations for methods
var defaults = {
find: function(scope) {
return querySelectorAll(scope, "." + this.name);
},
renderError: function(el, err) {
var $el = $(el);
this.clearError(el);
// Add all these error classes, as Shiny does
var errClass = "shiny-output-error";
if (err.type !== null) {
// use the classes of the error condition as CSS class names
errClass = errClass + " " + $.map(asArray(err.type), function(type) {
return errClass + "-" + type;
}).join(" ");
}
errClass = errClass + " htmlwidgets-error";
// Is el inline or block? If inline or inline-block, just display:none it
// and add an inline error.
var display = $el.css("display");
$el.data("restore-display-mode", display);
if (display === "inline" || display === "inline-block") {
$el.hide();
if (err.message !== "") {
var errorSpan = $("").addClass(errClass);
errorSpan.text(err.message);
$el.after(errorSpan);
}
} else if (display === "block") {
// If block, add an error just after the el, set visibility:none on the
// el, and position the error to be on top of the el.
// Mark it with a unique ID and CSS class so we can remove it later.
$el.css("visibility", "hidden");
if (err.message !== "") {
var errorDiv = $("
").addClass(errClass).css("position", "absolute")
.css("top", el.offsetTop)
.css("left", el.offsetLeft)
// setting width can push out the page size, forcing otherwise
// unnecessary scrollbars to appear and making it impossible for
// the element to shrink; so use max-width instead
.css("maxWidth", el.offsetWidth)
.css("height", el.offsetHeight);
errorDiv.text(err.message);
$el.after(errorDiv);
// Really dumb way to keep the size/position of the error in sync with
// the parent element as the window is resized or whatever.
var intId = setInterval(function() {
if (!errorDiv[0].parentElement) {
clearInterval(intId);
return;
}
errorDiv
.css("top", el.offsetTop)
.css("left", el.offsetLeft)
.css("maxWidth", el.offsetWidth)
.css("height", el.offsetHeight);
}, 500);
}
}
},
clearError: function(el) {
var $el = $(el);
var display = $el.data("restore-display-mode");
$el.data("restore-display-mode", null);
if (display === "inline" || display === "inline-block") {
if (display)
$el.css("display", display);
$(el.nextSibling).filter(".htmlwidgets-error").remove();
} else if (display === "block"){
$el.css("visibility", "inherit");
$(el.nextSibling).filter(".htmlwidgets-error").remove();
}
},
sizing: {}
};
// Called by widget bindings to register a new type of widget. The definition
// object can contain the following properties:
// - name (required) - A string indicating the binding name, which will be
// used by default as the CSS classname to look for.
// - initialize (optional) - A function(el) that will be called once per
// widget element; if a value is returned, it will be passed as the third
// value to renderValue.
// - renderValue (required) - A function(el, data, initValue) that will be
// called with data. Static contexts will cause this to be called once per
// element; Shiny apps will cause this to be called multiple times per
// element, as the data changes.
window.HTMLWidgets.widget = function(definition) {
if (!definition.name) {
throw new Error("Widget must have a name");
}
if (!definition.type) {
throw new Error("Widget must have a type");
}
// Currently we only support output widgets
if (definition.type !== "output") {
throw new Error("Unrecognized widget type '" + definition.type + "'");
}
// TODO: Verify that .name is a valid CSS classname
// Support new-style instance-bound definitions. Old-style class-bound
// definitions have one widget "object" per widget per type/class of
// widget; the renderValue and resize methods on such widget objects
// take el and instance arguments, because the widget object can't
// store them. New-style instance-bound definitions have one widget
// object per widget instance; the definition that's passed in doesn't
// provide renderValue or resize methods at all, just the single method
// factory(el, width, height)
// which returns an object that has renderValue(x) and resize(w, h).
// This enables a far more natural programming style for the widget
// author, who can store per-instance state using either OO-style
// instance fields or functional-style closure variables (I guess this
// is in contrast to what can only be called C-style pseudo-OO which is
// what we required before).
if (definition.factory) {
definition = createLegacyDefinitionAdapter(definition);
}
if (!definition.renderValue) {
throw new Error("Widget must have a renderValue function");
}
// For static rendering (non-Shiny), use a simple widget registration
// scheme. We also use this scheme for Shiny apps/documents that also
// contain static widgets.
window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || [];
// Merge defaults into the definition; don't mutate the original definition.
var staticBinding = extend({}, defaults, definition);
overrideMethod(staticBinding, "find", function(superfunc) {
return function(scope) {
var results = superfunc(scope);
// Filter out Shiny outputs, we only want the static kind
return filterByClass(results, "html-widget-output", false);
};
});
window.HTMLWidgets.widgets.push(staticBinding);
if (shinyMode) {
// Shiny is running. Register the definition with an output binding.
// The definition itself will not be the output binding, instead
// we will make an output binding object that delegates to the
// definition. This is because we foolishly used the same method
// name (renderValue) for htmlwidgets definition and Shiny bindings
// but they actually have quite different semantics (the Shiny
// bindings receive data that includes lots of metadata that it
// strips off before calling htmlwidgets renderValue). We can't
// just ignore the difference because in some widgets it's helpful
// to call this.renderValue() from inside of resize(), and if
// we're not delegating, then that call will go to the Shiny
// version instead of the htmlwidgets version.
// Merge defaults with definition, without mutating either.
var bindingDef = extend({}, defaults, definition);
// This object will be our actual Shiny binding.
var shinyBinding = new Shiny.OutputBinding();
// With a few exceptions, we'll want to simply use the bindingDef's
// version of methods if they are available, otherwise fall back to
// Shiny's defaults. NOTE: If Shiny's output bindings gain additional
// methods in the future, and we want them to be overrideable by
// HTMLWidget binding definitions, then we'll need to add them to this
// list.
delegateMethod(shinyBinding, bindingDef, "getId");
delegateMethod(shinyBinding, bindingDef, "onValueChange");
delegateMethod(shinyBinding, bindingDef, "onValueError");
delegateMethod(shinyBinding, bindingDef, "renderError");
delegateMethod(shinyBinding, bindingDef, "clearError");
delegateMethod(shinyBinding, bindingDef, "showProgress");
// The find, renderValue, and resize are handled differently, because we
// want to actually decorate the behavior of the bindingDef methods.
shinyBinding.find = function(scope) {
var results = bindingDef.find(scope);
// Only return elements that are Shiny outputs, not static ones
var dynamicResults = results.filter(".html-widget-output");
// It's possible that whatever caused Shiny to think there might be
// new dynamic outputs, also caused there to be new static outputs.
// Since there might be lots of different htmlwidgets bindings, we
// schedule execution for later--no need to staticRender multiple
// times.
if (results.length !== dynamicResults.length)
scheduleStaticRender();
return dynamicResults;
};
// Wrap renderValue to handle initialization, which unfortunately isn't
// supported natively by Shiny at the time of this writing.
shinyBinding.renderValue = function(el, data) {
Shiny.renderDependencies(data.deps);
// Resolve strings marked as javascript literals to objects
if (!(data.evals instanceof Array)) data.evals = [data.evals];
for (var i = 0; data.evals && i < data.evals.length; i++) {
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]);
}
if (!bindingDef.renderOnNullValue) {
if (data.x === null) {
el.style.visibility = "hidden";
return;
} else {
el.style.visibility = "inherit";
}
}
if (!elementData(el, "initialized")) {
initSizing(el);
elementData(el, "initialized", true);
if (bindingDef.initialize) {
var result = bindingDef.initialize(el, el.offsetWidth,
el.offsetHeight);
elementData(el, "init_result", result);
}
}
bindingDef.renderValue(el, data.x, elementData(el, "init_result"));
evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]);
};
// Only override resize if bindingDef implements it
if (bindingDef.resize) {
shinyBinding.resize = function(el, width, height) {
// Shiny can call resize before initialize/renderValue have been
// called, which doesn't make sense for widgets.
if (elementData(el, "initialized")) {
bindingDef.resize(el, width, height, elementData(el, "init_result"));
}
};
}
Shiny.outputBindings.register(shinyBinding, bindingDef.name);
}
};
var scheduleStaticRenderTimerId = null;
function scheduleStaticRender() {
if (!scheduleStaticRenderTimerId) {
scheduleStaticRenderTimerId = setTimeout(function() {
scheduleStaticRenderTimerId = null;
window.HTMLWidgets.staticRender();
}, 1);
}
}
// Render static widgets after the document finishes loading
// Statically render all elements that are of this widget's class
window.HTMLWidgets.staticRender = function() {
var bindings = window.HTMLWidgets.widgets || [];
forEach(bindings, function(binding) {
var matches = binding.find(document.documentElement);
forEach(matches, function(el) {
var sizeObj = initSizing(el, binding);
if (hasClass(el, "html-widget-static-bound"))
return;
el.className = el.className + " html-widget-static-bound";
var initResult;
if (binding.initialize) {
initResult = binding.initialize(el,
sizeObj ? sizeObj.getWidth() : el.offsetWidth,
sizeObj ? sizeObj.getHeight() : el.offsetHeight
);
elementData(el, "init_result", initResult);
}
if (binding.resize) {
var lastSize = {
w: sizeObj ? sizeObj.getWidth() : el.offsetWidth,
h: sizeObj ? sizeObj.getHeight() : el.offsetHeight
};
var resizeHandler = function(e) {
var size = {
w: sizeObj ? sizeObj.getWidth() : el.offsetWidth,
h: sizeObj ? sizeObj.getHeight() : el.offsetHeight
};
if (size.w === 0 && size.h === 0)
return;
if (size.w === lastSize.w && size.h === lastSize.h)
return;
lastSize = size;
binding.resize(el, size.w, size.h, initResult);
};
on(window, "resize", resizeHandler);
// This is needed for cases where we're running in a Shiny
// app, but the widget itself is not a Shiny output, but
// rather a simple static widget. One example of this is
// an rmarkdown document that has runtime:shiny and widget
// that isn't in a render function. Shiny only knows to
// call resize handlers for Shiny outputs, not for static
// widgets, so we do it ourselves.
if (window.jQuery) {
window.jQuery(document).on(
"shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets",
resizeHandler
);
window.jQuery(document).on(
"hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets",
resizeHandler
);
}
// This is needed for the specific case of ioslides, which
// flips slides between display:none and display:block.
// Ideally we would not have to have ioslide-specific code
// here, but rather have ioslides raise a generic event,
// but the rmarkdown package just went to CRAN so the
// window to getting that fixed may be long.
if (window.addEventListener) {
// It's OK to limit this to window.addEventListener
// browsers because ioslides itself only supports
// such browsers.
on(document, "slideenter", resizeHandler);
on(document, "slideleave", resizeHandler);
}
}
var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");
if (scriptData) {
var data = JSON.parse(scriptData.textContent || scriptData.text);
// Resolve strings marked as javascript literals to objects
if (!(data.evals instanceof Array)) data.evals = [data.evals];
for (var k = 0; data.evals && k < data.evals.length; k++) {
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]);
}
binding.renderValue(el, data.x, initResult);
evalAndRun(data.jsHooks.render, initResult, [el, data.x]);
}
});
});
invokePostRenderHandlers();
}
// Wait until after the document has loaded to render the widgets.
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", function() {
document.removeEventListener("DOMContentLoaded", arguments.callee, false);
window.HTMLWidgets.staticRender();
}, false);
} else if (document.attachEvent) {
document.attachEvent("onreadystatechange", function() {
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", arguments.callee);
window.HTMLWidgets.staticRender();
}
});
}
window.HTMLWidgets.getAttachmentUrl = function(depname, key) {
// If no key, default to the first item
if (typeof(key) === "undefined")
key = 1;
var link = document.getElementById(depname + "-" + key + "-attachment");
if (!link) {
throw new Error("Attachment " + depname + "/" + key + " not found in document");
}
return link.getAttribute("href");
};
window.HTMLWidgets.dataframeToD3 = function(df) {
var names = [];
var length;
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;
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;
};
window.HTMLWidgets.transposeArray2D = function(array) {
if (array.length === 0) return array;
var newArray = array[0].map(function(col, i) {
return array.map(function(row) {
return row[i]
})
});
return newArray;
};
// Split value at splitChar, but allow splitChar to be escaped
// using escapeChar. Any other characters escaped by escapeChar
// will be included as usual (including escapeChar itself).
function splitWithEscape(value, splitChar, escapeChar) {
var results = [];
var escapeMode = false;
var currentResult = "";
for (var pos = 0; pos < value.length; pos++) {
if (!escapeMode) {
if (value[pos] === splitChar) {
results.push(currentResult);
currentResult = "";
} else if (value[pos] === escapeChar) {
escapeMode = true;
} else {
currentResult += value[pos];
}
} else {
currentResult += value[pos];
escapeMode = false;
}
}
if (currentResult !== "") {
results.push(currentResult);
}
return results;
}
// Function authored by Yihui/JJ Allaire
window.HTMLWidgets.evaluateStringMember = function(o, member) {
var parts = splitWithEscape(member, '.', '\\');
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
// part may be a character or 'numeric' member name
if (o !== null && typeof o === "object" && part in o) {
if (i == (l - 1)) { // if we are at the end of the line then evalulate
if (typeof o[part] === "string")
o[part] = eval("(" + o[part] + ")");
} else { // otherwise continue to next embedded object
o = o[part];
}
}
}
};
// Retrieve the HTMLWidget instance (i.e. the return value of an
// HTMLWidget binding's initialize() or factory() function)
// associated with an element, or null if none.
window.HTMLWidgets.getInstance = function(el) {
return elementData(el, "init_result");
};
// Finds the first element in the scope that matches the selector,
// and returns the HTMLWidget instance (i.e. the return value of
// an HTMLWidget binding's initialize() or factory() function)
// associated with that element, if any. If no element matches the
// selector, or the first matching element has no HTMLWidget
// instance associated with it, then null is returned.
//
// The scope argument is optional, and defaults to window.document.
window.HTMLWidgets.find = function(scope, selector) {
if (arguments.length == 1) {
selector = scope;
scope = document;
}
var el = scope.querySelector(selector);
if (el === null) {
return null;
} else {
return window.HTMLWidgets.getInstance(el);
}
};
// Finds all elements in the scope that match the selector, and
// returns the HTMLWidget instances (i.e. the return values of
// an HTMLWidget binding's initialize() or factory() function)
// associated with the elements, in an array. If elements that
// match the selector don't have an associated HTMLWidget
// instance, the returned array will contain nulls.
//
// The scope argument is optional, and defaults to window.document.
window.HTMLWidgets.findAll = function(scope, selector) {
if (arguments.length == 1) {
selector = scope;
scope = document;
}
var nodes = scope.querySelectorAll(selector);
var results = [];
for (var i = 0; i < nodes.length; i++) {
results.push(window.HTMLWidgets.getInstance(nodes[i]));
}
return results;
};
var postRenderHandlers = [];
function invokePostRenderHandlers() {
while (postRenderHandlers.length) {
var handler = postRenderHandlers.shift();
if (handler) {
handler();
}
}
}
// Register the given callback function to be invoked after the
// next time static widgets are rendered.
window.HTMLWidgets.addPostRenderHandler = function(callback) {
postRenderHandlers.push(callback);
};
// Takes a new-style instance-bound definition, and returns an
// old-style class-bound definition. This saves us from having
// to rewrite all the logic in this file to accomodate both
// types of definitions.
function createLegacyDefinitionAdapter(defn) {
var result = {
name: defn.name,
type: defn.type,
initialize: function(el, width, height) {
return defn.factory(el, width, height);
},
renderValue: function(el, x, instance) {
return instance.renderValue(x);
},
resize: function(el, width, height, instance) {
return instance.resize(width, height);
}
};
if (defn.find)
result.find = defn.find;
if (defn.renderError)
result.renderError = defn.renderError;
if (defn.clearError)
result.clearError = defn.clearError;
return result;
}
})();
================================================
FILE: docs/images/02_bellCurve/lib/htmlwidgets-1.5.1/htmlwidgets.js
================================================
(function() {
// If window.HTMLWidgets is already defined, then use it; otherwise create a
// new object. This allows preceding code to set options that affect the
// initialization process (though none currently exist).
window.HTMLWidgets = window.HTMLWidgets || {};
// See if we're running in a viewer pane. If not, we're in a web browser.
var viewerMode = window.HTMLWidgets.viewerMode =
/\bviewer_pane=1\b/.test(window.location);
// See if we're running in Shiny mode. If not, it's a static document.
// Note that static widgets can appear in both Shiny and static modes, but
// obviously, Shiny widgets can only appear in Shiny apps/documents.
var shinyMode = window.HTMLWidgets.shinyMode =
typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings;
// We can't count on jQuery being available, so we implement our own
// version if necessary.
function querySelectorAll(scope, selector) {
if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) {
return scope.find(selector);
}
if (scope.querySelectorAll) {
return scope.querySelectorAll(selector);
}
}
function asArray(value) {
if (value === null)
return [];
if ($.isArray(value))
return value;
return [value];
}
// Implement jQuery's extend
function extend(target /*, ... */) {
if (arguments.length == 1) {
return target;
}
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
}
return target;
}
// IE8 doesn't support Array.forEach.
function forEach(values, callback, thisArg) {
if (values.forEach) {
values.forEach(callback, thisArg);
} else {
for (var i = 0; i < values.length; i++) {
callback.call(thisArg, values[i], i, values);
}
}
}
// Replaces the specified method with the return value of funcSource.
//
// Note that funcSource should not BE the new method, it should be a function
// that RETURNS the new method. funcSource receives a single argument that is
// the overridden method, it can be called from the new method. The overridden
// method can be called like a regular function, it has the target permanently
// bound to it so "this" will work correctly.
function overrideMethod(target, methodName, funcSource) {
var superFunc = target[methodName] || function() {};
var superFuncBound = function() {
return superFunc.apply(target, arguments);
};
target[methodName] = funcSource(superFuncBound);
}
// Add a method to delegator that, when invoked, calls
// delegatee.methodName. If there is no such method on
// the delegatee, but there was one on delegator before
// delegateMethod was called, then the original version
// is invoked instead.
// For example:
//
// var a = {
// method1: function() { console.log('a1'); }
// method2: function() { console.log('a2'); }
// };
// var b = {
// method1: function() { console.log('b1'); }
// };
// delegateMethod(a, b, "method1");
// delegateMethod(a, b, "method2");
// a.method1();
// a.method2();
//
// The output would be "b1", "a2".
function delegateMethod(delegator, delegatee, methodName) {
var inherited = delegator[methodName];
delegator[methodName] = function() {
var target = delegatee;
var method = delegatee[methodName];
// The method doesn't exist on the delegatee. Instead,
// call the method on the delegator, if it exists.
if (!method) {
target = delegator;
method = inherited;
}
if (method) {
return method.apply(target, arguments);
}
};
}
// Implement a vague facsimilie of jQuery's data method
function elementData(el, name, value) {
if (arguments.length == 2) {
return el["htmlwidget_data_" + name];
} else if (arguments.length == 3) {
el["htmlwidget_data_" + name] = value;
return el;
} else {
throw new Error("Wrong number of arguments for elementData: " +
arguments.length);
}
}
// http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function hasClass(el, className) {
var re = new RegExp("\\b" + escapeRegExp(className) + "\\b");
return re.test(el.className);
}
// elements - array (or array-like object) of HTML elements
// className - class name to test for
// include - if true, only return elements with given className;
// if false, only return elements *without* given className
function filterByClass(elements, className, include) {
var results = [];
for (var i = 0; i < elements.length; i++) {
if (hasClass(elements[i], className) == include)
results.push(elements[i]);
}
return results;
}
function on(obj, eventName, func) {
if (obj.addEventListener) {
obj.addEventListener(eventName, func, false);
} else if (obj.attachEvent) {
obj.attachEvent(eventName, func);
}
}
function off(obj, eventName, func) {
if (obj.removeEventListener)
obj.removeEventListener(eventName, func, false);
else if (obj.detachEvent) {
obj.detachEvent(eventName, func);
}
}
// Translate array of values to top/right/bottom/left, as usual with
// the "padding" CSS property
// https://developer.mozilla.org/en-US/docs/Web/CSS/padding
function unpackPadding(value) {
if (typeof(value) === "number")
value = [value];
if (value.length === 1) {
return {top: value[0], right: value[0], bottom: value[0], left: value[0]};
}
if (value.length === 2) {
return {top: value[0], right: value[1], bottom: value[0], left: value[1]};
}
if (value.length === 3) {
return {top: value[0], right: value[1], bottom: value[2], left: value[1]};
}
if (value.length === 4) {
return {top: value[0], right: value[1], bottom: value[2], left: value[3]};
}
}
// Convert an unpacked padding object to a CSS value
function paddingToCss(paddingObj) {
return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px";
}
// Makes a number suitable for CSS
function px(x) {
if (typeof(x) === "number")
return x + "px";
else
return x;
}
// Retrieves runtime widget sizing information for an element.
// The return value is either null, or an object with fill, padding,
// defaultWidth, defaultHeight fields.
function sizingPolicy(el) {
var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']");
if (!sizingEl)
return null;
var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}");
if (viewerMode) {
return sp.viewer;
} else {
return sp.browser;
}
}
// @param tasks Array of strings (or falsy value, in which case no-op).
// Each element must be a valid JavaScript expression that yields a
// function. Or, can be an array of objects with "code" and "data"
// properties; in this case, the "code" property should be a string
// of JS that's an expr that yields a function, and "data" should be
// an object that will be added as an additional argument when that
// function is called.
// @param target The object that will be "this" for each function
// execution.
// @param args Array of arguments to be passed to the functions. (The
// same arguments will be passed to all functions.)
function evalAndRun(tasks, target, args) {
if (tasks) {
forEach(tasks, function(task) {
var theseArgs = args;
if (typeof(task) === "object") {
theseArgs = theseArgs.concat([task.data]);
task = task.code;
}
var taskFunc = tryEval(task);
if (typeof(taskFunc) !== "function") {
throw new Error("Task must be a function! Source:\n" + task);
}
taskFunc.apply(target, theseArgs);
});
}
}
// Attempt eval() both with and without enclosing in parentheses.
// Note that enclosing coerces a function declaration into
// an expression that eval() can parse
// (otherwise, a SyntaxError is thrown)
function tryEval(code) {
var result = null;
try {
result = eval(code);
} catch(error) {
if (!error instanceof SyntaxError) {
throw error;
}
try {
result = eval("(" + code + ")");
} catch(e) {
if (e instanceof SyntaxError) {
throw error;
} else {
throw e;
}
}
}
return result;
}
function initSizing(el) {
var sizing = sizingPolicy(el);
if (!sizing)
return;
var cel = document.getElementById("htmlwidget_container");
if (!cel)
return;
if (typeof(sizing.padding) !== "undefined") {
document.body.style.margin = "0";
document.body.style.padding = paddingToCss(unpackPadding(sizing.padding));
}
if (sizing.fill) {
document.body.style.overflow = "hidden";
document.body.style.width = "100%";
document.body.style.height = "100%";
document.documentElement.style.width = "100%";
document.documentElement.style.height = "100%";
if (cel) {
cel.style.position = "absolute";
var pad = unpackPadding(sizing.padding);
cel.style.top = pad.top + "px";
cel.style.right = pad.right + "px";
cel.style.bottom = pad.bottom + "px";
cel.style.left = pad.left + "px";
el.style.width = "100%";
el.style.height = "100%";
}
return {
getWidth: function() { return cel.offsetWidth; },
getHeight: function() { return cel.offsetHeight; }
};
} else {
el.style.width = px(sizing.width);
el.style.height = px(sizing.height);
return {
getWidth: function() { return el.offsetWidth; },
getHeight: function() { return el.offsetHeight; }
};
}
}
// Default implementations for methods
var defaults = {
find: function(scope) {
return querySelectorAll(scope, "." + this.name);
},
renderError: function(el, err) {
var $el = $(el);
this.clearError(el);
// Add all these error classes, as Shiny does
var errClass = "shiny-output-error";
if (err.type !== null) {
// use the classes of the error condition as CSS class names
errClass = errClass + " " + $.map(asArray(err.type), function(type) {
return errClass + "-" + type;
}).join(" ");
}
errClass = errClass + " htmlwidgets-error";
// Is el inline or block? If inline or inline-block, just display:none it
// and add an inline error.
var display = $el.css("display");
$el.data("restore-display-mode", display);
if (display === "inline" || display === "inline-block") {
$el.hide();
if (err.message !== "") {
var errorSpan = $("").addClass(errClass);
errorSpan.text(err.message);
$el.after(errorSpan);
}
} else if (display === "block") {
// If block, add an error just after the el, set visibility:none on the
// el, and position the error to be on top of the el.
// Mark it with a unique ID and CSS class so we can remove it later.
$el.css("visibility", "hidden");
if (err.message !== "") {
var errorDiv = $("
").addClass(errClass).css("position", "absolute")
.css("top", el.offsetTop)
.css("left", el.offsetLeft)
// setting width can push out the page size, forcing otherwise
// unnecessary scrollbars to appear and making it impossible for
// the element to shrink; so use max-width instead
.css("maxWidth", el.offsetWidth)
.css("height", el.offsetHeight);
errorDiv.text(err.message);
$el.after(errorDiv);
// Really dumb way to keep the size/position of the error in sync with
// the parent element as the window is resized or whatever.
var intId = setInterval(function() {
if (!errorDiv[0].parentElement) {
clearInterval(intId);
return;
}
errorDiv
.css("top", el.offsetTop)
.css("left", el.offsetLeft)
.css("maxWidth", el.offsetWidth)
.css("height", el.offsetHeight);
}, 500);
}
}
},
clearError: function(el) {
var $el = $(el);
var display = $el.data("restore-display-mode");
$el.data("restore-display-mode", null);
if (display === "inline" || display === "inline-block") {
if (display)
$el.css("display", display);
$(el.nextSibling).filter(".htmlwidgets-error").remove();
} else if (display === "block"){
$el.css("visibility", "inherit");
$(el.nextSibling).filter(".htmlwidgets-error").remove();
}
},
sizing: {}
};
// Called by widget bindings to register a new type of widget. The definition
// object can contain the following properties:
// - name (required) - A string indicating the binding name, which will be
// used by default as the CSS classname to look for.
// - initialize (optional) - A function(el) that will be called once per
// widget element; if a value is returned, it will be passed as the third
// value to renderValue.
// - renderValue (required) - A function(el, data, initValue) that will be
// called with data. Static contexts will cause this to be called once per
// element; Shiny apps will cause this to be called multiple times per
// element, as the data changes.
window.HTMLWidgets.widget = function(definition) {
if (!definition.name) {
throw new Error("Widget must have a name");
}
if (!definition.type) {
throw new Error("Widget must have a type");
}
// Currently we only support output widgets
if (definition.type !== "output") {
throw new Error("Unrecognized widget type '" + definition.type + "'");
}
// TODO: Verify that .name is a valid CSS classname
// Support new-style instance-bound definitions. Old-style class-bound
// definitions have one widget "object" per widget per type/class of
// widget; the renderValue and resize methods on such widget objects
// take el and instance arguments, because the widget object can't
// store them. New-style instance-bound definitions have one widget
// object per widget instance; the definition that's passed in doesn't
// provide renderValue or resize methods at all, just the single method
// factory(el, width, height)
// which returns an object that has renderValue(x) and resize(w, h).
// This enables a far more natural programming style for the widget
// author, who can store per-instance state using either OO-style
// instance fields or functional-style closure variables (I guess this
// is in contrast to what can only be called C-style pseudo-OO which is
// what we required before).
if (definition.factory) {
definition = createLegacyDefinitionAdapter(definition);
}
if (!definition.renderValue) {
throw new Error("Widget must have a renderValue function");
}
// For static rendering (non-Shiny), use a simple widget registration
// scheme. We also use this scheme for Shiny apps/documents that also
// contain static widgets.
window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || [];
// Merge defaults into the definition; don't mutate the original definition.
var staticBinding = extend({}, defaults, definition);
overrideMethod(staticBinding, "find", function(superfunc) {
return function(scope) {
var results = superfunc(scope);
// Filter out Shiny outputs, we only want the static kind
return filterByClass(results, "html-widget-output", false);
};
});
window.HTMLWidgets.widgets.push(staticBinding);
if (shinyMode) {
// Shiny is running. Register the definition with an output binding.
// The definition itself will not be the output binding, instead
// we will make an output binding object that delegates to the
// definition. This is because we foolishly used the same method
// name (renderValue) for htmlwidgets definition and Shiny bindings
// but they actually have quite different semantics (the Shiny
// bindings receive data that includes lots of metadata that it
// strips off before calling htmlwidgets renderValue). We can't
// just ignore the difference because in some widgets it's helpful
// to call this.renderValue() from inside of resize(), and if
// we're not delegating, then that call will go to the Shiny
// version instead of the htmlwidgets version.
// Merge defaults with definition, without mutating either.
var bindingDef = extend({}, defaults, definition);
// This object will be our actual Shiny binding.
var shinyBinding = new Shiny.OutputBinding();
// With a few exceptions, we'll want to simply use the bindingDef's
// version of methods if they are available, otherwise fall back to
// Shiny's defaults. NOTE: If Shiny's output bindings gain additional
// methods in the future, and we want them to be overrideable by
// HTMLWidget binding definitions, then we'll need to add them to this
// list.
delegateMethod(shinyBinding, bindingDef, "getId");
delegateMethod(shinyBinding, bindingDef, "onValueChange");
delegateMethod(shinyBinding, bindingDef, "onValueError");
delegateMethod(shinyBinding, bindingDef, "renderError");
delegateMethod(shinyBinding, bindingDef, "clearError");
delegateMethod(shinyBinding, bindingDef, "showProgress");
// The find, renderValue, and resize are handled differently, because we
// want to actually decorate the behavior of the bindingDef methods.
shinyBinding.find = function(scope) {
var results = bindingDef.find(scope);
// Only return elements that are Shiny outputs, not static ones
var dynamicResults = results.filter(".html-widget-output");
// It's possible that whatever caused Shiny to think there might be
// new dynamic outputs, also caused there to be new static outputs.
// Since there might be lots of different htmlwidgets bindings, we
// schedule execution for later--no need to staticRender multiple
// times.
if (results.length !== dynamicResults.length)
scheduleStaticRender();
return dynamicResults;
};
// Wrap renderValue to handle initialization, which unfortunately isn't
// supported natively by Shiny at the time of this writing.
shinyBinding.renderValue = function(el, data) {
Shiny.renderDependencies(data.deps);
// Resolve strings marked as javascript literals to objects
if (!(data.evals instanceof Array)) data.evals = [data.evals];
for (var i = 0; data.evals && i < data.evals.length; i++) {
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]);
}
if (!bindingDef.renderOnNullValue) {
if (data.x === null) {
el.style.visibility = "hidden";
return;
} else {
el.style.visibility = "inherit";
}
}
if (!elementData(el, "initialized")) {
initSizing(el);
elementData(el, "initialized", true);
if (bindingDef.initialize) {
var result = bindingDef.initialize(el, el.offsetWidth,
el.offsetHeight);
elementData(el, "init_result", result);
}
}
bindingDef.renderValue(el, data.x, elementData(el, "init_result"));
evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]);
};
// Only override resize if bindingDef implements it
if (bindingDef.resize) {
shinyBinding.resize = function(el, width, height) {
// Shiny can call resize before initialize/renderValue have been
// called, which doesn't make sense for widgets.
if (elementData(el, "initialized")) {
bindingDef.resize(el, width, height, elementData(el, "init_result"));
}
};
}
Shiny.outputBindings.register(shinyBinding, bindingDef.name);
}
};
var scheduleStaticRenderTimerId = null;
function scheduleStaticRender() {
if (!scheduleStaticRenderTimerId) {
scheduleStaticRenderTimerId = setTimeout(function() {
scheduleStaticRenderTimerId = null;
window.HTMLWidgets.staticRender();
}, 1);
}
}
// Render static widgets after the document finishes loading
// Statically render all elements that are of this widget's class
window.HTMLWidgets.staticRender = function() {
var bindings = window.HTMLWidgets.widgets || [];
forEach(bindings, function(binding) {
var matches = binding.find(document.documentElement);
forEach(matches, function(el) {
var sizeObj = initSizing(el, binding);
if (hasClass(el, "html-widget-static-bound"))
return;
el.className = el.className + " html-widget-static-bound";
var initResult;
if (binding.initialize) {
initResult = binding.initialize(el,
sizeObj ? sizeObj.getWidth() : el.offsetWidth,
sizeObj ? sizeObj.getHeight() : el.offsetHeight
);
elementData(el, "init_result", initResult);
}
if (binding.resize) {
var lastSize = {
w: sizeObj ? sizeObj.getWidth() : el.offsetWidth,
h: sizeObj ? sizeObj.getHeight() : el.offsetHeight
};
var resizeHandler = function(e) {
var size = {
w: sizeObj ? sizeObj.getWidth() : el.offsetWidth,
h: sizeObj ? sizeObj.getHeight() : el.offsetHeight
};
if (size.w === 0 && size.h === 0)
return;
if (size.w === lastSize.w && size.h === lastSize.h)
return;
lastSize = size;
binding.resize(el, size.w, size.h, initResult);
};
on(window, "resize", resizeHandler);
// This is needed for cases where we're running in a Shiny
// app, but the widget itself is not a Shiny output, but
// rather a simple static widget. One example of this is
// an rmarkdown document that has runtime:shiny and widget
// that isn't in a render function. Shiny only knows to
// call resize handlers for Shiny outputs, not for static
// widgets, so we do it ourselves.
if (window.jQuery) {
window.jQuery(document).on(
"shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets",
resizeHandler
);
window.jQuery(document).on(
"hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets",
resizeHandler
);
}
// This is needed for the specific case of ioslides, which
// flips slides between display:none and display:block.
// Ideally we would not have to have ioslide-specific code
// here, but rather have ioslides raise a generic event,
// but the rmarkdown package just went to CRAN so the
// window to getting that fixed may be long.
if (window.addEventListener) {
// It's OK to limit this to window.addEventListener
// browsers because ioslides itself only supports
// such browsers.
on(document, "slideenter", resizeHandler);
on(document, "slideleave", resizeHandler);
}
}
var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");
if (scriptData) {
var data = JSON.parse(scriptData.textContent || scriptData.text);
// Resolve strings marked as javascript literals to objects
if (!(data.evals instanceof Array)) data.evals = [data.evals];
for (var k = 0; data.evals && k < data.evals.length; k++) {
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]);
}
binding.renderValue(el, data.x, initResult);
evalAndRun(data.jsHooks.render, initResult, [el, data.x]);
}
});
});
invokePostRenderHandlers();
}
function has_jQuery3() {
if (!window.jQuery) {
return false;
}
var $version = window.jQuery.fn.jquery;
var $major_version = parseInt($version.split(".")[0]);
return $major_version >= 3;
}
/*
/ Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's
/ on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now
/ really means $(setTimeout(fn)).
/ https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous
/
/ Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny
/ one tick later than it did before, which means staticRender() is
/ called renderValue() earlier than (advanced) widget authors might be expecting.
/ https://github.com/rstudio/shiny/issues/2630
/
/ For a concrete example, leaflet has some methods (e.g., updateBounds)
/ which reference Shiny methods registered in initShiny (e.g., setInputValue).
/ Since leaflet is privy to this life-cycle, it knows to use setTimeout() to
/ delay execution of those methods (until Shiny methods are ready)
/ https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268
/
/ Ideally widget authors wouldn't need to use this setTimeout() hack that
/ leaflet uses to call Shiny methods on a staticRender(). In the long run,
/ the logic initShiny should be broken up so that method registration happens
/ right away, but binding happens later.
*/
function maybeStaticRenderLater() {
if (shinyMode && has_jQuery3()) {
window.jQuery(window.HTMLWidgets.staticRender);
} else {
window.HTMLWidgets.staticRender();
}
}
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", function() {
document.removeEventListener("DOMContentLoaded", arguments.callee, false);
maybeStaticRenderLater();
}, false);
} else if (document.attachEvent) {
document.attachEvent("onreadystatechange", function() {
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", arguments.callee);
maybeStaticRenderLater();
}
});
}
window.HTMLWidgets.getAttachmentUrl = function(depname, key) {
// If no key, default to the first item
if (typeof(key) === "undefined")
key = 1;
var link = document.getElementById(depname + "-" + key + "-attachment");
if (!link) {
throw new Error("Attachment " + depname + "/" + key + " not found in document");
}
return link.getAttribute("href");
};
window.HTMLWidgets.dataframeToD3 = function(df) {
var names = [];
var length;
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;
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;
};
window.HTMLWidgets.transposeArray2D = function(array) {
if (array.length === 0) return array;
var newArray = array[0].map(function(col, i) {
return array.map(function(row) {
return row[i]
})
});
return newArray;
};
// Split value at splitChar, but allow splitChar to be escaped
// using escapeChar. Any other characters escaped by escapeChar
// will be included as usual (including escapeChar itself).
function splitWithEscape(value, splitChar, escapeChar) {
var results = [];
var escapeMode = false;
var currentResult = "";
for (var pos = 0; pos < value.length; pos++) {
if (!escapeMode) {
if (value[pos] === splitChar) {
results.push(currentResult);
currentResult = "";
} else if (value[pos] === escapeChar) {
escapeMode = true;
} else {
currentResult += value[pos];
}
} else {
currentResult += value[pos];
escapeMode = false;
}
}
if (currentResult !== "") {
results.push(currentResult);
}
return results;
}
// Function authored by Yihui/JJ Allaire
window.HTMLWidgets.evaluateStringMember = function(o, member) {
var parts = splitWithEscape(member, '.', '\\');
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
// part may be a character or 'numeric' member name
if (o !== null && typeof o === "object" && part in o) {
if (i == (l - 1)) { // if we are at the end of the line then evalulate
if (typeof o[part] === "string")
o[part] = tryEval(o[part]);
} else { // otherwise continue to next embedded object
o = o[part];
}
}
}
};
// Retrieve the HTMLWidget instance (i.e. the return value of an
// HTMLWidget binding's initialize() or factory() function)
// associated with an element, or null if none.
window.HTMLWidgets.getInstance = function(el) {
return elementData(el, "init_result");
};
// Finds the first element in the scope that matches the selector,
// and returns the HTMLWidget instance (i.e. the return value of
// an HTMLWidget binding's initialize() or factory() function)
// associated with that element, if any. If no element matches the
// selector, or the first matching element has no HTMLWidget
// instance associated with it, then null is returned.
//
// The scope argument is optional, and defaults to window.document.
window.HTMLWidgets.find = function(scope, selector) {
if (arguments.length == 1) {
selector = scope;
scope = document;
}
var el = scope.querySelector(selector);
if (el === null) {
return null;
} else {
return window.HTMLWidgets.getInstance(el);
}
};
// Finds all elements in the scope that match the selector, and
// returns the HTMLWidget instances (i.e. the return values of
// an HTMLWidget binding's initialize() or factory() function)
// associated with the elements, in an array. If elements that
// match the selector don't have an associated HTMLWidget
// instance, the returned array will contain nulls.
//
// The scope argument is optional, and defaults to window.document.
window.HTMLWidgets.findAll = function(scope, selector) {
if (arguments.length == 1) {
selector = scope;
scope = document;
}
var nodes = scope.querySelectorAll(selector);
var results = [];
for (var i = 0; i < nodes.length; i++) {
results.push(window.HTMLWidgets.getInstance(nodes[i]));
}
return results;
};
var postRenderHandlers = [];
function invokePostRenderHandlers() {
while (postRenderHandlers.length) {
var handler = postRenderHandlers.shift();
if (handler) {
handler();
}
}
}
// Register the given callback function to be invoked after the
// next time static widgets are rendered.
window.HTMLWidgets.addPostRenderHandler = function(callback) {
postRenderHandlers.push(callback);
};
// Takes a new-style instance-bound definition, and returns an
// old-style class-bound definition. This saves us from having
// to rewrite all the logic in this file to accomodate both
// types of definitions.
function createLegacyDefinitionAdapter(defn) {
var result = {
name: defn.name,
type: defn.type,
initialize: function(el, width, height) {
return defn.factory(el, width, height);
},
renderValue: function(el, x, instance) {
return instance.renderValue(x);
},
resize: function(el, width, height, instance) {
return instance.resize(width, height);
}
};
if (defn.find)
result.find = defn.find;
if (defn.renderError)
result.renderError = defn.renderError;
if (defn.clearError)
result.clearError = defn.clearError;
return result;
}
})();
================================================
FILE: docs/images/02_bellCurve/lib/htmlwidgets-1.5.4/htmlwidgets.js
================================================
(function() {
// If window.HTMLWidgets is already defined, then use it; otherwise create a
// new object. This allows preceding code to set options that affect the
// initialization process (though none currently exist).
window.HTMLWidgets = window.HTMLWidgets || {};
// See if we're running in a viewer pane. If not, we're in a web browser.
var viewerMode = window.HTMLWidgets.viewerMode =
/\bviewer_pane=1\b/.test(window.location);
// See if we're running in Shiny mode. If not, it's a static document.
// Note that static widgets can appear in both Shiny and static modes, but
// obviously, Shiny widgets can only appear in Shiny apps/documents.
var shinyMode = window.HTMLWidgets.shinyMode =
typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings;
// We can't count on jQuery being available, so we implement our own
// version if necessary.
function querySelectorAll(scope, selector) {
if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) {
return scope.find(selector);
}
if (scope.querySelectorAll) {
return scope.querySelectorAll(selector);
}
}
function asArray(value) {
if (value === null)
return [];
if ($.isArray(value))
return value;
return [value];
}
// Implement jQuery's extend
function extend(target /*, ... */) {
if (arguments.length == 1) {
return target;
}
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
}
return target;
}
// IE8 doesn't support Array.forEach.
function forEach(values, callback, thisArg) {
if (values.forEach) {
values.forEach(callback, thisArg);
} else {
for (var i = 0; i < values.length; i++) {
callback.call(thisArg, values[i], i, values);
}
}
}
// Replaces the specified method with the return value of funcSource.
//
// Note that funcSource should not BE the new method, it should be a function
// that RETURNS the new method. funcSource receives a single argument that is
// the overridden method, it can be called from the new method. The overridden
// method can be called like a regular function, it has the target permanently
// bound to it so "this" will work correctly.
function overrideMethod(target, methodName, funcSource) {
var superFunc = target[methodName] || function() {};
var superFuncBound = function() {
return superFunc.apply(target, arguments);
};
target[methodName] = funcSource(superFuncBound);
}
// Add a method to delegator that, when invoked, calls
// delegatee.methodName. If there is no such method on
// the delegatee, but there was one on delegator before
// delegateMethod was called, then the original version
// is invoked instead.
// For example:
//
// var a = {
// method1: function() { console.log('a1'); }
// method2: function() { console.log('a2'); }
// };
// var b = {
// method1: function() { console.log('b1'); }
// };
// delegateMethod(a, b, "method1");
// delegateMethod(a, b, "method2");
// a.method1();
// a.method2();
//
// The output would be "b1", "a2".
function delegateMethod(delegator, delegatee, methodName) {
var inherited = delegator[methodName];
delegator[methodName] = function() {
var target = delegatee;
var method = delegatee[methodName];
// The method doesn't exist on the delegatee. Instead,
// call the method on the delegator, if it exists.
if (!method) {
target = delegator;
method = inherited;
}
if (method) {
return method.apply(target, arguments);
}
};
}
// Implement a vague facsimilie of jQuery's data method
function elementData(el, name, value) {
if (arguments.length == 2) {
return el["htmlwidget_data_" + name];
} else if (arguments.length == 3) {
el["htmlwidget_data_" + name] = value;
return el;
} else {
throw new Error("Wrong number of arguments for elementData: " +
arguments.length);
}
}
// http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function hasClass(el, className) {
var re = new RegExp("\\b" + escapeRegExp(className) + "\\b");
return re.test(el.className);
}
// elements - array (or array-like object) of HTML elements
// className - class name to test for
// include - if true, only return elements with given className;
// if false, only return elements *without* given className
function filterByClass(elements, className, include) {
var results = [];
for (var i = 0; i < elements.length; i++) {
if (hasClass(elements[i], className) == include)
results.push(elements[i]);
}
return results;
}
function on(obj, eventName, func) {
if (obj.addEventListener) {
obj.addEventListener(eventName, func, false);
} else if (obj.attachEvent) {
obj.attachEvent(eventName, func);
}
}
function off(obj, eventName, func) {
if (obj.removeEventListener)
obj.removeEventListener(eventName, func, false);
else if (obj.detachEvent) {
obj.detachEvent(eventName, func);
}
}
// Translate array of values to top/right/bottom/left, as usual with
// the "padding" CSS property
// https://developer.mozilla.org/en-US/docs/Web/CSS/padding
function unpackPadding(value) {
if (typeof(value) === "number")
value = [value];
if (value.length === 1) {
return {top: value[0], right: value[0], bottom: value[0], left: value[0]};
}
if (value.length === 2) {
return {top: value[0], right: value[1], bottom: value[0], left: value[1]};
}
if (value.length === 3) {
return {top: value[0], right: value[1], bottom: value[2], left: value[1]};
}
if (value.length === 4) {
return {top: value[0], right: value[1], bottom: value[2], left: value[3]};
}
}
// Convert an unpacked padding object to a CSS value
function paddingToCss(paddingObj) {
return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px";
}
// Makes a number suitable for CSS
function px(x) {
if (typeof(x) === "number")
return x + "px";
else
return x;
}
// Retrieves runtime widget sizing information for an element.
// The return value is either null, or an object with fill, padding,
// defaultWidth, defaultHeight fields.
function sizingPolicy(el) {
var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']");
if (!sizingEl)
return null;
var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}");
if (viewerMode) {
return sp.viewer;
} else {
return sp.browser;
}
}
// @param tasks Array of strings (or falsy value, in which case no-op).
// Each element must be a valid JavaScript expression that yields a
// function. Or, can be an array of objects with "code" and "data"
// properties; in this case, the "code" property should be a string
// of JS that's an expr that yields a function, and "data" should be
// an object that will be added as an additional argument when that
// function is called.
// @param target The object that will be "this" for each function
// execution.
// @param args Array of arguments to be passed to the functions. (The
// same arguments will be passed to all functions.)
function evalAndRun(tasks, target, args) {
if (tasks) {
forEach(tasks, function(task) {
var theseArgs = args;
if (typeof(task) === "object") {
theseArgs = theseArgs.concat([task.data]);
task = task.code;
}
var taskFunc = tryEval(task);
if (typeof(taskFunc) !== "function") {
throw new Error("Task must be a function! Source:\n" + task);
}
taskFunc.apply(target, theseArgs);
});
}
}
// Attempt eval() both with and without enclosing in parentheses.
// Note that enclosing coerces a function declaration into
// an expression that eval() can parse
// (otherwise, a SyntaxError is thrown)
function tryEval(code) {
var result = null;
try {
result = eval("(" + code + ")");
} catch(error) {
if (!(error instanceof SyntaxError)) {
throw error;
}
try {
result = eval(code);
} catch(e) {
if (e instanceof SyntaxError) {
throw error;
} else {
throw e;
}
}
}
return result;
}
function initSizing(el) {
var sizing = sizingPolicy(el);
if (!sizing)
return;
var cel = document.getElementById("htmlwidget_container");
if (!cel)
return;
if (typeof(sizing.padding) !== "undefined") {
document.body.style.margin = "0";
document.body.style.padding = paddingToCss(unpackPadding(sizing.padding));
}
if (sizing.fill) {
document.body.style.overflow = "hidden";
document.body.style.width = "100%";
document.body.style.height = "100%";
document.documentElement.style.width = "100%";
document.documentElement.style.height = "100%";
if (cel) {
cel.style.position = "absolute";
var pad = unpackPadding(sizing.padding);
cel.style.top = pad.top + "px";
cel.style.right = pad.right + "px";
cel.style.bottom = pad.bottom + "px";
cel.style.left = pad.left + "px";
el.style.width = "100%";
el.style.height = "100%";
}
return {
getWidth: function() { return cel.offsetWidth; },
getHeight: function() { return cel.offsetHeight; }
};
} else {
el.style.width = px(sizing.width);
el.style.height = px(sizing.height);
return {
getWidth: function() { return el.offsetWidth; },
getHeight: function() { return el.offsetHeight; }
};
}
}
// Default implementations for methods
var defaults = {
find: function(scope) {
return querySelectorAll(scope, "." + this.name);
},
renderError: function(el, err) {
var $el = $(el);
this.clearError(el);
// Add all these error classes, as Shiny does
var errClass = "shiny-output-error";
if (err.type !== null) {
// use the classes of the error condition as CSS class names
errClass = errClass + " " + $.map(asArray(err.type), function(type) {
return errClass + "-" + type;
}).join(" ");
}
errClass = errClass + " htmlwidgets-error";
// Is el inline or block? If inline or inline-block, just display:none it
// and add an inline error.
var display = $el.css("display");
$el.data("restore-display-mode", display);
if (display === "inline" || display === "inline-block") {
$el.hide();
if (err.message !== "") {
var errorSpan = $("").addClass(errClass);
errorSpan.text(err.message);
$el.after(errorSpan);
}
} else if (display === "block") {
// If block, add an error just after the el, set visibility:none on the
// el, and position the error to be on top of the el.
// Mark it with a unique ID and CSS class so we can remove it later.
$el.css("visibility", "hidden");
if (err.message !== "") {
var errorDiv = $("
").addClass(errClass).css("position", "absolute")
.css("top", el.offsetTop)
.css("left", el.offsetLeft)
// setting width can push out the page size, forcing otherwise
// unnecessary scrollbars to appear and making it impossible for
// the element to shrink; so use max-width instead
.css("maxWidth", el.offsetWidth)
.css("height", el.offsetHeight);
errorDiv.text(err.message);
$el.after(errorDiv);
// Really dumb way to keep the size/position of the error in sync with
// the parent element as the window is resized or whatever.
var intId = setInterval(function() {
if (!errorDiv[0].parentElement) {
clearInterval(intId);
return;
}
errorDiv
.css("top", el.offsetTop)
.css("left", el.offsetLeft)
.css("maxWidth", el.offsetWidth)
.css("height", el.offsetHeight);
}, 500);
}
}
},
clearError: function(el) {
var $el = $(el);
var display = $el.data("restore-display-mode");
$el.data("restore-display-mode", null);
if (display === "inline" || display === "inline-block") {
if (display)
$el.css("display", display);
$(el.nextSibling).filter(".htmlwidgets-error").remove();
} else if (display === "block"){
$el.css("visibility", "inherit");
$(el.nextSibling).filter(".htmlwidgets-error").remove();
}
},
sizing: {}
};
// Called by widget bindings to register a new type of widget. The definition
// object can contain the following properties:
// - name (required) - A string indicating the binding name, which will be
// used by default as the CSS classname to look for.
// - initialize (optional) - A function(el) that will be called once per
// widget element; if a value is returned, it will be passed as the third
// value to renderValue.
// - renderValue (required) - A function(el, data, initValue) that will be
// called with data. Static contexts will cause this to be called once per
// element; Shiny apps will cause this to be called multiple times per
// element, as the data changes.
window.HTMLWidgets.widget = function(definition) {
if (!definition.name) {
throw new Error("Widget must have a name");
}
if (!definition.type) {
throw new Error("Widget must have a type");
}
// Currently we only support output widgets
if (definition.type !== "output") {
throw new Error("Unrecognized widget type '" + definition.type + "'");
}
// TODO: Verify that .name is a valid CSS classname
// Support new-style instance-bound definitions. Old-style class-bound
// definitions have one widget "object" per widget per type/class of
// widget; the renderValue and resize methods on such widget objects
// take el and instance arguments, because the widget object can't
// store them. New-style instance-bound definitions have one widget
// object per widget instance; the definition that's passed in doesn't
// provide renderValue or resize methods at all, just the single method
// factory(el, width, height)
// which returns an object that has renderValue(x) and resize(w, h).
// This enables a far more natural programming style for the widget
// author, who can store per-instance state using either OO-style
// instance fields or functional-style closure variables (I guess this
// is in contrast to what can only be called C-style pseudo-OO which is
// what we required before).
if (definition.factory) {
definition = createLegacyDefinitionAdapter(definition);
}
if (!definition.renderValue) {
throw new Error("Widget must have a renderValue function");
}
// For static rendering (non-Shiny), use a simple widget registration
// scheme. We also use this scheme for Shiny apps/documents that also
// contain static widgets.
window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || [];
// Merge defaults into the definition; don't mutate the original definition.
var staticBinding = extend({}, defaults, definition);
overrideMethod(staticBinding, "find", function(superfunc) {
return function(scope) {
var results = superfunc(scope);
// Filter out Shiny outputs, we only want the static kind
return filterByClass(results, "html-widget-output", false);
};
});
window.HTMLWidgets.widgets.push(staticBinding);
if (shinyMode) {
// Shiny is running. Register the definition with an output binding.
// The definition itself will not be the output binding, instead
// we will make an output binding object that delegates to the
// definition. This is because we foolishly used the same method
// name (renderValue) for htmlwidgets definition and Shiny bindings
// but they actually have quite different semantics (the Shiny
// bindings receive data that includes lots of metadata that it
// strips off before calling htmlwidgets renderValue). We can't
// just ignore the difference because in some widgets it's helpful
// to call this.renderValue() from inside of resize(), and if
// we're not delegating, then that call will go to the Shiny
// version instead of the htmlwidgets version.
// Merge defaults with definition, without mutating either.
var bindingDef = extend({}, defaults, definition);
// This object will be our actual Shiny binding.
var shinyBinding = new Shiny.OutputBinding();
// With a few exceptions, we'll want to simply use the bindingDef's
// version of methods if they are available, otherwise fall back to
// Shiny's defaults. NOTE: If Shiny's output bindings gain additional
// methods in the future, and we want them to be overrideable by
// HTMLWidget binding definitions, then we'll need to add them to this
// list.
delegateMethod(shinyBinding, bindingDef, "getId");
delegateMethod(shinyBinding, bindingDef, "onValueChange");
delegateMethod(shinyBinding, bindingDef, "onValueError");
delegateMethod(shinyBinding, bindingDef, "renderError");
delegateMethod(shinyBinding, bindingDef, "clearError");
delegateMethod(shinyBinding, bindingDef, "showProgress");
// The find, renderValue, and resize are handled differently, because we
// want to actually decorate the behavior of the bindingDef methods.
shinyBinding.find = function(scope) {
var results = bindingDef.find(scope);
// Only return elements that are Shiny outputs, not static ones
var dynamicResults = results.filter(".html-widget-output");
// It's possible that whatever caused Shiny to think there might be
// new dynamic outputs, also caused there to be new static outputs.
// Since there might be lots of different htmlwidgets bindings, we
// schedule execution for later--no need to staticRender multiple
// times.
if (results.length !== dynamicResults.length)
scheduleStaticRender();
return dynamicResults;
};
// Wrap renderValue to handle initialization, which unfortunately isn't
// supported natively by Shiny at the time of this writing.
shinyBinding.renderValue = function(el, data) {
Shiny.renderDependencies(data.deps);
// Resolve strings marked as javascript literals to objects
if (!(data.evals instanceof Array)) data.evals = [data.evals];
for (var i = 0; data.evals && i < data.evals.length; i++) {
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]);
}
if (!bindingDef.renderOnNullValue) {
if (data.x === null) {
el.style.visibility = "hidden";
return;
} else {
el.style.visibility = "inherit";
}
}
if (!elementData(el, "initialized")) {
initSizing(el);
elementData(el, "initialized", true);
if (bindingDef.initialize) {
var result = bindingDef.initialize(el, el.offsetWidth,
el.offsetHeight);
elementData(el, "init_result", result);
}
}
bindingDef.renderValue(el, data.x, elementData(el, "init_result"));
evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]);
};
// Only override resize if bindingDef implements it
if (bindingDef.resize) {
shinyBinding.resize = function(el, width, height) {
// Shiny can call resize before initialize/renderValue have been
// called, which doesn't make sense for widgets.
if (elementData(el, "initialized")) {
bindingDef.resize(el, width, height, elementData(el, "init_result"));
}
};
}
Shiny.outputBindings.register(shinyBinding, bindingDef.name);
}
};
var scheduleStaticRenderTimerId = null;
function scheduleStaticRender() {
if (!scheduleStaticRenderTimerId) {
scheduleStaticRenderTimerId = setTimeout(function() {
scheduleStaticRenderTimerId = null;
window.HTMLWidgets.staticRender();
}, 1);
}
}
// Render static widgets after the document finishes loading
// Statically render all elements that are of this widget's class
window.HTMLWidgets.staticRender = function() {
var bindings = window.HTMLWidgets.widgets || [];
forEach(bindings, function(binding) {
var matches = binding.find(document.documentElement);
forEach(matches, function(el) {
var sizeObj = initSizing(el, binding);
if (hasClass(el, "html-widget-static-bound"))
return;
el.className = el.className + " html-widget-static-bound";
var initResult;
if (binding.initialize) {
initResult = binding.initialize(el,
sizeObj ? sizeObj.getWidth() : el.offsetWidth,
sizeObj ? sizeObj.getHeight() : el.offsetHeight
);
elementData(el, "init_result", initResult);
}
if (binding.resize) {
var lastSize = {
w: sizeObj ? sizeObj.getWidth() : el.offsetWidth,
h: sizeObj ? sizeObj.getHeight() : el.offsetHeight
};
var resizeHandler = function(e) {
var size = {
w: sizeObj ? sizeObj.getWidth() : el.offsetWidth,
h: sizeObj ? sizeObj.getHeight() : el.offsetHeight
};
if (size.w === 0 && size.h === 0)
return;
if (size.w === lastSize.w && size.h === lastSize.h)
return;
lastSize = size;
binding.resize(el, size.w, size.h, initResult);
};
on(window, "resize", resizeHandler);
// This is needed for cases where we're running in a Shiny
// app, but the widget itself is not a Shiny output, but
// rather a simple static widget. One example of this is
// an rmarkdown document that has runtime:shiny and widget
// that isn't in a render function. Shiny only knows to
// call resize handlers for Shiny outputs, not for static
// widgets, so we do it ourselves.
if (window.jQuery) {
window.jQuery(document).on(
"shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets",
resizeHandler
);
window.jQuery(document).on(
"hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets",
resizeHandler
);
}
// This is needed for the specific case of ioslides, which
// flips slides between display:none and display:block.
// Ideally we would not have to have ioslide-specific code
// here, but rather have ioslides raise a generic event,
// but the rmarkdown package just went to CRAN so the
// window to getting that fixed may be long.
if (window.addEventListener) {
// It's OK to limit this to window.addEventListener
// browsers because ioslides itself only supports
// such browsers.
on(document, "slideenter", resizeHandler);
on(document, "slideleave", resizeHandler);
}
}
var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");
if (scriptData) {
var data = JSON.parse(scriptData.textContent || scriptData.text);
// Resolve strings marked as javascript literals to objects
if (!(data.evals instanceof Array)) data.evals = [data.evals];
for (var k = 0; data.evals && k < data.evals.length; k++) {
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]);
}
binding.renderValue(el, data.x, initResult);
evalAndRun(data.jsHooks.render, initResult, [el, data.x]);
}
});
});
invokePostRenderHandlers();
}
function has_jQuery3() {
if (!window.jQuery) {
return false;
}
var $version = window.jQuery.fn.jquery;
var $major_version = parseInt($version.split(".")[0]);
return $major_version >= 3;
}
/*
/ Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's
/ on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now
/ really means $(setTimeout(fn)).
/ https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous
/
/ Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny
/ one tick later than it did before, which means staticRender() is
/ called renderValue() earlier than (advanced) widget authors might be expecting.
/ https://github.com/rstudio/shiny/issues/2630
/
/ For a concrete example, leaflet has some methods (e.g., updateBounds)
/ which reference Shiny methods registered in initShiny (e.g., setInputValue).
/ Since leaflet is privy to this life-cycle, it knows to use setTimeout() to
/ delay execution of those methods (until Shiny methods are ready)
/ https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268
/
/ Ideally widget authors wouldn't need to use this setTimeout() hack that
/ leaflet uses to call Shiny methods on a staticRender(). In the long run,
/ the logic initShiny should be broken up so that method registration happens
/ right away, but binding happens later.
*/
function maybeStaticRenderLater() {
if (shinyMode && has_jQuery3()) {
window.jQuery(window.HTMLWidgets.staticRender);
} else {
window.HTMLWidgets.staticRender();
}
}
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", function() {
document.removeEventListener("DOMContentLoaded", arguments.callee, false);
maybeStaticRenderLater();
}, false);
} else if (document.attachEvent) {
document.attachEvent("onreadystatechange", function() {
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", arguments.callee);
maybeStaticRenderLater();
}
});
}
window.HTMLWidgets.getAttachmentUrl = function(depname, key) {
// If no key, default to the first item
if (typeof(key) === "undefined")
key = 1;
var link = document.getElementById(depname + "-" + key + "-attachment");
if (!link) {
throw new Error("Attachment " + depname + "/" + key + " not found in document");
}
return link.getAttribute("href");
};
window.HTMLWidgets.dataframeToD3 = function(df) {
var names = [];
var length;
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;
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;
};
window.HTMLWidgets.transposeArray2D = function(array) {
if (array.length === 0) return array;
var newArray = array[0].map(function(col, i) {
return array.map(function(row) {
return row[i]
})
});
return newArray;
};
// Split value at splitChar, but allow splitChar to be escaped
// using escapeChar. Any other characters escaped by escapeChar
// will be included as usual (including escapeChar itself).
function splitWithEscape(value, splitChar, escapeChar) {
var results = [];
var escapeMode = false;
var currentResult = "";
for (var pos = 0; pos < value.length; pos++) {
if (!escapeMode) {
if (value[pos] === splitChar) {
results.push(currentResult);
currentResult = "";
} else if (value[pos] === escapeChar) {
escapeMode = true;
} else {
currentResult += value[pos];
}
} else {
currentResult += value[pos];
escapeMode = false;
}
}
if (currentResult !== "") {
results.push(currentResult);
}
return results;
}
// Function authored by Yihui/JJ Allaire
window.HTMLWidgets.evaluateStringMember = function(o, member) {
var parts = splitWithEscape(member, '.', '\\');
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
// part may be a character or 'numeric' member name
if (o !== null && typeof o === "object" && part in o) {
if (i == (l - 1)) { // if we are at the end of the line then evalulate
if (typeof o[part] === "string")
o[part] = tryEval(o[part]);
} else { // otherwise continue to next embedded object
o = o[part];
}
}
}
};
// Retrieve the HTMLWidget instance (i.e. the return value of an
// HTMLWidget binding's initialize() or factory() function)
// associated with an element, or null if none.
window.HTMLWidgets.getInstance = function(el) {
return elementData(el, "init_result");
};
// Finds the first element in the scope that matches the selector,
// and returns the HTMLWidget instance (i.e. the return value of
// an HTMLWidget binding's initialize() or factory() function)
// associated with that element, if any. If no element matches the
// selector, or the first matching element has no HTMLWidget
// instance associated with it, then null is returned.
//
// The scope argument is optional, and defaults to window.document.
window.HTMLWidgets.find = function(scope, selector) {
if (arguments.length == 1) {
selector = scope;
scope = document;
}
var el = scope.querySelector(selector);
if (el === null) {
return null;
} else {
return window.HTMLWidgets.getInstance(el);
}
};
// Finds all elements in the scope that match the selector, and
// returns the HTMLWidget instances (i.e. the return values of
// an HTMLWidget binding's initialize() or factory() function)
// associated with the elements, in an array. If elements that
// match the selector don't have an associated HTMLWidget
// instance, the returned array will contain nulls.
//
// The scope argument is optional, and defaults to window.document.
window.HTMLWidgets.findAll = function(scope, selector) {
if (arguments.length == 1) {
selector = scope;
scope = document;
}
var nodes = scope.querySelectorAll(selector);
var results = [];
for (var i = 0; i < nodes.length; i++) {
results.push(window.HTMLWidgets.getInstance(nodes[i]));
}
return results;
};
var postRenderHandlers = [];
function invokePostRenderHandlers() {
while (postRenderHandlers.length) {
var handler = postRenderHandlers.shift();
if (handler) {
handler();
}
}
}
// Register the given callback function to be invoked after the
// next time static widgets are rendered.
window.HTMLWidgets.addPostRenderHandler = function(callback) {
postRenderHandlers.push(callback);
};
// Takes a new-style instance-bound definition, and returns an
// old-style class-bound definition. This saves us from having
// to rewrite all the logic in this file to accomodate both
// types of definitions.
function createLegacyDefinitionAdapter(defn) {
var result = {
name: defn.name,
type: defn.type,
initialize: function(el, width, height) {
return defn.factory(el, width, height);
},
renderValue: function(el, x, instance) {
return instance.renderValue(x);
},
resize: function(el, width, height, instance) {
return instance.resize(width, height);
}
};
if (defn.find)
result.find = defn.find;
if (defn.renderError)
result.renderError = defn.renderError;
if (defn.clearError)
result.clearError = defn.clearError;
return result;
}
})();
================================================
FILE: docs/images/02_bellCurve/lib/jquery-1.11.3/jquery-AUTHORS.txt
================================================
Authors ordered by first contribution.
John Resig
Gilles van den Hoven
Michael Geary
Stefan Petre
Yehuda Katz
Corey Jewett
Klaus Hartl
Franck Marcia
Jörn Zaefferer
Paul Bakaus
Brandon Aaron
Mike Alsup
Dave Methvin
Ed Engelhardt
Sean Catchpole
Paul Mclanahan
David Serduke
Richard D. Worth
Scott González
Ariel Flesler
Jon Evans
TJ Holowaychuk
Michael Bensoussan
Robert Katić
Louis-Rémi Babé
Earle Castledine
Damian Janowski
Rich Dougherty
Kim Dalsgaard
Andrea Giammarchi
Mark Gibson
Karl Swedberg
Justin Meyer
Ben Alman
James Padolsey
David Petersen
Batiste Bieler
Alexander Farkas
Rick Waldron
Filipe Fortes
Neeraj Singh
Paul Irish
Iraê Carvalho
Matt Curry
Michael Monteleone
Noah Sloan
Tom Viner
Douglas Neiner
Adam J. Sontag
Dave Reed
Ralph Whitbeck
Carl Fürstenberg
Jacob Wright
J. Ryan Stinnett
unknown
temp01
Heungsub Lee
Colin Snover
Ryan W Tenney
Pinhook
Ron Otten
Jephte Clain
Anton Matzneller
Alex Sexton
Dan Heberden
Henri Wiechers
Russell Holbrook
Julian Aubourg
Gianni Alessandro Chiappetta
Scott Jehl
James Burke
Jonas Pfenniger
Xavi Ramirez
Jared Grippe
Sylvester Keil
Brandon Sterne
Mathias Bynens
Timmy Willison
Corey Frang
Digitalxero
Anton Kovalyov
David Murdoch
Josh Varner
Charles McNulty
Jordan Boesch
Jess Thrysoee
Michael Murray
Lee Carpenter
Alexis Abril
Rob Morgan
John Firebaugh
Sam Bisbee
Gilmore Davidson
Brian Brennan
Xavier Montillet
Daniel Pihlstrom
Sahab Yazdani
avaly
Scott Hughes
Mike Sherov
Greg Hazel
Schalk Neethling
Denis Knauf
Timo Tijhof
Steen Nielsen
Anton Ryzhov
Shi Chuan
Berker Peksag
Toby Brain
Matt Mueller
Justin
Daniel Herman
Oleg Gaidarenko
Richard Gibson
Rafaël Blais Masson
cmc3cn <59194618@qq.com>
Joe Presbrey
Sindre Sorhus
Arne de Bree
Vladislav Zarakovsky
Andrew E Monat
Oskari
Joao Henrique de Andrade Bruni
tsinha
Matt Farmer
Trey Hunner
Jason Moon
Jeffery To
Kris Borchers
Vladimir Zhuravlev
Jacob Thornton
Chad Killingsworth
Nowres Rafid
David Benjamin
Uri Gilad
Chris Faulkner
Elijah Manor
Daniel Chatfield
Nikita Govorov
Wesley Walser
Mike Pennisi
Markus Staab
Dave Riddle
Callum Macrae
Benjamin Truyman
James Huston
Erick Ruiz de Chávez
David Bonner
Akintayo Akinwunmi
MORGAN
Ismail Khair
Carl Danley
Mike Petrovich
Greg Lavallee
Daniel Gálvez
Sai Lung Wong
Tom H Fuertes
Roland Eckl
Jay Merrifield
Allen J Schmidt Jr
Jonathan Sampson
Marcel Greter
Matthias Jäggli
David Fox
Yiming He
Devin Cooper
Paul Ramos
Rod Vagg
Bennett Sorbo
Sebastian Burkhard
nanto
Danil Somsikov
Ryunosuke SATO
Jean Boussier
Adam Coulombe
Andrew Plummer
Mark Raddatz
Dmitry Gusev
Michał Gołębiowski
Nguyen Phuc Lam
Tom H Fuertes
Brandon Johnson
Jason Bedard
Kyle Robinson Young
Renato Oliveira dos Santos
Chris Talkington
Eddie Monge
Terry Jones
Jason Merino
Jeremy Dunck
Chris Price
Amey Sakhadeo
Anthony Ryan
Dominik D. Geyer
George Kats
Lihan Li
Ronny Springer
Marian Sollmann
Corey Frang
Chris Antaki
Noah Hamann
David Hong
Jakob Stoeck
Christopher Jones
Forbes Lindesay
John Paul
S. Andrew Sheppard
Leonardo Balter
Roman Reiß
Benjy Cui
Rodrigo Rosenfeld Rosas
John Hoven
Christian Kosmowski
Liang Peng
TJ VanToll
================================================
FILE: docs/images/02_bellCurve/lib/jquery-1.11.3/jquery.js
================================================
/*!
* jQuery JavaScript Library v1.11.3
* http://jquery.com/
*
* Includes Sizzle.js
* http://sizzlejs.com/
*
* Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2015-04-28T16:19Z
*/
(function( global, factory ) {
if ( typeof module === "object" && typeof module.exports === "object" ) {
// For CommonJS and CommonJS-like environments where a proper window is present,
// execute the factory and get jQuery
// For environments that do not inherently posses a window with a document
// (such as Node.js), expose a jQuery-making factory as module.exports
// This accentuates the need for the creation of a real window
// e.g. var jQuery = require("jquery")(window);
// See ticket #14549 for more info
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
// Pass this if window is not defined yet
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
// Can't do this because several apps including ASP.NET trace
// the stack via arguments.caller.callee and Firefox dies if
// you try to trace through "use strict" call chains. (#13335)
// Support: Firefox 18+
//
var deletedIds = [];
var slice = deletedIds.slice;
var concat = deletedIds.concat;
var push = deletedIds.push;
var indexOf = deletedIds.indexOf;
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var support = {};
var
version = "1.11.3",
// Define a local copy of jQuery
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery.fn.init( selector, context );
},
// Support: Android<4.1, IE<9
// Make sure we trim BOM and NBSP
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
// Matches dashed string for camelizing
rmsPrefix = /^-ms-/,
rdashAlpha = /-([\da-z])/gi,
// Used by jQuery.camelCase as callback to replace()
fcamelCase = function( all, letter ) {
return letter.toUpperCase();
};
jQuery.fn = jQuery.prototype = {
// The current version of jQuery being used
jquery: version,
constructor: jQuery,
// Start with an empty selector
selector: "",
// The default length of a jQuery object is 0
length: 0,
toArray: function() {
return slice.call( this );
},
// Get the Nth element in the matched element set OR
// Get the whole matched element set as a clean array
get: function( num ) {
return num != null ?
// Return just the one element from the set
( num < 0 ? this[ num + this.length ] : this[ num ] ) :
// Return all the elements in a clean array
slice.call( this );
},
// Take an array of elements and push it onto the stack
// (returning the new matched element set)
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
ret.context = this.context;
// Return the newly-formed element set
return ret;
},
// Execute a callback for every element in the matched set.
// (You can seed the arguments with an array of args, but this is
// only used internally.)
each: function( callback, args ) {
return jQuery.each( this, callback, args );
},
map: function( callback ) {
return this.pushStack( jQuery.map(this, function( elem, i ) {
return callback.call( elem, i, elem );
}));
},
slice: function() {
return this.pushStack( slice.apply( this, arguments ) );
},
first: function() {
return this.eq( 0 );
},
last: function() {
return this.eq( -1 );
},
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
},
end: function() {
return this.prevObject || this.constructor(null);
},
// For internal use only.
// Behaves like an Array's method, not like a jQuery method.
push: push,
sort: deletedIds.sort,
splice: deletedIds.splice
};
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
target = arguments[0] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
// skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( (options = arguments[ i ]) != null ) {
// Extend the base object
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// Prevent never-ending loop
if ( target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
jQuery.extend({
// Unique for each copy of jQuery on the page
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
// Assume jQuery is ready without the ready module
isReady: true,
error: function( msg ) {
throw new Error( msg );
},
noop: function() {},
// See test/unit/core.js for details concerning isFunction.
// Since version 1.3, DOM methods and functions like alert
// aren't supported. They return false on IE (#2968).
isFunction: function( obj ) {
return jQuery.type(obj) === "function";
},
isArray: Array.isArray || function( obj ) {
return jQuery.type(obj) === "array";
},
isWindow: function( obj ) {
/* jshint eqeqeq: false */
return obj != null && obj == obj.window;
},
isNumeric: function( obj ) {
// parseFloat NaNs numeric-cast false positives (null|true|false|"")
// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
// subtraction forces infinities to NaN
// adding 1 corrects loss of precision from parseFloat (#15100)
return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;
},
isEmptyObject: function( obj ) {
var name;
for ( name in obj ) {
return false;
}
return true;
},
isPlainObject: function( obj ) {
var key;
// Must be an Object.
// Because of IE, we also have to check the presence of the constructor property.
// Make sure that DOM nodes and window objects don't pass through, as well
if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
return false;
}
try {
// Not own constructor property must be Object
if ( obj.constructor &&
!hasOwn.call(obj, "constructor") &&
!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
return false;
}
} catch ( e ) {
// IE8,9 Will throw exceptions on certain host objects #9897
return false;
}
// Support: IE<9
// Handle iteration over inherited properties before own properties.
if ( support.ownLast ) {
for ( key in obj ) {
return hasOwn.call( obj, key );
}
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
for ( key in obj ) {}
return key === undefined || hasOwn.call( obj, key );
},
type: function( obj ) {
if ( obj == null ) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[ toString.call(obj) ] || "object" :
typeof obj;
},
// Evaluates a script in a global context
// Workarounds based on findings by Jim Driscoll
// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
globalEval: function( data ) {
if ( data && jQuery.trim( data ) ) {
// We use execScript on Internet Explorer
// We use an anonymous function so that context is window
// rather than jQuery in Firefox
( window.execScript || function( data ) {
window[ "eval" ].call( window, data );
} )( data );
}
},
// Convert dashed to camelCase; used by the css and data modules
// Microsoft forgot to hump their vendor prefix (#9572)
camelCase: function( string ) {
return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
},
nodeName: function( elem, name ) {
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
},
// args is for internal usage only
each: function( obj, callback, args ) {
var value,
i = 0,
length = obj.length,
isArray = isArraylike( obj );
if ( args ) {
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.apply( obj[ i ], args );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.apply( obj[ i ], args );
if ( value === false ) {
break;
}
}
}
// A special, fast, case for the most common use of each
} else {
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
}
}
return obj;
},
// Support: Android<4.1, IE<9
trim: function( text ) {
return text == null ?
"" :
( text + "" ).replace( rtrim, "" );
},
// results is for internal usage only
makeArray: function( arr, results ) {
var ret = results || [];
if ( arr != null ) {
if ( isArraylike( Object(arr) ) ) {
jQuery.merge( ret,
typeof arr === "string" ?
[ arr ] : arr
);
} else {
push.call( ret, arr );
}
}
return ret;
},
inArray: function( elem, arr, i ) {
var len;
if ( arr ) {
if ( indexOf ) {
return indexOf.call( arr, elem, i );
}
len = arr.length;
i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
for ( ; i < len; i++ ) {
// Skip accessing in sparse arrays
if ( i in arr && arr[ i ] === elem ) {
return i;
}
}
}
return -1;
},
merge: function( first, second ) {
var len = +second.length,
j = 0,
i = first.length;
while ( j < len ) {
first[ i++ ] = second[ j++ ];
}
// Support: IE<9
// Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
if ( len !== len ) {
while ( second[j] !== undefined ) {
first[ i++ ] = second[ j++ ];
}
}
first.length = i;
return first;
},
grep: function( elems, callback, invert ) {
var callbackInverse,
matches = [],
i = 0,
length = elems.length,
callbackExpect = !invert;
// Go through the array, only saving the items
// that pass the validator function
for ( ; i < length; i++ ) {
callbackInverse = !callback( elems[ i ], i );
if ( callbackInverse !== callbackExpect ) {
matches.push( elems[ i ] );
}
}
return matches;
},
// arg is for internal usage only
map: function( elems, callback, arg ) {
var value,
i = 0,
length = elems.length,
isArray = isArraylike( elems ),
ret = [];
// Go through the array, translating each of the items to their new values
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
// Go through every key on the object,
} else {
for ( i in elems ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
}
// Flatten any nested arrays
return concat.apply( [], ret );
},
// A global GUID counter for objects
guid: 1,
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function( fn, context ) {
var args, proxy, tmp;
if ( typeof context === "string" ) {
tmp = fn[ context ];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
args = slice.call( arguments, 2 );
proxy = function() {
return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
},
now: function() {
return +( new Date() );
},
// jQuery.support is not used in Core but other projects attach their
// properties to it so it needs to exist.
support: support
});
// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
function isArraylike( obj ) {
// Support: iOS 8.2 (not reproducible in simulator)
// `in` check used to prevent JIT error (gh-2145)
// hasOwn isn't used here due to false negatives
// regarding Nodelist length in IE
var length = "length" in obj && obj.length,
type = jQuery.type( obj );
if ( type === "function" || jQuery.isWindow( obj ) ) {
return false;
}
if ( obj.nodeType === 1 && length ) {
return true;
}
return type === "array" || length === 0 ||
typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
var Sizzle =
/*!
* Sizzle CSS Selector Engine v2.2.0-pre
* http://sizzlejs.com/
*
* Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
* Released under the MIT license
* http://jquery.org/license
*
* Date: 2014-12-16
*/
(function( window ) {
var i,
support,
Expr,
getText,
isXML,
tokenize,
compile,
select,
outermostContext,
sortInput,
hasDuplicate,
// Local document vars
setDocument,
document,
docElem,
documentIsHTML,
rbuggyQSA,
rbuggyMatches,
matches,
contains,
// Instance-specific data
expando = "sizzle" + 1 * new Date(),
preferredDoc = window.document,
dirruns = 0,
done = 0,
classCache = createCache(),
tokenCache = createCache(),
compilerCache = createCache(),
sortOrder = function( a, b ) {
if ( a === b ) {
hasDuplicate = true;
}
return 0;
},
// General-purpose constants
MAX_NEGATIVE = 1 << 31,
// Instance methods
hasOwn = ({}).hasOwnProperty,
arr = [],
pop = arr.pop,
push_native = arr.push,
push = arr.push,
slice = arr.slice,
// Use a stripped-down indexOf as it's faster than native
// http://jsperf.com/thor-indexof-vs-for/5
indexOf = function( list, elem ) {
var i = 0,
len = list.length;
for ( ; i < len; i++ ) {
if ( list[i] === elem ) {
return i;
}
}
return -1;
},
booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
// Regular expressions
// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
whitespace = "[\\x20\\t\\r\\n\\f]",
// http://www.w3.org/TR/css3-syntax/#characters
characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
// Loosely modeled on CSS identifier characters
// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
identifier = characterEncoding.replace( "w", "w#" ),
// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
// Operator (capture 2)
"*([*^$|!~]?=)" + whitespace +
// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
"*\\]",
pseudos = ":(" + characterEncoding + ")(?:\\((" +
// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
// 1. quoted (capture 3; capture 4 or capture 5)
"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
// 2. simple (capture 6)
"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
// 3. anything else (capture 2)
".*" +
")\\)|)",
// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
rwhitespace = new RegExp( whitespace + "+", "g" ),
rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
rpseudo = new RegExp( pseudos ),
ridentifier = new RegExp( "^" + identifier + "$" ),
matchExpr = {
"ID": new RegExp( "^#(" + characterEncoding + ")" ),
"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
"ATTR": new RegExp( "^" + attributes ),
"PSEUDO": new RegExp( "^" + pseudos ),
"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
// For use in libraries implementing .is()
// We use this for POS matching in `select`
"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
},
rinputs = /^(?:input|select|textarea|button)$/i,
rheader = /^h\d$/i,
rnative = /^[^{]+\{\s*\[native \w/,
// Easily-parseable/retrievable ID or TAG or CLASS selectors
rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
rsibling = /[+~]/,
rescape = /'|\\/g,
// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
funescape = function( _, escaped, escapedWhitespace ) {
var high = "0x" + escaped - 0x10000;
// NaN means non-codepoint
// Support: Firefox<24
// Workaround erroneous numeric interpretation of +"0x"
return high !== high || escapedWhitespace ?
escaped :
high < 0 ?
// BMP codepoint
String.fromCharCode( high + 0x10000 ) :
// Supplemental Plane codepoint (surrogate pair)
String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
},
// Used for iframes
// See setDocument()
// Removing the function wrapper causes a "Permission Denied"
// error in IE
unloadHandler = function() {
setDocument();
};
// Optimize for push.apply( _, NodeList )
try {
push.apply(
(arr = slice.call( preferredDoc.childNodes )),
preferredDoc.childNodes
);
// Support: Android<4.0
// Detect silently failing push.apply
arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
push = { apply: arr.length ?
// Leverage slice if possible
function( target, els ) {
push_native.apply( target, slice.call(els) );
} :
// Support: IE<9
// Otherwise append directly
function( target, els ) {
var j = target.length,
i = 0;
// Can't trust NodeList.length
while ( (target[j++] = els[i++]) ) {}
target.length = j - 1;
}
};
}
function Sizzle( selector, context, results, seed ) {
var match, elem, m, nodeType,
// QSA vars
i, groups, old, nid, newContext, newSelector;
if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
setDocument( context );
}
context = context || document;
results = results || [];
nodeType = context.nodeType;
if ( typeof selector !== "string" || !selector ||
nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
return results;
}
if ( !seed && documentIsHTML ) {
// Try to shortcut find operations when possible (e.g., not under DocumentFragment)
if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
// Speed-up: Sizzle("#ID")
if ( (m = match[1]) ) {
if ( nodeType === 9 ) {
elem = context.getElementById( m );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document (jQuery #6963)
if ( elem && elem.parentNode ) {
// Handle the case where IE, Opera, and Webkit return items
// by name instead of ID
if ( elem.id === m ) {
results.push( elem );
return results;
}
} else {
return results;
}
} else {
// Context is not a document
if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
contains( context, elem ) && elem.id === m ) {
results.push( elem );
return results;
}
}
// Speed-up: Sizzle("TAG")
} else if ( match[2] ) {
push.apply( results, context.getElementsByTagName( selector ) );
return results;
// Speed-up: Sizzle(".CLASS")
} else if ( (m = match[3]) && support.getElementsByClassName ) {
push.apply( results, context.getElementsByClassName( m ) );
return results;
}
}
// QSA path
if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
nid = old = expando;
newContext = context;
newSelector = nodeType !== 1 && selector;
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
groups = tokenize( selector );
if ( (old = context.getAttribute("id")) ) {
nid = old.replace( rescape, "\\$&" );
} else {
context.setAttribute( "id", nid );
}
nid = "[id='" + nid + "'] ";
i = groups.length;
while ( i-- ) {
groups[i] = nid + toSelector( groups[i] );
}
newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
newSelector = groups.join(",");
}
if ( newSelector ) {
try {
push.apply( results,
newContext.querySelectorAll( newSelector )
);
return results;
} catch(qsaError) {
} finally {
if ( !old ) {
context.removeAttribute("id");
}
}
}
}
}
// All others
return select( selector.replace( rtrim, "$1" ), context, results, seed );
}
/**
* Create key-value caches of limited size
* @returns {Function(string, Object)} Returns the Object data after storing it on itself with
* property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
* deleting the oldest entry
*/
function createCache() {
var keys = [];
function cache( key, value ) {
// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
if ( keys.push( key + " " ) > Expr.cacheLength ) {
// Only keep the most recent entries
delete cache[ keys.shift() ];
}
return (cache[ key + " " ] = value);
}
return cache;
}
/**
* Mark a function for special use by Sizzle
* @param {Function} fn The function to mark
*/
function markFunction( fn ) {
fn[ expando ] = true;
return fn;
}
/**
* Support testing using an element
* @param {Function} fn Passed the created div and expects a boolean result
*/
function assert( fn ) {
var div = document.createElement("div");
try {
return !!fn( div );
} catch (e) {
return false;
} finally {
// Remove from its parent by default
if ( div.parentNode ) {
div.parentNode.removeChild( div );
}
// release memory in IE
div = null;
}
}
/**
* Adds the same handler for all of the specified attrs
* @param {String} attrs Pipe-separated list of attributes
* @param {Function} handler The method that will be applied
*/
function addHandle( attrs, handler ) {
var arr = attrs.split("|"),
i = attrs.length;
while ( i-- ) {
Expr.attrHandle[ arr[i] ] = handler;
}
}
/**
* Checks document order of two siblings
* @param {Element} a
* @param {Element} b
* @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
*/
function siblingCheck( a, b ) {
var cur = b && a,
diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
( ~b.sourceIndex || MAX_NEGATIVE ) -
( ~a.sourceIndex || MAX_NEGATIVE );
// Use IE sourceIndex if available on both nodes
if ( diff ) {
return diff;
}
// Check if b follows a
if ( cur ) {
while ( (cur = cur.nextSibling) ) {
if ( cur === b ) {
return -1;
}
}
}
return a ? 1 : -1;
}
/**
* Returns a function to use in pseudos for input types
* @param {String} type
*/
function createInputPseudo( type ) {
return function( elem ) {
var name = elem.nodeName.toLowerCase();
return name === "input" && elem.type === type;
};
}
/**
* Returns a function to use in pseudos for buttons
* @param {String} type
*/
function createButtonPseudo( type ) {
return function( elem ) {
var name = elem.nodeName.toLowerCase();
return (name === "input" || name === "button") && elem.type === type;
};
}
/**
* Returns a function to use in pseudos for positionals
* @param {Function} fn
*/
function createPositionalPseudo( fn ) {
return markFunction(function( argument ) {
argument = +argument;
return markFunction(function( seed, matches ) {
var j,
matchIndexes = fn( [], seed.length, argument ),
i = matchIndexes.length;
// Match elements found at the specified indexes
while ( i-- ) {
if ( seed[ (j = matchIndexes[i]) ] ) {
seed[j] = !(matches[j] = seed[j]);
}
}
});
});
}
/**
* Checks a node for validity as a Sizzle context
* @param {Element|Object=} context
* @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
*/
function testContext( context ) {
return context && typeof context.getElementsByTagName !== "undefined" && context;
}
// Expose support vars for convenience
support = Sizzle.support = {};
/**
* Detects XML nodes
* @param {Element|Object} elem An element or a document
* @returns {Boolean} True iff elem is a non-HTML XML node
*/
isXML = Sizzle.isXML = function( elem ) {
// documentElement is verified for cases where it doesn't yet exist
// (such as loading iframes in IE - #4833)
var documentElement = elem && (elem.ownerDocument || elem).documentElement;
return documentElement ? documentElement.nodeName !== "HTML" : false;
};
/**
* Sets document-related variables once based on the current document
* @param {Element|Object} [doc] An element or document object to use to set the document
* @returns {Object} Returns the current document
*/
setDocument = Sizzle.setDocument = function( node ) {
var hasCompare, parent,
doc = node ? node.ownerDocument || node : preferredDoc;
// If no document and documentElement is available, return
if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
return document;
}
// Set our document
document = doc;
docElem = doc.documentElement;
parent = doc.defaultView;
// Support: IE>8
// If iframe document is assigned to "document" variable and if iframe has been reloaded,
// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
// IE6-8 do not support the defaultView property so parent will be undefined
if ( parent && parent !== parent.top ) {
// IE11 does not have attachEvent, so all must suffer
if ( parent.addEventListener ) {
parent.addEventListener( "unload", unloadHandler, false );
} else if ( parent.attachEvent ) {
parent.attachEvent( "onunload", unloadHandler );
}
}
/* Support tests
---------------------------------------------------------------------- */
documentIsHTML = !isXML( doc );
/* Attributes
---------------------------------------------------------------------- */
// Support: IE<8
// Verify that getAttribute really returns attributes and not properties
// (excepting IE8 booleans)
support.attributes = assert(function( div ) {
div.className = "i";
return !div.getAttribute("className");
});
/* getElement(s)By*
---------------------------------------------------------------------- */
// Check if getElementsByTagName("*") returns only elements
support.getElementsByTagName = assert(function( div ) {
div.appendChild( doc.createComment("") );
return !div.getElementsByTagName("*").length;
});
// Support: IE<9
support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
// Support: IE<10
// Check if getElementById returns elements by name
// The broken getElementById methods don't pick up programatically-set names,
// so use a roundabout getElementsByName test
support.getById = assert(function( div ) {
docElem.appendChild( div ).id = expando;
return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
});
// ID find and filter
if ( support.getById ) {
Expr.find["ID"] = function( id, context ) {
if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
var m = context.getElementById( id );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
return m && m.parentNode ? [ m ] : [];
}
};
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
return elem.getAttribute("id") === attrId;
};
};
} else {
// Support: IE6/7
// getElementById is not reliable as a find shortcut
delete Expr.find["ID"];
Expr.filter["ID"] = function( id ) {
var attrId = id.replace( runescape, funescape );
return function( elem ) {
var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
return node && node.value === attrId;
};
};
}
// Tag
Expr.find["TAG"] = support.getElementsByTagName ?
function( tag, context ) {
if ( typeof context.getElementsByTagName !== "undefined" ) {
return context.getElementsByTagName( tag );
// DocumentFragment nodes don't have gEBTN
} else if ( support.qsa ) {
return context.querySelectorAll( tag );
}
} :
function( tag, context ) {
var elem,
tmp = [],
i = 0,
// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
results = context.getElementsByTagName( tag );
// Filter out possible comments
if ( tag === "*" ) {
while ( (elem = results[i++]) ) {
if ( elem.nodeType === 1 ) {
tmp.push( elem );
}
}
return tmp;
}
return results;
};
// Class
Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
if ( documentIsHTML ) {
return context.getElementsByClassName( className );
}
};
/* QSA/matchesSelector
---------------------------------------------------------------------- */
// QSA and matchesSelector support
// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
rbuggyMatches = [];
// qSa(:focus) reports false when true (Chrome 21)
// We allow this because of a bug in IE8/9 that throws an error
// whenever `document.activeElement` is accessed on an iframe
// So, we allow :focus to pass through QSA all the time to avoid the IE error
// See http://bugs.jquery.com/ticket/13378
rbuggyQSA = [];
if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
// Build QSA regex
// Regex strategy adopted from Diego Perini
assert(function( div ) {
// Select is set to empty string on purpose
// This is to test IE's treatment of not explicitly
// setting a boolean content attribute,
// since its presence should be enough
// http://bugs.jquery.com/ticket/12359
docElem.appendChild( div ).innerHTML = "" +
"";
// Support: IE8, Opera 11-12.16
// Nothing should be selected when empty strings follow ^= or $= or *=
// The test attribute must be unknown in Opera but "safe" for WinRT
// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
if ( div.querySelectorAll("[msallowcapture^='']").length ) {
rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
}
// Support: IE8
// Boolean attributes and "value" are not treated correctly
if ( !div.querySelectorAll("[selected]").length ) {
rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
}
// Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+
if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
rbuggyQSA.push("~=");
}
// Webkit/Opera - :checked should return selected option elements
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
// IE8 throws error here and will not see later tests
if ( !div.querySelectorAll(":checked").length ) {
rbuggyQSA.push(":checked");
}
// Support: Safari 8+, iOS 8+
// https://bugs.webkit.org/show_bug.cgi?id=136851
// In-page `selector#id sibing-combinator selector` fails
if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
rbuggyQSA.push(".#.+[+~]");
}
});
assert(function( div ) {
// Support: Windows 8 Native Apps
// The type and name attributes are restricted during .innerHTML assignment
var input = doc.createElement("input");
input.setAttribute( "type", "hidden" );
div.appendChild( input ).setAttribute( "name", "D" );
// Support: IE8
// Enforce case-sensitivity of name attribute
if ( div.querySelectorAll("[name=d]").length ) {
rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
}
// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
// IE8 throws error here and will not see later tests
if ( !div.querySelectorAll(":enabled").length ) {
rbuggyQSA.push( ":enabled", ":disabled" );
}
// Opera 10-11 does not throw on post-comma invalid pseudos
div.querySelectorAll("*,:x");
rbuggyQSA.push(",.*:");
});
}
if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
docElem.webkitMatchesSelector ||
docElem.mozMatchesSelector ||
docElem.oMatchesSelector ||
docElem.msMatchesSelector) )) ) {
assert(function( div ) {
// Check to see if it's possible to do matchesSelector
// on a disconnected node (IE 9)
support.disconnectedMatch = matches.call( div, "div" );
// This should fail with an exception
// Gecko does not error, returns false instead
matches.call( div, "[s!='']:x" );
rbuggyMatches.push( "!=", pseudos );
});
}
rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
/* Contains
---------------------------------------------------------------------- */
hasCompare = rnative.test( docElem.compareDocumentPosition );
// Element contains another
// Purposefully does not implement inclusive descendent
// As in, an element does not contain itself
contains = hasCompare || rnative.test( docElem.contains ) ?
function( a, b ) {
var adown = a.nodeType === 9 ? a.documentElement : a,
bup = b && b.parentNode;
return a === bup || !!( bup && bup.nodeType === 1 && (
adown.contains ?
adown.contains( bup ) :
a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
));
} :
function( a, b ) {
if ( b ) {
while ( (b = b.parentNode) ) {
if ( b === a ) {
return true;
}
}
}
return false;
};
/* Sorting
---------------------------------------------------------------------- */
// Document order sorting
sortOrder = hasCompare ?
function( a, b ) {
// Flag for duplicate removal
if ( a === b ) {
hasDuplicate = true;
return 0;
}
// Sort on method existence if only one input has compareDocumentPosition
var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
if ( compare ) {
return compare;
}
// Calculate position if both inputs belong to the same document
compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
a.compareDocumentPosition( b ) :
// Otherwise we know they are disconnected
1;
// Disconnected nodes
if ( compare & 1 ||
(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
// Choose the first element that is related to our preferred document
if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
return -1;
}
if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
return 1;
}
// Maintain original order
return sortInput ?
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
0;
}
return compare & 4 ? -1 : 1;
} :
function( a, b ) {
// Exit early if the nodes are identical
if ( a === b ) {
hasDuplicate = true;
return 0;
}
var cur,
i = 0,
aup = a.parentNode,
bup = b.parentNode,
ap = [ a ],
bp = [ b ];
// Parentless nodes are either documents or disconnected
if ( !aup || !bup ) {
return a === doc ? -1 :
b === doc ? 1 :
aup ? -1 :
bup ? 1 :
sortInput ?
( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
0;
// If the nodes are siblings, we can do a quick check
} else if ( aup === bup ) {
return siblingCheck( a, b );
}
// Otherwise we need full lists of their ancestors for comparison
cur = a;
while ( (cur = cur.parentNode) ) {
ap.unshift( cur );
}
cur = b;
while ( (cur = cur.parentNode) ) {
bp.unshift( cur );
}
// Walk down the tree looking for a discrepancy
while ( ap[i] === bp[i] ) {
i++;
}
return i ?
// Do a sibling check if the nodes have a common ancestor
siblingCheck( ap[i], bp[i] ) :
// Otherwise nodes in our document sort first
ap[i] === preferredDoc ? -1 :
bp[i] === preferredDoc ? 1 :
0;
};
return doc;
};
Sizzle.matches = function( expr, elements ) {
return Sizzle( expr, null, null, elements );
};
Sizzle.matchesSelector = function( elem, expr ) {
// Set document vars if needed
if ( ( elem.ownerDocument || elem ) !== document ) {
setDocument( elem );
}
// Make sure that attribute selectors are quoted
expr = expr.replace( rattributeQuotes, "='$1']" );
if ( support.matchesSelector && documentIsHTML &&
( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {
try {
var ret = matches.call( elem, expr );
// IE 9's matchesSelector returns false on disconnected nodes
if ( ret || support.disconnectedMatch ||
// As well, disconnected nodes are said to be in a document
// fragment in IE 9
elem.document && elem.document.nodeType !== 11 ) {
return ret;
}
} catch (e) {}
}
return Sizzle( expr, document, null, [ elem ] ).length > 0;
};
Sizzle.contains = function( context, elem ) {
// Set document vars if needed
if ( ( context.ownerDocument || context ) !== document ) {
setDocument( context );
}
return contains( context, elem );
};
Sizzle.attr = function( elem, name ) {
// Set document vars if needed
if ( ( elem.ownerDocument || elem ) !== document ) {
setDocument( elem );
}
var fn = Expr.attrHandle[ name.toLowerCase() ],
// Don't get fooled by Object.prototype properties (jQuery #13807)
val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
fn( elem, name, !documentIsHTML ) :
undefined;
return val !== undefined ?
val :
support.attributes || !documentIsHTML ?
elem.getAttribute( name ) :
(val = elem.getAttributeNode(name)) && val.specified ?
val.value :
null;
};
Sizzle.error = function( msg ) {
throw new Error( "Syntax error, unrecognized expression: " + msg );
};
/**
* Document sorting and removing duplicates
* @param {ArrayLike} results
*/
Sizzle.uniqueSort = function( results ) {
var elem,
duplicates = [],
j = 0,
i = 0;
// Unless we *know* we can detect duplicates, assume their presence
hasDuplicate = !support.detectDuplicates;
sortInput = !support.sortStable && results.slice( 0 );
results.sort( sortOrder );
if ( hasDuplicate ) {
while ( (elem = results[i++]) ) {
if ( elem === results[ i ] ) {
j = duplicates.push( i );
}
}
while ( j-- ) {
results.splice( duplicates[ j ], 1 );
}
}
// Clear input after sorting to release objects
// See https://github.com/jquery/sizzle/pull/225
sortInput = null;
return results;
};
/**
* Utility function for retrieving the text value of an array of DOM nodes
* @param {Array|Element} elem
*/
getText = Sizzle.getText = function( elem ) {
var node,
ret = "",
i = 0,
nodeType = elem.nodeType;
if ( !nodeType ) {
// If no nodeType, this is expected to be an array
while ( (node = elem[i++]) ) {
// Do not traverse comment nodes
ret += getText( node );
}
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// Use textContent for elements
// innerText usage removed for consistency of new lines (jQuery #11153)
if ( typeof elem.textContent === "string" ) {
return elem.textContent;
} else {
// Traverse its children
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
ret += getText( elem );
}
}
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue;
}
// Do not include comment or processing instruction nodes
return ret;
};
Expr = Sizzle.selectors = {
// Can be adjusted by the user
cacheLength: 50,
createPseudo: markFunction,
match: matchExpr,
attrHandle: {},
find: {},
relative: {
">": { dir: "parentNode", first: true },
" ": { dir: "parentNode" },
"+": { dir: "previousSibling", first: true },
"~": { dir: "previousSibling" }
},
preFilter: {
"ATTR": function( match ) {
match[1] = match[1].replace( runescape, funescape );
// Move the given value to match[3] whether quoted or unquoted
match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
if ( match[2] === "~=" ) {
match[3] = " " + match[3] + " ";
}
return match.slice( 0, 4 );
},
"CHILD": function( match ) {
/* matches from matchExpr["CHILD"]
1 type (only|nth|...)
2 what (child|of-type)
3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
4 xn-component of xn+y argument ([+-]?\d*n|)
5 sign of xn-component
6 x of xn-component
7 sign of y-component
8 y of y-component
*/
match[1] = match[1].toLowerCase();
if ( match[1].slice( 0, 3 ) === "nth" ) {
// nth-* requires argument
if ( !match[3] ) {
Sizzle.error( match[0] );
}
// numeric x and y parameters for Expr.filter.CHILD
// remember that false/true cast respectively to 0/1
match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
// other types prohibit arguments
} else if ( match[3] ) {
Sizzle.error( match[0] );
}
return match;
},
"PSEUDO": function( match ) {
var excess,
unquoted = !match[6] && match[2];
if ( matchExpr["CHILD"].test( match[0] ) ) {
return null;
}
// Accept quoted arguments as-is
if ( match[3] ) {
match[2] = match[4] || match[5] || "";
// Strip excess characters from unquoted arguments
} else if ( unquoted && rpseudo.test( unquoted ) &&
// Get excess from tokenize (recursively)
(excess = tokenize( unquoted, true )) &&
// advance to the next closing parenthesis
(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
// excess is a negative index
match[0] = match[0].slice( 0, excess );
match[2] = unquoted.slice( 0, excess );
}
// Return only captures needed by the pseudo filter method (type and argument)
return match.slice( 0, 3 );
}
},
filter: {
"TAG": function( nodeNameSelector ) {
var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
return nodeNameSelector === "*" ?
function() { return true; } :
function( elem ) {
return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
};
},
"CLASS": function( className ) {
var pattern = classCache[ className + " " ];
return pattern ||
(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
classCache( className, function( elem ) {
return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
});
},
"ATTR": function( name, operator, check ) {
return function( elem ) {
var result = Sizzle.attr( elem, name );
if ( result == null ) {
return operator === "!=";
}
if ( !operator ) {
return true;
}
result += "";
return operator === "=" ? result === check :
operator === "!=" ? result !== check :
operator === "^=" ? check && result.indexOf( check ) === 0 :
operator === "*=" ? check && result.indexOf( check ) > -1 :
operator === "$=" ? check && result.slice( -check.length ) === check :
operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
false;
};
},
"CHILD": function( type, what, argument, first, last ) {
var simple = type.slice( 0, 3 ) !== "nth",
forward = type.slice( -4 ) !== "last",
ofType = what === "of-type";
return first === 1 && last === 0 ?
// Shortcut for :nth-*(n)
function( elem ) {
return !!elem.parentNode;
} :
function( elem, context, xml ) {
var cache, outerCache, node, diff, nodeIndex, start,
dir = simple !== forward ? "nextSibling" : "previousSibling",
parent = elem.parentNode,
name = ofType && elem.nodeName.toLowerCase(),
useCache = !xml && !ofType;
if ( parent ) {
// :(first|last|only)-(child|of-type)
if ( simple ) {
while ( dir ) {
node = elem;
while ( (node = node[ dir ]) ) {
if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
return false;
}
}
// Reverse direction for :only-* (if we haven't yet done so)
start = dir = type === "only" && !start && "nextSibling";
}
return true;
}
start = [ forward ? parent.firstChild : parent.lastChild ];
// non-xml :nth-child(...) stores cache data on `parent`
if ( forward && useCache ) {
// Seek `elem` from a previously-cached index
outerCache = parent[ expando ] || (parent[ expando ] = {});
cache = outerCache[ type ] || [];
nodeIndex = cache[0] === dirruns && cache[1];
diff = cache[0] === dirruns && cache[2];
node = nodeIndex && parent.childNodes[ nodeIndex ];
while ( (node = ++nodeIndex && node && node[ dir ] ||
// Fallback to seeking `elem` from the start
(diff = nodeIndex = 0) || start.pop()) ) {
// When found, cache indexes on `parent` and break
if ( node.nodeType === 1 && ++diff && node === elem ) {
outerCache[ type ] = [ dirruns, nodeIndex, diff ];
break;
}
}
// Use previously-cached element index if available
} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
diff = cache[1];
// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
} else {
// Use the same loop as above to seek `elem` from the start
while ( (node = ++nodeIndex && node && node[ dir ] ||
(diff = nodeIndex = 0) || start.pop()) ) {
if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
// Cache the index of each encountered element
if ( useCache ) {
(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
}
if ( node === elem ) {
break;
}
}
}
}
// Incorporate the offset, then check against cycle size
diff -= last;
return diff === first || ( diff % first === 0 && diff / first >= 0 );
}
};
},
"PSEUDO": function( pseudo, argument ) {
// pseudo-class names are case-insensitive
// http://www.w3.org/TR/selectors/#pseudo-classes
// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
// Remember that setFilters inherits from pseudos
var args,
fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
Sizzle.error( "unsupported pseudo: " + pseudo );
// The user may use createPseudo to indicate that
// arguments are needed to create the filter function
// just as Sizzle does
if ( fn[ expando ] ) {
return fn( argument );
}
// But maintain support for old signatures
if ( fn.length > 1 ) {
args = [ pseudo, pseudo, "", argument ];
return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
markFunction(function( seed, matches ) {
var idx,
matched = fn( seed, argument ),
i = matched.length;
while ( i-- ) {
idx = indexOf( seed, matched[i] );
seed[ idx ] = !( matches[ idx ] = matched[i] );
}
}) :
function( elem ) {
return fn( elem, 0, args );
};
}
return fn;
}
},
pseudos: {
// Potentially complex pseudos
"not": markFunction(function( selector ) {
// Trim the selector passed to compile
// to avoid treating leading and trailing
// spaces as combinators
var input = [],
results = [],
matcher = compile( selector.replace( rtrim, "$1" ) );
return matcher[ expando ] ?
markFunction(function( seed, matches, context, xml ) {
var elem,
unmatched = matcher( seed, null, xml, [] ),
i = seed.length;
// Match elements unmatched by `matcher`
while ( i-- ) {
if ( (elem = unmatched[i]) ) {
seed[i] = !(matches[i] = elem);
}
}
}) :
function( elem, context, xml ) {
input[0] = elem;
matcher( input, null, xml, results );
// Don't keep the element (issue #299)
input[0] = null;
return !results.pop();
};
}),
"has": markFunction(function( selector ) {
return function( elem ) {
return Sizzle( selector, elem ).length > 0;
};
}),
"contains": markFunction(function( text ) {
text = text.replace( runescape, funescape );
return function( elem ) {
return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
};
}),
// "Whether an element is represented by a :lang() selector
// is based solely on the element's language value
// being equal to the identifier C,
// or beginning with the identifier C immediately followed by "-".
// The matching of C against the element's language value is performed case-insensitively.
// The identifier C does not have to be a valid language name."
// http://www.w3.org/TR/selectors/#lang-pseudo
"lang": markFunction( function( lang ) {
// lang value must be a valid identifier
if ( !ridentifier.test(lang || "") ) {
Sizzle.error( "unsupported lang: " + lang );
}
lang = lang.replace( runescape, funescape ).toLowerCase();
return function( elem ) {
var elemLang;
do {
if ( (elemLang = documentIsHTML ?
elem.lang :
elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
elemLang = elemLang.toLowerCase();
return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
}
} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
return false;
};
}),
// Miscellaneous
"target": function( elem ) {
var hash = window.location && window.location.hash;
return hash && hash.slice( 1 ) === elem.id;
},
"root": function( elem ) {
return elem === docElem;
},
"focus": function( elem ) {
return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
},
// Boolean properties
"enabled": function( elem ) {
return elem.disabled === false;
},
"disabled": function( elem ) {
return elem.disabled === true;
},
"checked": function( elem ) {
// In CSS3, :checked should return both checked and selected elements
// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
var nodeName = elem.nodeName.toLowerCase();
return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
},
"selected": function( elem ) {
// Accessing this property makes selected-by-default
// options in Safari work properly
if ( elem.parentNode ) {
elem.parentNode.selectedIndex;
}
return elem.selected === true;
},
// Contents
"empty": function( elem ) {
// http://www.w3.org/TR/selectors/#empty-pseudo
// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
// but not by others (comment: 8; processing instruction: 7; etc.)
// nodeType < 6 works because attributes (2) do not appear as children
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
if ( elem.nodeType < 6 ) {
return false;
}
}
return true;
},
"parent": function( elem ) {
return !Expr.pseudos["empty"]( elem );
},
// Element/input types
"header": function( elem ) {
return rheader.test( elem.nodeName );
},
"input": function( elem ) {
return rinputs.test( elem.nodeName );
},
"button": function( elem ) {
var name = elem.nodeName.toLowerCase();
return name === "input" && elem.type === "button" || name === "button";
},
"text": function( elem ) {
var attr;
return elem.nodeName.toLowerCase() === "input" &&
elem.type === "text" &&
// Support: IE<8
// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
},
// Position-in-collection
"first": createPositionalPseudo(function() {
return [ 0 ];
}),
"last": createPositionalPseudo(function( matchIndexes, length ) {
return [ length - 1 ];
}),
"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
return [ argument < 0 ? argument + length : argument ];
}),
"even": createPositionalPseudo(function( matchIndexes, length ) {
var i = 0;
for ( ; i < length; i += 2 ) {
matchIndexes.push( i );
}
return matchIndexes;
}),
"odd": createPositionalPseudo(function( matchIndexes, length ) {
var i = 1;
for ( ; i < length; i += 2 ) {
matchIndexes.push( i );
}
return matchIndexes;
}),
"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
var i = argument < 0 ? argument + length : argument;
for ( ; --i >= 0; ) {
matchIndexes.push( i );
}
return matchIndexes;
}),
"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
var i = argument < 0 ? argument + length : argument;
for ( ; ++i < length; ) {
matchIndexes.push( i );
}
return matchIndexes;
})
}
};
Expr.pseudos["nth"] = Expr.pseudos["eq"];
// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
Expr.pseudos[ i ] = createButtonPseudo( i );
}
// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();
tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
var matched, match, tokens, type,
soFar, groups, preFilters,
cached = tokenCache[ selector + " " ];
if ( cached ) {
return parseOnly ? 0 : cached.slice( 0 );
}
soFar = selector;
groups = [];
preFilters = Expr.preFilter;
while ( soFar ) {
// Comma and first run
if ( !matched || (match = rcomma.exec( soFar )) ) {
if ( match ) {
// Don't consume trailing commas as valid
soFar = soFar.slice( match[0].length ) || soFar;
}
groups.push( (tokens = []) );
}
matched = false;
// Combinators
if ( (match = rcombinators.exec( soFar )) ) {
matched = match.shift();
tokens.push({
value: matched,
// Cast descendant combinators to space
type: match[0].replace( rtrim, " " )
});
soFar = soFar.slice( matched.length );
}
// Filters
for ( type in Expr.filter ) {
if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
(match = preFilters[ type ]( match ))) ) {
matched = match.shift();
tokens.push({
value: matched,
type: type,
matches: match
});
soFar = soFar.slice( matched.length );
}
}
if ( !matched ) {
break;
}
}
// Return the length of the invalid excess
// if we're just parsing
// Otherwise, throw an error or return tokens
return parseOnly ?
soFar.length :
soFar ?
Sizzle.error( selector ) :
// Cache the tokens
tokenCache( selector, groups ).slice( 0 );
};
function toSelector( tokens ) {
var i = 0,
len = tokens.length,
selector = "";
for ( ; i < len; i++ ) {
selector += tokens[i].value;
}
return selector;
}
function addCombinator( matcher, combinator, base ) {
var dir = combinator.dir,
checkNonElements = base && dir === "parentNode",
doneName = done++;
return combinator.first ?
// Check against closest ancestor/preceding element
function( elem, context, xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
return matcher( elem, context, xml );
}
}
} :
// Check against all ancestor/preceding elements
function( elem, context, xml ) {
var oldCache, outerCache,
newCache = [ dirruns, doneName ];
// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
if ( xml ) {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
if ( matcher( elem, context, xml ) ) {
return true;
}
}
}
} else {
while ( (elem = elem[ dir ]) ) {
if ( elem.nodeType === 1 || checkNonElements ) {
outerCache = elem[ expando ] || (elem[ expando ] = {});
if ( (oldCache = outerCache[ dir ]) &&
oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
// Assign to newCache so results back-propagate to previous elements
return (newCache[ 2 ] = oldCache[ 2 ]);
} else {
// Reuse newcache so results back-propagate to previous elements
outerCache[ dir ] = newCache;
// A match means we're done; a fail means we have to keep checking
if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
return true;
}
}
}
}
}
};
}
function elementMatcher( matchers ) {
return matchers.length > 1 ?
function( elem, context, xml ) {
var i = matchers.length;
while ( i-- ) {
if ( !matchers[i]( elem, context, xml ) ) {
return false;
}
}
return true;
} :
matchers[0];
}
function multipleContexts( selector, contexts, results ) {
var i = 0,
len = contexts.length;
for ( ; i < len; i++ ) {
Sizzle( selector, contexts[i], results );
}
return results;
}
function condense( unmatched, map, filter, context, xml ) {
var elem,
newUnmatched = [],
i = 0,
len = unmatched.length,
mapped = map != null;
for ( ; i < len; i++ ) {
if ( (elem = unmatched[i]) ) {
if ( !filter || filter( elem, context, xml ) ) {
newUnmatched.push( elem );
if ( mapped ) {
map.push( i );
}
}
}
}
return newUnmatched;
}
function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
if ( postFilter && !postFilter[ expando ] ) {
postFilter = setMatcher( postFilter );
}
if ( postFinder && !postFinder[ expando ] ) {
postFinder = setMatcher( postFinder, postSelector );
}
return markFunction(function( seed, results, context, xml ) {
var temp, i, elem,
preMap = [],
postMap = [],
preexisting = results.length,
// Get initial elements from seed or context
elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
// Prefilter to get matcher input, preserving a map for seed-results synchronization
matcherIn = preFilter && ( seed || !selector ) ?
condense( elems, preMap, preFilter, context, xml ) :
elems,
matcherOut = matcher ?
// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
// ...intermediate processing is necessary
[] :
// ...otherwise use results directly
results :
matcherIn;
// Find primary matches
if ( matcher ) {
matcher( matcherIn, matcherOut, context, xml );
}
// Apply postFilter
if ( postFilter ) {
temp = condense( matcherOut, postMap );
postFilter( temp, [], context, xml );
// Un-match failing elements by moving them back to matcherIn
i = temp.length;
while ( i-- ) {
if ( (elem = temp[i]) ) {
matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
}
}
}
if ( seed ) {
if ( postFinder || preFilter ) {
if ( postFinder ) {
// Get the final matcherOut by condensing this intermediate into postFinder contexts
temp = [];
i = matcherOut.length;
while ( i-- ) {
if ( (elem = matcherOut[i]) ) {
// Restore matcherIn since elem is not yet a final match
temp.push( (matcherIn[i] = elem) );
}
}
postFinder( null, (matcherOut = []), temp, xml );
}
// Move matched elements from seed to results to keep them synchronized
i = matcherOut.length;
while ( i-- ) {
if ( (elem = matcherOut[i]) &&
(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
seed[temp] = !(results[temp] = elem);
}
}
}
// Add elements to results, through postFinder if defined
} else {
matcherOut = condense(
matcherOut === results ?
matcherOut.splice( preexisting, matcherOut.length ) :
matcherOut
);
if ( postFinder ) {
postFinder( null, results, matcherOut, xml );
} else {
push.apply( results, matcherOut );
}
}
});
}
function matcherFromTokens( tokens ) {
var checkContext, matcher, j,
len = tokens.length,
leadingRelative = Expr.relative[ tokens[0].type ],
implicitRelative = leadingRelative || Expr.relative[" "],
i = leadingRelative ? 1 : 0,
// The foundational matcher ensures that elements are reachable from top-level context(s)
matchContext = addCombinator( function( elem ) {
return elem === checkContext;
}, implicitRelative, true ),
matchAnyContext = addCombinator( function( elem ) {
return indexOf( checkContext, elem ) > -1;
}, implicitRelative, true ),
matchers = [ function( elem, context, xml ) {
var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
(checkContext = context).nodeType ?
matchContext( elem, context, xml ) :
matchAnyContext( elem, context, xml ) );
// Avoid hanging onto element (issue #299)
checkContext = null;
return ret;
} ];
for ( ; i < len; i++ ) {
if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
} else {
matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
// Return special upon seeing a positional matcher
if ( matcher[ expando ] ) {
// Find the next relative operator (if any) for proper handling
j = ++i;
for ( ; j < len; j++ ) {
if ( Expr.relative[ tokens[j].type ] ) {
break;
}
}
return setMatcher(
i > 1 && elementMatcher( matchers ),
i > 1 && toSelector(
// If the preceding token was a descendant combinator, insert an implicit any-element `*`
tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
).replace( rtrim, "$1" ),
matcher,
i < j && matcherFromTokens( tokens.slice( i, j ) ),
j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
j < len && toSelector( tokens )
);
}
matchers.push( matcher );
}
}
return elementMatcher( matchers );
}
function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
var bySet = setMatchers.length > 0,
byElement = elementMatchers.length > 0,
superMatcher = function( seed, context, xml, results, outermost ) {
var elem, j, matcher,
matchedCount = 0,
i = "0",
unmatched = seed && [],
setMatched = [],
contextBackup = outermostContext,
// We must always have either seed elements or outermost context
elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
// Use integer dirruns iff this is the outermost matcher
dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
len = elems.length;
if ( outermost ) {
outermostContext = context !== document && context;
}
// Add elements passing elementMatchers directly to results
// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
// Support: IE<9, Safari
// Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id
for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
if ( byElement && elem ) {
j = 0;
while ( (matcher = elementMatchers[j++]) ) {
if ( matcher( elem, context, xml ) ) {
results.push( elem );
break;
}
}
if ( outermost ) {
dirruns = dirrunsUnique;
}
}
// Track unmatched elements for set filters
if ( bySet ) {
// They will have gone through all possible matchers
if ( (elem = !matcher && elem) ) {
matchedCount--;
}
// Lengthen the array for every element, matched or not
if ( seed ) {
unmatched.push( elem );
}
}
}
// Apply set filters to unmatched elements
matchedCount += i;
if ( bySet && i !== matchedCount ) {
j = 0;
while ( (matcher = setMatchers[j++]) ) {
matcher( unmatched, setMatched, context, xml );
}
if ( seed ) {
// Reintegrate element matches to eliminate the need for sorting
if ( matchedCount > 0 ) {
while ( i-- ) {
if ( !(unmatched[i] || setMatched[i]) ) {
setMatched[i] = pop.call( results );
}
}
}
// Discard index placeholder values to get only actual matches
setMatched = condense( setMatched );
}
// Add matches to results
push.apply( results, setMatched );
// Seedless set matches succeeding multiple successful matchers stipulate sorting
if ( outermost && !seed && setMatched.length > 0 &&
( matchedCount + setMatchers.length ) > 1 ) {
Sizzle.uniqueSort( results );
}
}
// Override manipulation of globals by nested matchers
if ( outermost ) {
dirruns = dirrunsUnique;
outermostContext = contextBackup;
}
return unmatched;
};
return bySet ?
markFunction( superMatcher ) :
superMatcher;
}
compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
var i,
setMatchers = [],
elementMatchers = [],
cached = compilerCache[ selector + " " ];
if ( !cached ) {
// Generate a function of recursive functions that can be used to check each element
if ( !match ) {
match = tokenize( selector );
}
i = match.length;
while ( i-- ) {
cached = matcherFromTokens( match[i] );
if ( cached[ expando ] ) {
setMatchers.push( cached );
} else {
elementMatchers.push( cached );
}
}
// Cache the compiled function
cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
// Save selector and tokenization
cached.selector = selector;
}
return cached;
};
/**
* A low-level selection function that works with Sizzle's compiled
* selector functions
* @param {String|Function} selector A selector or a pre-compiled
* selector function built with Sizzle.compile
* @param {Element} context
* @param {Array} [results]
* @param {Array} [seed] A set of elements to match against
*/
select = Sizzle.select = function( selector, context, results, seed ) {
var i, tokens, token, type, find,
compiled = typeof selector === "function" && selector,
match = !seed && tokenize( (selector = compiled.selector || selector) );
results = results || [];
// Try to minimize operations if there is no seed and only one group
if ( match.length === 1 ) {
// Take a shortcut and set the context if the root selector is an ID
tokens = match[0] = match[0].slice( 0 );
if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
support.getById && context.nodeType === 9 && documentIsHTML &&
Expr.relative[ tokens[1].type ] ) {
context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
if ( !context ) {
return results;
// Precompiled matchers will still verify ancestry, so step up a level
} else if ( compiled ) {
context = context.parentNode;
}
selector = selector.slice( tokens.shift().value.length );
}
// Fetch a seed set for right-to-left matching
i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
while ( i-- ) {
token = tokens[i];
// Abort if we hit a combinator
if ( Expr.relative[ (type = token.type) ] ) {
break;
}
if ( (find = Expr.find[ type ]) ) {
// Search, expanding context for leading sibling combinators
if ( (seed = find(
token.matches[0].replace( runescape, funescape ),
rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
)) ) {
// If seed is empty or no tokens remain, we can return early
tokens.splice( i, 1 );
selector = seed.length && toSelector( tokens );
if ( !selector ) {
push.apply( results, seed );
return results;
}
break;
}
}
}
}
// Compile and execute a filtering function if one is not provided
// Provide `match` to avoid retokenization if we modified the selector above
( compiled || compile( selector, match ) )(
seed,
context,
!documentIsHTML,
results,
rsibling.test( selector ) && testContext( context.parentNode ) || context
);
return results;
};
// One-time assignments
// Sort stability
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
// Support: Chrome 14-35+
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = !!hasDuplicate;
// Initialize against the default document
setDocument();
// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
support.sortDetached = assert(function( div1 ) {
// Should return 1, but returns 4 (following)
return div1.compareDocumentPosition( document.createElement("div") ) & 1;
});
// Support: IE<8
// Prevent attribute/property "interpolation"
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert(function( div ) {
div.innerHTML = "";
return div.firstChild.getAttribute("href") === "#" ;
}) ) {
addHandle( "type|href|height|width", function( elem, name, isXML ) {
if ( !isXML ) {
return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
}
});
}
// Support: IE<9
// Use defaultValue in place of getAttribute("value")
if ( !support.attributes || !assert(function( div ) {
div.innerHTML = "";
div.firstChild.setAttribute( "value", "" );
return div.firstChild.getAttribute( "value" ) === "";
}) ) {
addHandle( "value", function( elem, name, isXML ) {
if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
return elem.defaultValue;
}
});
}
// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
if ( !assert(function( div ) {
return div.getAttribute("disabled") == null;
}) ) {
addHandle( booleans, function( elem, name, isXML ) {
var val;
if ( !isXML ) {
return elem[ name ] === true ? name.toLowerCase() :
(val = elem.getAttributeNode( name )) && val.specified ?
val.value :
null;
}
});
}
return Sizzle;
})( window );
jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.pseudos;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;
var rneedsContext = jQuery.expr.match.needsContext;
var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
var risSimple = /^.[^:#\[\.,]*$/;
// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
if ( jQuery.isFunction( qualifier ) ) {
return jQuery.grep( elements, function( elem, i ) {
/* jshint -W018 */
return !!qualifier.call( elem, i, elem ) !== not;
});
}
if ( qualifier.nodeType ) {
return jQuery.grep( elements, function( elem ) {
return ( elem === qualifier ) !== not;
});
}
if ( typeof qualifier === "string" ) {
if ( risSimple.test( qualifier ) ) {
return jQuery.filter( qualifier, elements, not );
}
qualifier = jQuery.filter( qualifier, elements );
}
return jQuery.grep( elements, function( elem ) {
return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;
});
}
jQuery.filter = function( expr, elems, not ) {
var elem = elems[ 0 ];
if ( not ) {
expr = ":not(" + expr + ")";
}
return elems.length === 1 && elem.nodeType === 1 ?
jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
return elem.nodeType === 1;
}));
};
jQuery.fn.extend({
find: function( selector ) {
var i,
ret = [],
self = this,
len = self.length;
if ( typeof selector !== "string" ) {
return this.pushStack( jQuery( selector ).filter(function() {
for ( i = 0; i < len; i++ ) {
if ( jQuery.contains( self[ i ], this ) ) {
return true;
}
}
}) );
}
for ( i = 0; i < len; i++ ) {
jQuery.find( selector, self[ i ], ret );
}
// Needed because $( selector, context ) becomes $( context ).find( selector )
ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
ret.selector = this.selector ? this.selector + " " + selector : selector;
return ret;
},
filter: function( selector ) {
return this.pushStack( winnow(this, selector || [], false) );
},
not: function( selector ) {
return this.pushStack( winnow(this, selector || [], true) );
},
is: function( selector ) {
return !!winnow(
this,
// If this is a positional/relative selector, check membership in the returned set
// so $("p:first").is("p:last") won't return true for a doc with two "p".
typeof selector === "string" && rneedsContext.test( selector ) ?
jQuery( selector ) :
selector || [],
false
).length;
}
});
// Initialize a jQuery object
// A central reference to the root jQuery(document)
var rootjQuery,
// Use the correct document accordingly with window argument (sandbox)
document = window.document,
// A simple way to check for HTML strings
// Prioritize #id over to avoid XSS via location.hash (#9521)
// Strict HTML recognition (#11290: must start with <)
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
init = jQuery.fn.init = function( selector, context ) {
var match, elem;
// HANDLE: $(""), $(null), $(undefined), $(false)
if ( !selector ) {
return this;
}
// Handle HTML strings
if ( typeof selector === "string" ) {
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [ null, selector, null ];
} else {
match = rquickExpr.exec( selector );
}
// Match html or make sure no context is specified for #id
if ( match && (match[1] || !context) ) {
// HANDLE: $(html) -> $(array)
if ( match[1] ) {
context = context instanceof jQuery ? context[0] : context;
// scripts is true for back-compat
// Intentionally let the error be thrown if parseHTML is not present
jQuery.merge( this, jQuery.parseHTML(
match[1],
context && context.nodeType ? context.ownerDocument || context : document,
true
) );
// HANDLE: $(html, props)
if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
for ( match in context ) {
// Properties of context are called as methods if possible
if ( jQuery.isFunction( this[ match ] ) ) {
this[ match ]( context[ match ] );
// ...and otherwise set as attributes
} else {
this.attr( match, context[ match ] );
}
}
}
return this;
// HANDLE: $(#id)
} else {
elem = document.getElementById( match[2] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id !== match[2] ) {
return rootjQuery.find( selector );
}
// Otherwise, we inject the element directly into the jQuery object
this.length = 1;
this[0] = elem;
}
this.context = document;
this.selector = selector;
return this;
}
// HANDLE: $(expr, $(...))
} else if ( !context || context.jquery ) {
return ( context || rootjQuery ).find( selector );
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
// HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
this.context = this[0] = selector;
this.length = 1;
return this;
// HANDLE: $(function)
// Shortcut for document ready
} else if ( jQuery.isFunction( selector ) ) {
return typeof rootjQuery.ready !== "undefined" ?
rootjQuery.ready( selector ) :
// Execute immediately if ready is not present
selector( jQuery );
}
if ( selector.selector !== undefined ) {
this.selector = selector.selector;
this.context = selector.context;
}
return jQuery.makeArray( selector, this );
};
// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;
// Initialize central reference
rootjQuery = jQuery( document );
var rparentsprev = /^(?:parents|prev(?:Until|All))/,
// methods guaranteed to produce a unique set when starting from a unique set
guaranteedUnique = {
children: true,
contents: true,
next: true,
prev: true
};
jQuery.extend({
dir: function( elem, dir, until ) {
var matched = [],
cur = elem[ dir ];
while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
if ( cur.nodeType === 1 ) {
matched.push( cur );
}
cur = cur[dir];
}
return matched;
},
sibling: function( n, elem ) {
var r = [];
for ( ; n; n = n.nextSibling ) {
if ( n.nodeType === 1 && n !== elem ) {
r.push( n );
}
}
return r;
}
});
jQuery.fn.extend({
has: function( target ) {
var i,
targets = jQuery( target, this ),
len = targets.length;
return this.filter(function() {
for ( i = 0; i < len; i++ ) {
if ( jQuery.contains( this, targets[i] ) ) {
return true;
}
}
});
},
closest: function( selectors, context ) {
var cur,
i = 0,
l = this.length,
matched = [],
pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
jQuery( selectors, context || this.context ) :
0;
for ( ; i < l; i++ ) {
for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
// Always skip document fragments
if ( cur.nodeType < 11 && (pos ?
pos.index(cur) > -1 :
// Don't pass non-elements to Sizzle
cur.nodeType === 1 &&
jQuery.find.matchesSelector(cur, selectors)) ) {
matched.push( cur );
break;
}
}
}
return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
},
// Determine the position of an element within
// the matched set of elements
index: function( elem ) {
// No argument, return index in parent
if ( !elem ) {
return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;
}
// index in selector
if ( typeof elem === "string" ) {
return jQuery.inArray( this[0], jQuery( elem ) );
}
// Locate the position of the desired element
return jQuery.inArray(
// If it receives a jQuery object, the first element is used
elem.jquery ? elem[0] : elem, this );
},
add: function( selector, context ) {
return this.pushStack(
jQuery.unique(
jQuery.merge( this.get(), jQuery( selector, context ) )
)
);
},
addBack: function( selector ) {
return this.add( selector == null ?
this.prevObject : this.prevObject.filter(selector)
);
}
});
function sibling( cur, dir ) {
do {
cur = cur[ dir ];
} while ( cur && cur.nodeType !== 1 );
return cur;
}
jQuery.each({
parent: function( elem ) {
var parent = elem.parentNode;
return parent && parent.nodeType !== 11 ? parent : null;
},
parents: function( elem ) {
return jQuery.dir( elem, "parentNode" );
},
parentsUntil: function( elem, i, until ) {
return jQuery.dir( elem, "parentNode", until );
},
next: function( elem ) {
return sibling( elem, "nextSibling" );
},
prev: function( elem ) {
return sibling( elem, "previousSibling" );
},
nextAll: function( elem ) {
return jQuery.dir( elem, "nextSibling" );
},
prevAll: function( elem ) {
return jQuery.dir( elem, "previousSibling" );
},
nextUntil: function( elem, i, until ) {
return jQuery.dir( elem, "nextSibling", until );
},
prevUntil: function( elem, i, until ) {
return jQuery.dir( elem, "previousSibling", until );
},
siblings: function( elem ) {
return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
},
children: function( elem ) {
return jQuery.sibling( elem.firstChild );
},
contents: function( elem ) {
return jQuery.nodeName( elem, "iframe" ) ?
elem.contentDocument || elem.contentWindow.document :
jQuery.merge( [], elem.childNodes );
}
}, function( name, fn ) {
jQuery.fn[ name ] = function( until, selector ) {
var ret = jQuery.map( this, fn, until );
if ( name.slice( -5 ) !== "Until" ) {
selector = until;
}
if ( selector && typeof selector === "string" ) {
ret = jQuery.filter( selector, ret );
}
if ( this.length > 1 ) {
// Remove duplicates
if ( !guaranteedUnique[ name ] ) {
ret = jQuery.unique( ret );
}
// Reverse order for parents* and prev-derivatives
if ( rparentsprev.test( name ) ) {
ret = ret.reverse();
}
}
return this.pushStack( ret );
};
});
var rnotwhite = (/\S+/g);
// String to Object options format cache
var optionsCache = {};
// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true;
});
return object;
}
/*
* Create a callback list using the following parameters:
*
* options: an optional list of space-separated options that will change how
* the callback list behaves or a more traditional option object
*
* By default a callback list will act like an event callback list and can be
* "fired" multiple times.
*
* Possible options:
*
* once: will ensure the callback list can only be fired once (like a Deferred)
*
* memory: will keep track of previous values and will call any callback added
* after the list has been fired right away with the latest "memorized"
* values (like a Deferred)
*
* unique: will ensure a callback can only be added once (no duplicate in the list)
*
* stopOnFalse: interrupt callings when a callback returns false
*
*/
jQuery.Callbacks = function( options ) {
// Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );
var // Flag to know if list is currently firing
firing,
// Last fire value (for non-forgettable lists)
memory,
// Flag to know if list was already fired
fired,
// End of the loop when firing
firingLength,
// Index of currently firing callback (modified by remove if needed)
firingIndex,
// First callback to fire (used internally by add and fireWith)
firingStart,
// Actual callback list
list = [],
// Stack of fire calls for repeatable lists
stack = !options.once && [],
// Fire callbacks
fire = function( data ) {
memory = options.memory && data;
fired = true;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true;
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if ( list ) {
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) {
list = [];
} else {
self.disable();
}
}
},
// Actual Callbacks object
self = {
// Add a callback or a collection of callbacks to the list
add: function() {
if ( list ) {
// First, we save the current length
var start = list.length;
(function add( args ) {
jQuery.each( args, function( _, arg ) {
var type = jQuery.type( arg );
if ( type === "function" ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && type !== "string" ) {
// Inspect recursively
add( arg );
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch?
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
} else if ( memory ) {
firingStart = start;
fire( memory );
}
}
return this;
},
// Remove a callback from the list
remove: function() {
if ( list ) {
jQuery.each( arguments, function( _, arg ) {
var index;
while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );
// Handle firing indexes
if ( firing ) {
if ( index <= firingLength ) {
firingLength--;
}
if ( index <= firingIndex ) {
firingIndex--;
}
}
}
});
}
return this;
},
// Check if a given callback is in the list.
// If no argument is given, return whether or not list has callbacks attached.
has: function( fn ) {
return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
},
// Remove all callbacks from the list
empty: function() {
list = [];
firingLength = 0;
return this;
},
// Have the list do nothing anymore
disable: function() {
list = stack = memory = undefined;
return this;
},
// Is it disabled?
disabled: function() {
return !list;
},
// Lock the list in its current state
lock: function() {
stack = undefined;
if ( !memory ) {
self.disable();
}
return this;
},
// Is it locked?
locked: function() {
return !stack;
},
// Call all callbacks with the given context and arguments
fireWith: function( context, args ) {
if ( list && ( !fired || stack ) ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
if ( firing ) {
stack.push( args );
} else {
fire( args );
}
}
return this;
},
// Call all the callbacks with the given arguments
fire: function() {
self.fireWith( this, arguments );
return this;
},
// To know if the callbacks have already been called at least once
fired: function() {
return !!fired;
}
};
return self;
};
jQuery.extend({
Deferred: function( func ) {
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
],
state = "pending",
promise = {
state: function() {
return state;
},
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ](function() {
var returned = fn && fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
}
});
});
fns = null;
}).promise();
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {
return obj != null ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {};
// Keep pipe for back-compat
promise.pipe = promise.then;
// Add list-specific methods
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
// promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add;
// Handle state
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString;
// [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
// deferred[ resolve | reject | notify ]
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
});
// Make the deferred a promise
promise.promise( deferred );
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
},
// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
var i = 0,
resolveValues = slice.call( arguments ),
length = resolveValues.length,
// the count of uncompleted subordinates
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
// Update function for both resolve and progress values
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
if ( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !(--remaining) ) {
deferred.resolveWith( contexts, values );
}
};
},
progressValues, progressContexts, resolveContexts;
// add listeners to Deferred subordinates; treat others as resolved
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
.done( updateFunc( i, resolveContexts, resolveValues ) )
.fail( deferred.reject )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
}
// if we're not waiting on anything, resolve the master
if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
}
return deferred.promise();
}
});
// The deferred used on DOM ready
var readyList;
jQuery.fn.ready = function( fn ) {
// Add the callback
jQuery.ready.promise().done( fn );
return this;
};
jQuery.extend({
// Is the DOM ready to be used? Set to true once it occurs.
isReady: false,
// A counter to track how many items to wait for before
// the ready event fires. See #6781
readyWait: 1,
// Hold (or release) the ready event
holdReady: function( hold ) {
if ( hold ) {
jQuery.readyWait++;
} else {
jQuery.ready( true );
}
},
// Handle when the DOM is ready
ready: function( wait ) {
// Abort if there are pending holds or we're already ready
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
return;
}
// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
if ( !document.body ) {
return setTimeout( jQuery.ready );
}
// Remember that the DOM is ready
jQuery.isReady = true;
// If a normal DOM Ready event fired, decrement, and wait if need be
if ( wait !== true && --jQuery.readyWait > 0 ) {
return;
}
// If there are functions bound, to execute
readyList.resolveWith( document, [ jQuery ] );
// Trigger any bound ready events
if ( jQuery.fn.triggerHandler ) {
jQuery( document ).triggerHandler( "ready" );
jQuery( document ).off( "ready" );
}
}
});
/**
* Clean-up method for dom ready events
*/
function detach() {
if ( document.addEventListener ) {
document.removeEventListener( "DOMContentLoaded", completed, false );
window.removeEventListener( "load", completed, false );
} else {
document.detachEvent( "onreadystatechange", completed );
window.detachEvent( "onload", completed );
}
}
/**
* The ready event handler and self cleanup method
*/
function completed() {
// readyState === "complete" is good enough for us to call the dom ready in oldIE
if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
detach();
jQuery.ready();
}
}
jQuery.ready.promise = function( obj ) {
if ( !readyList ) {
readyList = jQuery.Deferred();
// Catch cases where $(document).ready() is called after the browser event has already occurred.
// we once tried to use readyState "interactive" here, but it caused issues like the one
// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
if ( document.readyState === "complete" ) {
// Handle it asynchronously to allow scripts the opportunity to delay ready
setTimeout( jQuery.ready );
// Standards-based browsers support DOMContentLoaded
} else if ( document.addEventListener ) {
// Use the handy event callback
document.addEventListener( "DOMContentLoaded", completed, false );
// A fallback to window.onload, that will always work
window.addEventListener( "load", completed, false );
// If IE event model is used
} else {
// Ensure firing before onload, maybe late but safe also for iframes
document.attachEvent( "onreadystatechange", completed );
// A fallback to window.onload, that will always work
window.attachEvent( "onload", completed );
// If IE and not a frame
// continually check to see if the document is ready
var top = false;
try {
top = window.frameElement == null && document.documentElement;
} catch(e) {}
if ( top && top.doScroll ) {
(function doScrollCheck() {
if ( !jQuery.isReady ) {
try {
// Use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
top.doScroll("left");
} catch(e) {
return setTimeout( doScrollCheck, 50 );
}
// detach all dom ready events
detach();
// and execute any waiting functions
jQuery.ready();
}
})();
}
}
}
return readyList.promise( obj );
};
var strundefined = typeof undefined;
// Support: IE<9
// Iteration over object's inherited properties before its own
var i;
for ( i in jQuery( support ) ) {
break;
}
support.ownLast = i !== "0";
// Note: most support tests are defined in their respective modules.
// false until the test is run
support.inlineBlockNeedsLayout = false;
// Execute ASAP in case we need to set body.style.zoom
jQuery(function() {
// Minified: var a,b,c,d
var val, div, body, container;
body = document.getElementsByTagName( "body" )[ 0 ];
if ( !body || !body.style ) {
// Return for frameset docs that don't have a body
return;
}
// Setup
div = document.createElement( "div" );
container = document.createElement( "div" );
container.style.cssText = "position:absolute;border:0;width:0;height:0;top:0;left:-9999px";
body.appendChild( container ).appendChild( div );
if ( typeof div.style.zoom !== strundefined ) {
// Support: IE<8
// Check if natively block-level elements act like inline-block
// elements when setting their display to 'inline' and giving
// them layout
div.style.cssText = "display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1";
support.inlineBlockNeedsLayout = val = div.offsetWidth === 3;
if ( val ) {
// Prevent IE 6 from affecting layout for positioned elements #11048
// Prevent IE from shrinking the body in IE 7 mode #12869
// Support: IE<8
body.style.zoom = 1;
}
}
body.removeChild( container );
});
(function() {
var div = document.createElement( "div" );
// Execute the test only if not already executed in another module.
if (support.deleteExpando == null) {
// Support: IE<9
support.deleteExpando = true;
try {
delete div.test;
} catch( e ) {
support.deleteExpando = false;
}
}
// Null elements to avoid leaks in IE.
div = null;
})();
/**
* Determines whether an object can have data
*/
jQuery.acceptData = function( elem ) {
var noData = jQuery.noData[ (elem.nodeName + " ").toLowerCase() ],
nodeType = +elem.nodeType || 1;
// Do not set data on non-element DOM nodes because it will not be cleared (#8335).
return nodeType !== 1 && nodeType !== 9 ?
false :
// Nodes accept data unless otherwise specified; rejection can be conditional
!noData || noData !== true && elem.getAttribute("classid") === noData;
};
var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
rmultiDash = /([A-Z])/g;
function dataAttr( elem, key, data ) {
// If nothing was found internally, try to fetch any
// data from the HTML5 data-* attribute
if ( data === undefined && elem.nodeType === 1 ) {
var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
data = elem.getAttribute( name );
if ( typeof data === "string" ) {
try {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
// Only convert to a number if it doesn't change the string
+data + "" === data ? +data :
rbrace.test( data ) ? jQuery.parseJSON( data ) :
data;
} catch( e ) {}
// Make sure we set the data so it isn't changed later
jQuery.data( elem, key, data );
} else {
data = undefined;
}
}
return data;
}
// checks a cache object for emptiness
function isEmptyDataObject( obj ) {
var name;
for ( name in obj ) {
// if the public data object is empty, the private is still empty
if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
continue;
}
if ( name !== "toJSON" ) {
return false;
}
}
return true;
}
function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var ret, thisCache,
internalKey = jQuery.expando,
// We have to handle DOM nodes and JS objects differently because IE6-7
// can't GC object references properly across the DOM-JS boundary
isNode = elem.nodeType,
// Only DOM nodes need the global jQuery cache; JS object data is
// attached directly to the object so GC can occur automatically
cache = isNode ? jQuery.cache : elem,
// Only defining an ID for JS objects if its cache already exists allows
// the code to shortcut on the same path as a DOM node with no cache
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
// Avoid doing any more work than we need to when trying to get data on an
// object that has no data at all
if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
return;
}
if ( !id ) {
// Only DOM nodes need a new unique ID for each element since their data
// ends up in the global cache
if ( isNode ) {
id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
} else {
id = internalKey;
}
}
if ( !cache[ id ] ) {
// Avoid exposing jQuery metadata on plain JS objects when the object
// is serialized using JSON.stringify
cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
}
// An object can be passed to jQuery.data instead of a key/value pair; this gets
// shallow copied over onto the existing cache
if ( typeof name === "object" || typeof name === "function" ) {
if ( pvt ) {
cache[ id ] = jQuery.extend( cache[ id ], name );
} else {
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
}
}
thisCache = cache[ id ];
// jQuery data() is stored in a separate object inside the object's internal data
// cache in order to avoid key collisions between internal data and user-defined
// data.
if ( !pvt ) {
if ( !thisCache.data ) {
thisCache.data = {};
}
thisCache = thisCache.data;
}
if ( data !== undefined ) {
thisCache[ jQuery.camelCase( name ) ] = data;
}
// Check for both converted-to-camel and non-converted data property names
// If a data property was specified
if ( typeof name === "string" ) {
// First Try to find as-is property data
ret = thisCache[ name ];
// Test for null|undefined property data
if ( ret == null ) {
// Try to find the camelCased property
ret = thisCache[ jQuery.camelCase( name ) ];
}
} else {
ret = thisCache;
}
return ret;
}
function internalRemoveData( elem, name, pvt ) {
if ( !jQuery.acceptData( elem ) ) {
return;
}
var thisCache, i,
isNode = elem.nodeType,
// See jQuery.data for more information
cache = isNode ? jQuery.cache : elem,
id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
// If there is already no cache entry for this object, there is no
// purpose in continuing
if ( !cache[ id ] ) {
return;
}
if ( name ) {
thisCache = pvt ? cache[ id ] : cache[ id ].data;
if ( thisCache ) {
// Support array or space separated string names for data keys
if ( !jQuery.isArray( name ) ) {
// try the string as a key before any manipulation
if ( name in thisCache ) {
name = [ name ];
} else {
// split the camel cased version by spaces unless a key with the spaces exists
name = jQuery.camelCase( name );
if ( name in thisCache ) {
name = [ name ];
} else {
name = name.split(" ");
}
}
} else {
// If "name" is an array of keys...
// When data is initially created, via ("key", "val") signature,
// keys will be converted to camelCase.
// Since there is no way to tell _how_ a key was added, remove
// both plain key and camelCase key. #12786
// This will only penalize the array argument path.
name = name.concat( jQuery.map( name, jQuery.camelCase ) );
}
i = name.length;
while ( i-- ) {
delete thisCache[ name[i] ];
}
// If there is no data left in the cache, we want to continue
// and let the cache object itself get destroyed
if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {
return;
}
}
}
// See jQuery.data for more information
if ( !pvt ) {
delete cache[ id ].data;
// Don't destroy the parent cache unless the internal data object
// had been the only thing left in it
if ( !isEmptyDataObject( cache[ id ] ) ) {
return;
}
}
// Destroy the cache
if ( isNode ) {
jQuery.cleanData( [ elem ], true );
// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
/* jshint eqeqeq: false */
} else if ( support.deleteExpando || cache != cache.window ) {
/* jshint eqeqeq: true */
delete cache[ id ];
// When all else fails, null
} else {
cache[ id ] = null;
}
}
jQuery.extend({
cache: {},
// The following elements (space-suffixed to avoid Object.prototype collisions)
// throw uncatchable exceptions if you attempt to set expando properties
noData: {
"applet ": true,
"embed ": true,
// ...but Flash objects (which have this classid) *can* handle expandos
"object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
},
hasData: function( elem ) {
elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
return !!elem && !isEmptyDataObject( elem );
},
data: function( elem, name, data ) {
return internalData( elem, name, data );
},
removeData: function( elem, name ) {
return internalRemoveData( elem, name );
},
// For internal use only.
_data: function( elem, name, data ) {
return internalData( elem, name, data, true );
},
_removeData: function( elem, name ) {
return internalRemoveData( elem, name, true );
}
});
jQuery.fn.extend({
data: function( key, value ) {
var i, name, data,
elem = this[0],
attrs = elem && elem.attributes;
// Special expections of .data basically thwart jQuery.access,
// so implement the relevant behavior ourselves
// Gets all values
if ( key === undefined ) {
if ( this.length ) {
data = jQuery.data( elem );
if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
i = attrs.length;
while ( i-- ) {
// Support: IE11+
// The attrs elements can be null (#14894)
if ( attrs[ i ] ) {
name = attrs[ i ].name;
if ( name.indexOf( "data-" ) === 0 ) {
name = jQuery.camelCase( name.slice(5) );
dataAttr( elem, name, data[ name ] );
}
}
}
jQuery._data( elem, "parsedAttrs", true );
}
}
return data;
}
// Sets multiple values
if ( typeof key === "object" ) {
return this.each(function() {
jQuery.data( this, key );
});
}
return arguments.length > 1 ?
// Sets one value
this.each(function() {
jQuery.data( this, key, value );
}) :
// Gets one value
// Try to fetch any internally stored data first
elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : undefined;
},
removeData: function( key ) {
return this.each(function() {
jQuery.removeData( this, key );
});
}
});
jQuery.extend({
queue: function( elem, type, data ) {
var queue;
if ( elem ) {
type = ( type || "fx" ) + "queue";
queue = jQuery._data( elem, type );
// Speed up dequeue by getting out quickly if this is just a lookup
if ( data ) {
if ( !queue || jQuery.isArray(data) ) {
queue = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
queue.push( data );
}
}
return queue || [];
}
},
dequeue: function( elem, type ) {
type = type || "fx";
var queue = jQuery.queue( elem, type ),
startLength = queue.length,
fn = queue.shift(),
hooks = jQuery._queueHooks( elem, type ),
next = function() {
jQuery.dequeue( elem, type );
};
// If the fx queue is dequeued, always remove the progress sentinel
if ( fn === "inprogress" ) {
fn = queue.shift();
startLength--;
}
if ( fn ) {
// Add a progress sentinel to prevent the fx queue from being
// automatically dequeued
if ( type === "fx" ) {
queue.unshift( "inprogress" );
}
// clear up the last queue stop function
delete hooks.stop;
fn.call( elem, next, hooks );
}
if ( !startLength && hooks ) {
hooks.empty.fire();
}
},
// not intended for public consumption - generates a queueHooks object, or returns the current one
_queueHooks: function( elem, type ) {
var key = type + "queueHooks";
return jQuery._data( elem, key ) || jQuery._data( elem, key, {
empty: jQuery.Callbacks("once memory").add(function() {
jQuery._removeData( elem, type + "queue" );
jQuery._removeData( elem, key );
})
});
}
});
jQuery.fn.extend({
queue: function( type, data ) {
var setter = 2;
if ( typeof type !== "string" ) {
data = type;
type = "fx";
setter--;
}
if ( arguments.length < setter ) {
return jQuery.queue( this[0], type );
}
return data === undefined ?
this :
this.each(function() {
var queue = jQuery.queue( this, type, data );
// ensure a hooks for this queue
jQuery._queueHooks( this, type );
if ( type === "fx" && queue[0] !== "inprogress" ) {
jQuery.dequeue( this, type );
}
});
},
dequeue: function( type ) {
return this.each(function() {
jQuery.dequeue( this, type );
});
},
clearQueue: function( type ) {
return this.queue( type || "fx", [] );
},
// Get a promise resolved when queues of a certain type
// are emptied (fx is the type by default)
promise: function( type, obj ) {
var tmp,
count = 1,
defer = jQuery.Deferred(),
elements = this,
i = this.length,
resolve = function() {
if ( !( --count ) ) {
defer.resolveWith( elements, [ elements ] );
}
};
if ( typeof type !== "string" ) {
obj = type;
type = undefined;
}
type = type || "fx";
while ( i-- ) {
tmp = jQuery._data( elements[ i ], type + "queueHooks" );
if ( tmp && tmp.empty ) {
count++;
tmp.empty.add( resolve );
}
}
resolve();
return defer.promise( obj );
}
});
var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;
var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
var isHidden = function( elem, el ) {
// isHidden might be called from jQuery#filter function;
// in that case, element will be second argument
elem = el || elem;
return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
};
// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
length = elems.length,
bulk = key == null;
// Sets many values
if ( jQuery.type( key ) === "object" ) {
chainable = true;
for ( i in key ) {
jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
}
// Sets one value
} else if ( value !== undefined ) {
chainable = true;
if ( !jQuery.isFunction( value ) ) {
raw = true;
}
if ( bulk ) {
// Bulk operations run against the entire set
if ( raw ) {
fn.call( elems, value );
fn = null;
// ...except when executing function values
} else {
bulk = fn;
fn = function( elem, key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}
if ( fn ) {
for ( ; i < length; i++ ) {
fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
}
}
}
return chainable ?
elems :
// Gets
bulk ?
fn.call( elems ) :
length ? fn( elems[0], key ) : emptyGet;
};
var rcheckableType = (/^(?:checkbox|radio)$/i);
(function() {
// Minified: var a,b,c
var input = document.createElement( "input" ),
div = document.createElement( "div" ),
fragment = document.createDocumentFragment();
// Setup
div.innerHTML = "
a";
// IE strips leading whitespace when .innerHTML is used
support.leadingWhitespace = div.firstChild.nodeType === 3;
// Make sure that tbody elements aren't automatically inserted
// IE will insert them into empty tables
support.tbody = !div.getElementsByTagName( "tbody" ).length;
// Make sure that link elements get serialized correctly by innerHTML
// This requires a wrapper element in IE
support.htmlSerialize = !!div.getElementsByTagName( "link" ).length;
// Makes sure cloning an html5 element does not cause problems
// Where outerHTML is undefined, this still works
support.html5Clone =
document.createElement( "nav" ).cloneNode( true ).outerHTML !== "<:nav>";
// Check if a disconnected checkbox will retain its checked
// value of true after appended to the DOM (IE6/7)
input.type = "checkbox";
input.checked = true;
fragment.appendChild( input );
support.appendChecked = input.checked;
// Make sure textarea (and checkbox) defaultValue is properly cloned
// Support: IE6-IE11+
div.innerHTML = "";
support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
// #11217 - WebKit loses check when the name is after the checked attribute
fragment.appendChild( div );
div.innerHTML = "";
// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
// old WebKit doesn't clone checked state correctly in fragments
support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
// Support: IE<9
// Opera does not clone events (and typeof div.attachEvent === undefined).
// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
support.noCloneEvent = true;
if ( div.attachEvent ) {
div.attachEvent( "onclick", function() {
support.noCloneEvent = false;
});
div.cloneNode( true ).click();
}
// Execute the test only if not already executed in another module.
if (support.deleteExpando == null) {
// Support: IE<9
support.deleteExpando = true;
try {
delete div.test;
} catch( e ) {
support.deleteExpando = false;
}
}
})();
(function() {
var i, eventName,
div = document.createElement( "div" );
// Support: IE<9 (lack submit/change bubble), Firefox 23+ (lack focusin event)
for ( i in { submit: true, change: true, focusin: true }) {
eventName = "on" + i;
if ( !(support[ i + "Bubbles" ] = eventName in window) ) {
// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
div.setAttribute( eventName, "t" );
support[ i + "Bubbles" ] = div.attributes[ eventName ].expando === false;
}
}
// Null elements to avoid leaks in IE.
div = null;
})();
var rformElems = /^(?:input|select|textarea)$/i,
rkeyEvent = /^key/,
rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
function returnTrue() {
return true;
}
function returnFalse() {
return false;
}
function safeActiveElement() {
try {
return document.activeElement;
} catch ( err ) { }
}
/*
* Helper functions for managing events -- not part of the public interface.
* Props to Dean Edwards' addEvent library for many of the ideas.
*/
jQuery.event = {
global: {},
add: function( elem, types, handler, data, selector ) {
var tmp, events, t, handleObjIn,
special, eventHandle, handleObj,
handlers, type, namespaces, origType,
elemData = jQuery._data( elem );
// Don't attach events to noData or text/comment nodes (but allow plain objects)
if ( !elemData ) {
return;
}
// Caller can pass in an object of custom data in lieu of the handler
if ( handler.handler ) {
handleObjIn = handler;
handler = handleObjIn.handler;
selector = handleObjIn.selector;
}
// Make sure that the handler has a unique ID, used to find/remove it later
if ( !handler.guid ) {
handler.guid = jQuery.guid++;
}
// Init the element's event structure and main handler, if this is the first
if ( !(events = elemData.events) ) {
events = elemData.events = {};
}
if ( !(eventHandle = elemData.handle) ) {
eventHandle = elemData.handle = function( e ) {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== strundefined && (!e || jQuery.event.triggered !== e.type) ?
jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
undefined;
};
// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
eventHandle.elem = elem;
}
// Handle multiple events separated by a space
types = ( types || "" ).match( rnotwhite ) || [ "" ];
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[t] ) || [];
type = origType = tmp[1];
namespaces = ( tmp[2] || "" ).split( "." ).sort();
// There *must* be a type, no attaching namespace-only handlers
if ( !type ) {
continue;
}
// If event changes its type, use the special event handlers for the changed type
special = jQuery.event.special[ type ] || {};
// If selector defined, determine special event api type, otherwise given type
type = ( selector ? special.delegateType : special.bindType ) || type;
// Update special based on newly reset type
special = jQuery.event.special[ type ] || {};
// handleObj is passed to all event handlers
handleObj = jQuery.extend({
type: type,
origType: origType,
data: data,
handler: handler,
guid: handler.guid,
selector: selector,
needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
namespace: namespaces.join(".")
}, handleObjIn );
// Init the event handler queue if we're the first
if ( !(handlers = events[ type ]) ) {
handlers = events[ type ] = [];
handlers.delegateCount = 0;
// Only use addEventListener/attachEvent if the special events handler returns false
if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
// Bind the global event handler to the element
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, eventHandle );
}
}
}
if ( special.add ) {
special.add.call( elem, handleObj );
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
}
// Add to the element's handler list, delegates in front
if ( selector ) {
handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
handlers.push( handleObj );
}
// Keep track of which events have ever been used, for event optimization
jQuery.event.global[ type ] = true;
}
// Nullify elem to prevent memory leaks in IE
elem = null;
},
// Detach an event or set of events from an element
remove: function( elem, types, handler, selector, mappedTypes ) {
var j, handleObj, tmp,
origCount, t, events,
special, handlers, type,
namespaces, origType,
elemData = jQuery.hasData( elem ) && jQuery._data( elem );
if ( !elemData || !(events = elemData.events) ) {
return;
}
// Once for each type.namespace in types; type may be omitted
types = ( types || "" ).match( rnotwhite ) || [ "" ];
t = types.length;
while ( t-- ) {
tmp = rtypenamespace.exec( types[t] ) || [];
type = origType = tmp[1];
namespaces = ( tmp[2] || "" ).split( "." ).sort();
// Unbind all events (on this namespace, if provided) for the element
if ( !type ) {
for ( type in events ) {
jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
}
continue;
}
special = jQuery.event.special[ type ] || {};
type = ( selector ? special.delegateType : special.bindType ) || type;
handlers = events[ type ] || [];
tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
// Remove matching events
origCount = j = handlers.length;
while ( j-- ) {
handleObj = handlers[ j ];
if ( ( mappedTypes || origType === handleObj.origType ) &&
( !handler || handler.guid === handleObj.guid ) &&
( !tmp || tmp.test( handleObj.namespace ) ) &&
( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
handlers.splice( j, 1 );
if ( handleObj.selector ) {
handlers.delegateCount--;
}
if ( special.remove ) {
special.remove.call( elem, handleObj );
}
}
}
// Remove generic event handler if we removed something and no more handlers exist
// (avoids potential for endless recursion during removal of special event handlers)
if ( origCount && !handlers.length ) {
if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
jQuery.removeEvent( elem, type, elemData.handle );
}
delete events[ type ];
}
}
// Remove the expando if it's no longer used
if ( jQuery.isEmptyObject( events ) ) {
delete elemData.handle;
// removeData also checks for emptiness and clears the expando if empty
// so use it instead of delete
jQuery._removeData( elem, "events" );
}
},
trigger: function( event, data, elem, onlyHandlers ) {
var handle, ontype, cur,
bubbleType, special, tmp, i,
eventPath = [ elem || document ],
type = hasOwn.call( event, "type" ) ? event.type : event,
namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
cur = tmp = elem = elem || document;
// Don't do events on text and comment nodes
if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
return;
}
// focus/blur morphs to focusin/out; ensure we're not firing them right now
if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
return;
}
if ( type.indexOf(".") >= 0 ) {
// Namespaced trigger; create a regexp to match event type in handle()
namespaces = type.split(".");
type = namespaces.shift();
namespaces.sort();
}
ontype = type.indexOf(":") < 0 && "on" + type;
// Caller can pass in a jQuery.Event object, Object, or just an event type string
event = event[ jQuery.expando ] ?
event :
new jQuery.Event( type, typeof event === "object" && event );
// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
event.isTrigger = onlyHandlers ? 2 : 3;
event.namespace = namespaces.join(".");
event.namespace_re = event.namespace ?
new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
null;
// Clean up the event in case it is being reused
event.result = undefined;
if ( !event.target ) {
event.target = elem;
}
// Clone any incoming data and prepend the event, creating the handler arg list
data = data == null ?
[ event ] :
jQuery.makeArray( data, [ event ] );
// Allow special events to draw outside the lines
special = jQuery.event.special[ type ] || {};
if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
return;
}
// Determine event propagation path in advance, per W3C events spec (#9951)
// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
bubbleType = special.delegateType || type;
if ( !rfocusMorph.test( bubbleType + type ) ) {
cur = cur.parentNode;
}
for ( ; cur; cur = cur.parentNode ) {
eventPath.push( cur );
tmp = cur;
}
// Only add window if we got to document (e.g., not plain obj or detached DOM)
if ( tmp === (elem.ownerDocument || document) ) {
eventPath.push( tmp.defaultView || tmp.parentWindow || window );
}
}
// Fire handlers on the event path
i = 0;
while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
event.type = i > 1 ?
bubbleType :
special.bindType || type;
// jQuery handler
handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
if ( handle ) {
handle.apply( cur, data );
}
// Native handler
handle = ontype && cur[ ontype ];
if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
event.result = handle.apply( cur, data );
if ( event.result === false ) {
event.preventDefault();
}
}
}
event.type = type;
// If nobody prevented the default action, do it now
if ( !onlyHandlers && !event.isDefaultPrevented() ) {
if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
jQuery.acceptData( elem ) ) {
// Call a native DOM method on the target with the same name name as the event.
// Can't use an .isFunction() check here because IE6/7 fails that test.
// Don't do default actions on window, that's where global variables be (#6170)
if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
// Don't re-trigger an onFOO event when we call its FOO() method
tmp = elem[ ontype ];
if ( tmp ) {
elem[ ontype ] = null;
}
// Prevent re-triggering of the same event, since we already bubbled it above
jQuery.event.triggered = type;
try {
elem[ type ]();
} catch ( e ) {
// IE<9 dies on focus/blur to hidden element (#1486,#12518)
// only reproducible on winXP IE8 native, not IE9 in IE8 mode
}
jQuery.event.triggered = undefined;
if ( tmp ) {
elem[ ontype ] = tmp;
}
}
}
}
return event.result;
},
dispatch: function( event ) {
// Make a writable jQuery.Event from the native event object
event = jQuery.event.fix( event );
var i, ret, handleObj, matched, j,
handlerQueue = [],
args = slice.call( arguments ),
handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
special = jQuery.event.special[ event.type ] || {};
// Use the fix-ed jQuery.Event rather than the (read-only) native event
args[0] = event;
event.delegateTarget = this;
// Call the preDispatch hook for the mapped type, and let it bail if desired
if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
return;
}
// Determine handlers
handlerQueue = jQuery.event.handlers.call( this, event, handlers );
// Run delegates first; they may want to stop propagation beneath us
i = 0;
while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
event.currentTarget = matched.elem;
j = 0;
while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
// Triggered event must either 1) have no namespace, or
// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
event.handleObj = handleObj;
event.data = handleObj.data;
ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
.apply( matched.elem, args );
if ( ret !== undefined ) {
if ( (event.result = ret) === false ) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}
// Call the postDispatch hook for the mapped type
if ( special.postDispatch ) {
special.postDispatch.call( this, event );
}
return event.result;
},
handlers: function( event, handlers ) {
var sel, handleObj, matches, i,
handlerQueue = [],
delegateCount = handlers.delegateCount,
cur = event.target;
// Find delegate handlers
// Black-hole SVG